├── GPSTime.cpp ├── CosaCompat.h ├── doc ├── Acknowledgements.md ├── Program.md ├── ublox.md ├── Troubleshooting.md ├── Installing.md ├── RAM.md ├── Examples.md ├── Performance.md ├── Extending.md ├── Tradeoffs.md ├── Data Model.md └── Configurations.md ├── .gitattributes ├── examples ├── NMEAblink │ └── NMEAblink.ino ├── NMEA │ └── NMEA.ino ├── NMEAbenchmark │ └── NMEAbenchmark.ino ├── NMEAfused │ └── NMEAfused.ino ├── NMEAcoherent │ └── NMEAcoherent.ino ├── PUBX │ └── PUBX.ino ├── ublox │ └── ublox.ino └── NMEAtest │ └── NMEAtest.ino ├── ublox ├── ubxmsg.cpp ├── ubxNMEA.cpp ├── ubxNMEA.h ├── ubxGPS.h ├── ubxmsg.h └── ubxGPS.cpp ├── .gitignore ├── GPSfix_cfg.h ├── configs ├── Nominal │ ├── GPSfix_cfg.h │ └── NMEAGPS_cfg.h ├── DTL │ ├── GPSfix_cfg.h │ └── NMEAGPS_cfg.h └── Minimal │ ├── GPSfix_cfg.h │ └── NMEAGPS_cfg.h ├── Streamers.h ├── NeoGPS_cfg.h ├── GPSTime.h ├── README.md ├── Time.cpp ├── NMEAGPS_cfg.h ├── Streamers.cpp ├── Time.h ├── GPSfix.h └── NMEAGPS.h /GPSTime.cpp: -------------------------------------------------------------------------------- 1 | #include "GPSTime.h" 2 | 3 | uint8_t GPSTime::leap_seconds = 0; 4 | NeoGPS::clock_t GPSTime::s_start_of_week = 0; 5 | -------------------------------------------------------------------------------- /CosaCompat.h: -------------------------------------------------------------------------------- 1 | #ifndef COSACOMPAT_H 2 | #define COSACOMPAT_H 3 | 4 | #include 5 | 6 | typedef PGM_P str_P; 7 | #define __PROGMEM PROGMEM 8 | 9 | #endif -------------------------------------------------------------------------------- /doc/Acknowledgements.md: -------------------------------------------------------------------------------- 1 | Acknowledgements 2 | ========== 3 | Mikal Hart's [TinyGPS](https://github.com/mikalhart/TinyGPS) for the generic `decode` approach. 4 | 5 | tht's [initial implementation](http://forum.arduino.cc/index.php?topic=150299.msg1863220#msg1863220) of a Cosa `IOStream::Device`. 6 | -------------------------------------------------------------------------------- /doc/Program.md: -------------------------------------------------------------------------------- 1 | Program Space requirements 2 | ======= 3 | 4 | The **Minimal** configuration requires 866 bytes. 5 | 6 | The **DTL** configuration requires 2072 bytes. 7 | 8 | The **Nominal** configuration requires 2800 bytes. TinyGPS uses about 2400 bytes. 9 | 10 | The **Full** configuration requires 3462 bytes. 11 | 12 | (All configuration numbers include 166 bytes PROGMEM.) 13 | 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /doc/ublox.md: -------------------------------------------------------------------------------- 1 | ####u-blox NEO-6 2 | #####NMEA 0183 Proprietary Messages 3 | * UBX,00 - Lat/Long Position Data 4 | * UBX,04 - Time of Day and Clock Information 5 | 6 | #####UBX Protocol Messages 7 | * NAV_STATUS - Receiver Navigation Status 8 | * NAV_TIMEGPS - GPS Time Solution 9 | * NAV_TIMEUTC - UTC Time Solution 10 | * NAV_POSLLH - Geodetic Position Solution 11 | * NAV_VELNED - Velocity Solution in NED (North/East/Down) 12 | * NAV_SVINFO - Space Vehicle Information 13 | 14 | ####ubloxNMEA 15 | This derived class has the following configuration items near the top of ubxNMEA.h: 16 | ``` 17 | #define NMEAGPS_PARSE_PUBX_00 18 | #define NMEAGPS_PARSE_PUBX_04 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /examples/NMEAblink/NMEAblink.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Serial should be connected to the GPS device. 3 | This will toggle the LED once per second, when a GPRMC message is received. 4 | */ 5 | 6 | #include 7 | 8 | #include "NMEAGPS.h" 9 | 10 | static NMEAGPS gps; 11 | 12 | static const int led = 13; 13 | 14 | void setup() 15 | { 16 | // Start the UART for the GPS device 17 | Serial.begin(9600); 18 | pinMode(led, OUTPUT); 19 | } 20 | 21 | void loop() 22 | { 23 | while (Serial.available()) { 24 | if (gps.decode( Serial.read() ) == NMEAGPS::DECODE_COMPLETED) { 25 | if (gps.nmeaMessage == NMEAGPS::NMEA_RMC) 26 | digitalWrite(led, !digitalRead(led)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ublox/ubxmsg.cpp: -------------------------------------------------------------------------------- 1 | #include "ubxGPS.h" 2 | 3 | using namespace ublox; 4 | 5 | bool ublox::configNMEA( ubloxGPS &gps, NMEAGPS::nmea_msg_t msgType, uint8_t rate ) 6 | { 7 | static const ubx_nmea_msg_t ubx[] __PROGMEM = { 8 | UBX_GPGGA, 9 | UBX_GPGLL, 10 | UBX_GPGSA, 11 | UBX_GPGSV, 12 | UBX_GPRMC, 13 | UBX_GPVTG, 14 | UBX_GPZDA, 15 | }; 16 | 17 | uint8_t msg_index = (uint8_t) msgType - (uint8_t) NMEAGPS::NMEA_FIRST_MSG; 18 | 19 | if (msg_index >= sizeof(ubx)/sizeof(ubx[0])) 20 | return false; 21 | 22 | msg_id_t msg_id = (msg_id_t) pgm_read_byte( &ubx[msg_index] ); 23 | 24 | return gps.send( cfg_msg_t( UBX_NMEA, msg_id, rate ) ); 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /GPSfix_cfg.h: -------------------------------------------------------------------------------- 1 | #ifndef GPS_FIX_CFG 2 | #define GPS_FIX_CFG 3 | 4 | /** 5 | * Enable/disable the storage for the members of a fix. 6 | * 7 | * Disabling a member prevents it from being parsed from a received message. 8 | * The disabled member cannot be accessed or stored, and its validity flag 9 | * would not be available. It will not be declared, and code that uses that 10 | * member will not compile. 11 | * 12 | * DATE and TIME are somewhat coupled in that they share a single `time_t`, 13 | * but they have separate validity flags. 14 | * 15 | * See also note regarding the DOP members, below. 16 | * 17 | */ 18 | 19 | #define GPS_FIX_DATE 20 | #define GPS_FIX_TIME 21 | #define GPS_FIX_LOCATION 22 | #define GPS_FIX_ALTITUDE 23 | #define GPS_FIX_SPEED 24 | #define GPS_FIX_HEADING 25 | #define GPS_FIX_SATELLITES 26 | #define GPS_FIX_HDOP 27 | //#define GPS_FIX_VDOP 28 | //#define GPS_FIX_PDOP 29 | //#define GPS_FIX_LAT_ERR 30 | //#define GPS_FIX_LON_ERR 31 | //#define GPS_FIX_ALT_ERR 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /configs/Nominal/GPSfix_cfg.h: -------------------------------------------------------------------------------- 1 | #ifndef GPS_FIX_CFG 2 | #define GPS_FIX_CFG 3 | 4 | /** 5 | * Enable/disable the storage for the members of a fix. 6 | * 7 | * Disabling a member prevents it from being parsed from a received message. 8 | * The disabled member cannot be accessed or stored, and its validity flag 9 | * would not be available. It will not be declared, and code that uses that 10 | * member will not compile. 11 | * 12 | * DATE and TIME are somewhat coupled in that they share a single `time_t`, 13 | * but they have separate validity flags. 14 | * 15 | * See also note regarding the DOP members, below. 16 | * 17 | */ 18 | 19 | #define GPS_FIX_DATE 20 | #define GPS_FIX_TIME 21 | #define GPS_FIX_LOCATION 22 | #define GPS_FIX_ALTITUDE 23 | #define GPS_FIX_SPEED 24 | #define GPS_FIX_HEADING 25 | #define GPS_FIX_SATELLITES 26 | #define GPS_FIX_HDOP 27 | //#define GPS_FIX_VDOP 28 | //#define GPS_FIX_PDOP 29 | //#define GPS_FIX_LAT_ERR 30 | //#define GPS_FIX_LON_ERR 31 | //#define GPS_FIX_ALT_ERR 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /configs/DTL/GPSfix_cfg.h: -------------------------------------------------------------------------------- 1 | #ifndef GPS_FIX_CFG 2 | #define GPS_FIX_CFG 3 | 4 | /** 5 | * Enable/disable the storage for the members of a fix. 6 | * 7 | * Disabling a member prevents it from being parsed from a received message. 8 | * The disabled member cannot be accessed or stored, and its validity flag 9 | * would not be available. It will not be declared, and code that uses that 10 | * member will not compile. 11 | * 12 | * DATE and TIME are somewhat coupled in that they share a single `time_t`, 13 | * but they have separate validity flags. 14 | * 15 | * See also note regarding the DOP members, below. 16 | * 17 | */ 18 | 19 | #define GPS_FIX_DATE 20 | #define GPS_FIX_TIME 21 | #define GPS_FIX_LOCATION 22 | //#define GPS_FIX_ALTITUDE 23 | //#define GPS_FIX_SPEED 24 | //#define GPS_FIX_HEADING 25 | //#define GPS_FIX_SATELLITES 26 | //#define GPS_FIX_HDOP 27 | //#define GPS_FIX_VDOP 28 | //#define GPS_FIX_PDOP 29 | //#define GPS_FIX_LAT_ERR 30 | //#define GPS_FIX_LON_ERR 31 | //#define GPS_FIX_ALT_ERR 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /configs/Minimal/GPSfix_cfg.h: -------------------------------------------------------------------------------- 1 | #ifndef GPS_FIX_CFG 2 | #define GPS_FIX_CFG 3 | 4 | /** 5 | * Enable/disable the storage for the members of a fix. 6 | * 7 | * Disabling a member prevents it from being parsed from a received message. 8 | * The disabled member cannot be accessed or stored, and its validity flag 9 | * would not be available. It will not be declared, and code that uses that 10 | * member will not compile. 11 | * 12 | * DATE and TIME are somewhat coupled in that they share a single `time_t`, 13 | * but they have separate validity flags. 14 | * 15 | * See also note regarding the DOP members, below. 16 | * 17 | */ 18 | 19 | //#define GPS_FIX_DATE 20 | //#define GPS_FIX_TIME 21 | //#define GPS_FIX_LOCATION 22 | //#define GPS_FIX_ALTITUDE 23 | //#define GPS_FIX_SPEED 24 | //#define GPS_FIX_HEADING 25 | //#define GPS_FIX_SATELLITES 26 | //#define GPS_FIX_HDOP 27 | //#define GPS_FIX_VDOP 28 | //#define GPS_FIX_PDOP 29 | //#define GPS_FIX_LAT_ERR 30 | //#define GPS_FIX_LON_ERR 31 | //#define GPS_FIX_ALT_ERR 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /doc/Troubleshooting.md: -------------------------------------------------------------------------------- 1 | ####Troubleshooting 2 | 3 | ###Configurations 4 | Because there are so many configurable items, it is possible that your configuration prevents acquiring the desired GPS information. 5 | 6 | The compiler **cannot** catch message set dependencies: the enum 7 | `nmea_msg_t` is always available. So even though a `fix` member is enabled, 8 | you may have disabled all messages that would have set its value. 9 | NMEAtest.ino can be used to check some configurations. 10 | 11 | For example, if your application needs altitude, you **must** enable the GGA sentence. No other sentence provides the altitude member. If `NMEA_PARSE_GGA` is not defined, `gps.decode()` will return COMPLETED after a GGA is received, but no parts of the GGA sentence will have been parsed, and altitude will never be valid. NeoGPS will _recognize_ the GGA sentence, but it will not be parsed. 12 | 13 | 14 | The compiler will catch any attempt to use parts of a `fix` that have been 15 | configured out: you will see something like `gps_fix does not have member 16 | xxx`. -------------------------------------------------------------------------------- /Streamers.h: -------------------------------------------------------------------------------- 1 | #ifndef STREAMERS_H 2 | #define STREAMERS_H 3 | 4 | #include 5 | 6 | #include "Time.h" 7 | 8 | extern Stream & trace; // Forward declaration of debug output device 9 | 10 | // Note: 11 | // 12 | // If you use the trace object for debug print statements, you will also 13 | // need a definition somewhere, preferably in your .INO. For example, 14 | // 15 | // Stream & trace = Serial; // trace goes to Serial 16 | // 17 | 18 | extern Stream & operator <<( Stream & outs, const bool b ); 19 | extern Stream & operator <<( Stream & outs, const char c ); 20 | extern Stream & operator <<( Stream & outs, const uint16_t v ); 21 | extern Stream & operator <<( Stream & outs, const uint32_t v ); 22 | extern Stream & operator <<( Stream & outs, const int32_t v ); 23 | extern Stream & operator <<( Stream & outs, const uint8_t v ); 24 | extern Stream & operator <<( Stream & outs, const __FlashStringHelper *s ); 25 | 26 | class gps_fix; 27 | 28 | /** 29 | * Print valid fix data to the given stream with the format 30 | * "status,dateTime,lat,lon,heading,speed,altitude,satellites, 31 | * hdop,vdop,pdop,lat_err,lon_err,alt_err" 32 | * The "header" above contains the actual compile-time configuration. 33 | * A comma-separated field will be empty if the data is NOT valid. 34 | * @param[in] outs output stream. 35 | * @param[in] fix gps_fix instance. 36 | * @return iostream. 37 | */ 38 | extern Stream & operator <<( Stream &outs, const gps_fix &fix ); 39 | 40 | class NMEAGPS; 41 | 42 | extern uint32_t seconds; 43 | 44 | extern void trace_header(); 45 | extern void trace_all( const NMEAGPS &gps, const gps_fix &fix ); 46 | 47 | #endif -------------------------------------------------------------------------------- /doc/Installing.md: -------------------------------------------------------------------------------- 1 | Installing 2 | ========== 3 | For processing NMEA sentences from almost _any_ GPS manufacturer, copy all the .H and .CPP files from the top directory of NeoGPS to your application directory. You do not need the files from any other subdirectories, like **ublox**. Most of the example programs only use these generic NMEA files. 4 | 5 | If you want to handle $PUBX messages from a ublox Neo GPS device, you must copy the above files *and* also copy the ublox/ubxNMEA.* files into your application directory. (This is required if you are trying the example/PUBX/PUBX.ino application.) 6 | 7 | If you want to handle the UBX binary protocol from a ublox Neo GPS device, you must copy the above files *and* also copy the ublox/ubxGPS.* and ublox/ubxmsg.* into your application directory. (This is required if you are trying the example/ublox/ublox.ino application.) 8 | 9 | You can copy the entire example tree into your Arduino directory, and start the IDE by double-clicking on the INO file from those subdirectories. However, the sad state of Arduino library management will still require you to copy the library files as decribed above into *each* example subdirectory. :( 10 | 11 | There are also several example configurations in the [config](/config) subdirectory. Not all configurations will work with all example applications. Specifically, PUBX.ino and ublox.ino both require the following to be enabled in **NMEAGPS_cfg.h**: 12 | 13 | ``` 14 | #define NMEAGPS_DERIVED_TYPES 15 | ``` 16 | 17 | You may also want to change the configured PUBX messages in ubxNMEA.h, or the UBX binary messages in ubxGPS.h. They are currently configured to work with the example applications PUBX.ino and ublox.ino, respectively. 18 | -------------------------------------------------------------------------------- /NeoGPS_cfg.h: -------------------------------------------------------------------------------- 1 | #ifndef NEOGPS_CFG 2 | #define NEOGPS_CFG 3 | 4 | /** 5 | * Enable/disable packed data structures. 6 | * 7 | * Enabling packed data structures will use two less-portable language 8 | * features of GCC to reduce RAM requirements. Although it was expected to slightly increase execution time and code size, the reverse is true on 8-bit AVRs: the code is smaller and faster with packing enabled. 9 | * 10 | * Disabling packed data structures will be very portable to other 11 | * platforms. NeoGPS configurations will use slightly more RAM, and on 12 | * 8-bit AVRs, the speed is slightly slower, and the code is slightly 13 | * larger. There may be no choice but to disable packing on processors 14 | * that do not support packed structures. 15 | * 16 | * There may also be compiler-specific switches that affect packing and the 17 | * code which accesses packed members. YMMV. 18 | **/ 19 | 20 | #define NEOGPS_PACKED_DATA 21 | 22 | //------------------------------------------------------------------------ 23 | // Based on the above define, choose which set of packing macros should 24 | // be used in the rest of the NeoGPS package. Do not change these defines. 25 | 26 | #ifdef NEOGPS_PACKED_DATA 27 | 28 | // This is for specifying the number of bits to be used for a 29 | // member of a struct. Booleans are typically one bit. 30 | #define NEOGPS_BF(b) :b 31 | 32 | // This is for requesting the compiler to pack the struct or class members 33 | // "as closely as possible". This is a compiler-dependent interpretation. 34 | #define NEOGPS_PACKED __attribute__((packed)) 35 | 36 | #else 37 | 38 | // Let the compiler do whatever it wants. 39 | 40 | #define NEOGPS_PACKED 41 | #define NEOGPS_BF(b) 42 | 43 | #endif 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /doc/RAM.md: -------------------------------------------------------------------------------- 1 | RAM requirements 2 | ======= 3 | 4 | ####**NeoGPS** requires **72% to 96% _less_ RAM, saving 140 to 1100 bytes.** 5 | 6 | Because you can select what data members are stored, the RAM savings depends on the [configuration](doc/Configurations.md): 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
ConfigurationNeoGPS
Size
TinyGPS
Size (% smaller)
TinyGPS++
Size (% smaller)
Adafruit_GPS
Size (% smaller)
Minimal10- (95%)- (96%)
DTL25- (86%)- (90%)
Nominal41180 (72%)240 (83%)326 (87%)
Full242- (-)~1400 (83%)
15 | 16 | ####Why does **NeoGPS** use less RAM? 17 | 18 | As data is received from the device, various portions of a `fix` are 19 | modified. In fact, _**no buffering RAM is required**_. Each character 20 | affects the internal state machine and may also contribute to a data member 21 | (e.g., latitude). 22 | 23 | If your application only requires an accurate one pulse-per-second, you 24 | can configure it to parse *no* sentence types and retain *no* data members. 25 | This is the **Minimal** configuration. Although the 26 | `fix().status` can be checked, no valid flags are available. Even 27 | though no sentences are parsed and no data members are stored, the 28 | application will still receive a `decoded` message type once per second: 29 | 30 | ``` 31 | while (uart1.available()) 32 | if (gps.decode( uart1.getchar() )) { 33 | if (gps.nmeaMessage == NMEAGPS::NMEA_RMC) 34 | sentenceReceived(); 35 | } 36 | ``` 37 | 38 | The `ubloxNMEA` derived class doesn't use any extra bytes of RAM. 39 | 40 | The `ubloxGPS` derived class adds 20 bytes to handle the more-complicated protocol, 41 | plus 5 static bytes for converting GPS time and Time Of Week to UTC. 42 | 43 | -------------------------------------------------------------------------------- /examples/NMEA/NMEA.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Serial is for trace output. 3 | Serial1 should be connected to the GPS device. 4 | */ 5 | 6 | #include 7 | 8 | #include "NMEAGPS.h" 9 | #include "Streamers.h" 10 | 11 | // Set this to your debug output device. 12 | Stream & trace = Serial; 13 | 14 | static NMEAGPS gps; 15 | 16 | //-------------------------- 17 | 18 | void setup() 19 | { 20 | // Start the normal trace output 21 | Serial.begin(9600); 22 | trace.print( F("NMEA test: started\n") ); 23 | trace.print( F("fix object size = ") ); 24 | trace.println( sizeof(gps.fix()) ); 25 | trace.print( F("NMEAGPS object size = ") ); 26 | trace.println( sizeof(NMEAGPS) ); 27 | 28 | trace_header(); 29 | 30 | trace.flush(); 31 | 32 | // Start the UART for the GPS device 33 | Serial1.begin(9600); 34 | } 35 | 36 | //-------------------------- 37 | 38 | void loop() 39 | { 40 | static uint32_t last_rx = 0L; 41 | static gps_fix rmc_data; 42 | 43 | while (Serial1.available()) { 44 | last_rx = millis(); 45 | 46 | if (gps.decode( Serial1.read() ) == NMEAGPS::DECODE_COMPLETED) { 47 | 48 | //#ifdef NMEAGPS_SAVE_TALKER_ID 49 | //trace << gps.talker_id[0] << gps.talker_id[1] << ' '; 50 | //#endif 51 | //trace << (uint8_t) gps.nmeaMessage << ' '; 52 | 53 | // Make sure that the only sentence we care about is enabled 54 | #ifndef NMEAGPS_PARSE_RMC 55 | #error NMEAGPS_PARSE_RMC must be defined in NMEAGPS.h! 56 | #endif 57 | 58 | if (gps.nmeaMessage == NMEAGPS::NMEA_RMC) { 59 | rmc_data = gps.fix(); // copied for printing later... 60 | 61 | // Use received GPRMC sentence as a pulse 62 | seconds++; 63 | } 64 | } 65 | } 66 | 67 | // Print things out once per second, after the serial input has died down. 68 | // This prevents input buffer overflow during printing. 69 | 70 | static uint32_t last_trace = 0L; 71 | 72 | if ((last_trace != seconds) && (millis() - last_rx > 5)) { 73 | last_trace = seconds; 74 | 75 | // It's been 5ms since we received anything, log what we have so far... 76 | trace_all( gps, rmc_data ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/NMEAbenchmark/NMEAbenchmark.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Use GPGGA and GPRMC sentences to test the parser's performance. 3 | Serial is for trace output. 4 | */ 5 | 6 | #include 7 | 8 | #include "NMEAGPS.h" 9 | #include "Streamers.h" 10 | 11 | // Set this to your debug output device. 12 | Stream & trace = Serial; 13 | 14 | static NMEAGPS gps; 15 | 16 | //-------------------------- 17 | 18 | static uint32_t time_it( const char *data ) 19 | { 20 | const uint16_t ITERATIONS = 1024; 21 | uint32_t start, end; 22 | 23 | Serial.flush(); 24 | start = micros(); 25 | for (uint16_t i=ITERATIONS; i > 0; i--) { 26 | char *ptr = (char *) data; 27 | while (*ptr) 28 | gps.decode( *ptr++ ); 29 | } 30 | end = micros(); 31 | 32 | return (end-start)/ITERATIONS; 33 | } 34 | 35 | //-------------------------- 36 | 37 | void setup() 38 | { 39 | // Start the normal trace output 40 | Serial.begin(9600); 41 | trace.println( F("NMEAbenchmark: started") ); 42 | trace << F("fix object size = ") << sizeof(gps.fix()) << '\n'; 43 | trace << F("NMEAGPS object size = ") << sizeof(NMEAGPS) << '\n'; 44 | Serial.flush(); 45 | } 46 | 47 | //-------------------------- 48 | 49 | void loop() 50 | { 51 | #ifdef NMEAGPS_PARSE_GGA 52 | const char *gga = 53 | "$GPGGA,092725.00,4717.11399,N,00833.91590,E," 54 | "1,8,1.01,499.6,M,48.0,M,,0*5B\r\n"; 55 | const char *gga_no_lat = 56 | "$GPGGA,092725.00,,,00833.91590,E," 57 | "1,8,1.01,499.6,M,48.0,M,,0*5B\r\n"; 58 | trace << F("GGA time = ") << time_it( gga ) << '\n'; 59 | trace << F("GGA no lat time = ") << time_it( gga_no_lat ) << '\n'; 60 | #endif 61 | 62 | const char *rmc = 63 | "$GPRMC,083559.00,A,4717.11437,N,00833.91522,E," 64 | "0.004,77.52,091202,,,A*57\r\n"; 65 | 66 | trace << F("RMC time = ") << time_it( rmc ) << '\n'; 67 | 68 | #ifdef NMEAGPS_PARSE_GSV 69 | const char *gsv = 70 | "$GPGSV,3,1,10,23,38,230,44,29,71,156,47,07,29,116,41,08,09,081,36*7F\r\n" 71 | "$GPGSV,3,2,10,10,07,189,,05,05,220,,09,34,274,42,18,25,309,44*72\r\n" 72 | "$GPGSV,3,3,10,26,82,187,47,28,43,056,46*77\r\n"; 73 | trace << F("GSV time = ") << time_it( gsv ) << '\n'; 74 | #endif 75 | 76 | for (;;); 77 | } 78 | -------------------------------------------------------------------------------- /GPSTime.h: -------------------------------------------------------------------------------- 1 | #ifndef GPSTIME_H 2 | #define GPSTIME_H 3 | 4 | #include "Time.h" 5 | 6 | class GPSTime 7 | { 8 | GPSTime(); 9 | 10 | static NeoGPS::clock_t s_start_of_week; 11 | 12 | public: 13 | 14 | /** 15 | * GPS time is offset from UTC by a number of leap seconds. To convert a GPS 16 | * time to UTC time, the current number of leap seconds must be known. 17 | * See http://en.wikipedia.org/wiki/Global_Positioning_System#Leap_seconds 18 | */ 19 | static uint8_t leap_seconds; 20 | 21 | /** 22 | * Some receivers report time WRT start of the current week, defined as 23 | * Sunday 00:00:00. To save fairly expensive date/time calculations, 24 | * the UTC start of week is cached 25 | */ 26 | static void start_of_week( NeoGPS::time_t & now ) 27 | { 28 | now.set_day(); 29 | s_start_of_week = 30 | (NeoGPS::clock_t) now - 31 | (NeoGPS::clock_t) ((((now.day-1 ) * 24L + 32 | now.hours ) * 60L + 33 | now.minutes) * 60L + 34 | now.seconds); 35 | } 36 | 37 | static NeoGPS::clock_t start_of_week() 38 | { 39 | return s_start_of_week; 40 | } 41 | 42 | /* 43 | * Convert a GPS time-of-week to UTC. 44 | * Requires /leap_seconds/ and /start_of_week/. 45 | */ 46 | static NeoGPS::clock_t TOW_to_UTC( uint32_t time_of_week ) 47 | { return (NeoGPS::clock_t) 48 | (start_of_week() + time_of_week - leap_seconds); } 49 | 50 | /** 51 | * Set /fix/ timestamp from a GPS time-of-week in milliseconds. 52 | * Requires /leap_seconds/ and /start_of_week/. 53 | **/ 54 | static bool from_TOWms 55 | ( uint32_t time_of_week_ms, NeoGPS::time_t &dt, uint16_t &ms ) 56 | { 57 | //trace << PSTR("from_TOWms(") << time_of_week_ms << PSTR("), sow = ") << start_of_week() << PSTR(", leap = ") << leap_seconds << endl; 58 | bool ok = (start_of_week() != 0) && (leap_seconds != 0); 59 | if (ok) { 60 | NeoGPS::clock_t tow_s = time_of_week_ms/1000UL; 61 | dt = TOW_to_UTC( tow_s ); 62 | ms = (uint16_t)(time_of_week_ms - tow_s*1000UL); 63 | } 64 | return ok; 65 | } 66 | }; 67 | 68 | #endif -------------------------------------------------------------------------------- /examples/NMEAfused/NMEAfused.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Serial is for trace output. 3 | Serial1 should be connected to the GPS device. 4 | */ 5 | 6 | #include 7 | 8 | #include "Streamers.h" 9 | 10 | // Set this to your debug output device. 11 | Stream & trace = Serial; 12 | 13 | #include "NMEAGPS.h" 14 | 15 | #if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \ 16 | !defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \ 17 | !defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \ 18 | !defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST ) 19 | 20 | #if defined(GPS_FIX_DATE)| defined(GPS_FIX_TIME) 21 | #error No NMEA sentences enabled: no fix data available for fusing. 22 | #else 23 | #warning No NMEA sentences enabled: no fix data available for fusing,\n\ 24 | only pulse-per-second is available. 25 | #endif 26 | 27 | #endif 28 | 29 | static NMEAGPS gps; 30 | 31 | static gps_fix fused; 32 | 33 | //-------------------------- 34 | 35 | void setup() 36 | { 37 | // Start the normal trace output 38 | Serial.begin(9600); 39 | trace.print( F("NMEAfused: started\n") ); 40 | trace.print( F("fix object size = ") ); 41 | trace.println( sizeof(gps.fix()) ); 42 | trace.print( F("NMEAGPS object size = ") ); 43 | trace.println( sizeof(NMEAGPS) ); 44 | 45 | trace_header(); 46 | 47 | trace.flush(); 48 | 49 | // Start the UART for the GPS device 50 | Serial1.begin(9600); 51 | } 52 | 53 | //-------------------------- 54 | 55 | void loop() 56 | { 57 | static uint32_t last_rx = 0L; 58 | 59 | while (Serial1.available()) { 60 | last_rx = millis(); 61 | 62 | if (gps.decode( Serial1.read() ) == NMEAGPS::DECODE_COMPLETED) { 63 | 64 | // All enabled sentence types will be merged into one fix 65 | fused |= gps.fix(); 66 | 67 | if (gps.nmeaMessage == NMEAGPS::NMEA_RMC) 68 | // Use received GPRMC sentence as a pulse 69 | seconds++; 70 | } 71 | } 72 | 73 | // Print things out once per second, after the serial input has died down. 74 | // This prevents input buffer overflow during printing. 75 | 76 | static uint32_t last_trace = 0L; 77 | 78 | if ((last_trace != seconds) && (millis() - last_rx > 5)) { 79 | last_trace = seconds; 80 | 81 | // It's been 5ms since we received anything, log what we have so far... 82 | trace_all( gps, fused ); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /ublox/ubxNMEA.cpp: -------------------------------------------------------------------------------- 1 | #include "ubxNMEA.h" 2 | 3 | //--------------------------------------------- 4 | 5 | bool ubloxNMEA::parseField(char chr) 6 | { 7 | bool ok = true; 8 | 9 | switch (nmeaMessage) { 10 | 11 | case PUBX_00: 12 | switch (fieldIndex) { 13 | case 1: 14 | //trace << chr; 15 | // The first field is actually a message subtype 16 | if (chrCount == 0) 17 | ok = (chr == '0'); 18 | else if (chrCount == 1) 19 | nmeaMessage = (nmea_msg_t) (nmeaMessage + chr - '0'); 20 | break; 21 | #ifdef NMEAGPS_PARSE_PUBX_00 22 | case 2: return parseTime( chr ); 23 | PARSE_LOC(3); 24 | case 7: return parseAlt( chr ); 25 | case 8: return parseFix( chr ); break; 26 | case 11: return parseSpeed( chr ); // kph! 27 | case 12: return parseHeading( chr ); 28 | case 15: return parseHDOP( chr ); 29 | case 18: return parseSatellites( chr ); 30 | #endif 31 | } 32 | break; 33 | 34 | case PUBX_04: 35 | #ifdef NMEAGPS_PARSE_PUBX_04 36 | switch (fieldIndex) { 37 | case 2: return parseTime( chr ); 38 | case 3: return parseDDMMYY( chr ); 39 | } 40 | #endif 41 | break; 42 | 43 | default: 44 | // Delegate 45 | return NMEAGPS::parseField(chr); 46 | } 47 | 48 | return ok; 49 | } 50 | 51 | //--------------------------------------------- 52 | 53 | bool ubloxNMEA::parseFix( char chr ) 54 | { 55 | if (chrCount == 0) { 56 | NMEAGPS_INVALIDATE( status ); 57 | if (chr == 'N') 58 | m_fix.status = gps_fix::STATUS_NONE; 59 | else if (chr == 'T') 60 | m_fix.status = gps_fix::STATUS_TIME_ONLY; 61 | else if (chr == 'R') 62 | m_fix.status = gps_fix::STATUS_EST; 63 | else if (chr == 'G') 64 | m_fix.status = gps_fix::STATUS_STD; 65 | else if (chr == 'D') 66 | m_fix.status = gps_fix::STATUS_DGPS; 67 | 68 | } else if (chrCount == 1) { 69 | 70 | if (((chr == 'T') && (m_fix.status == gps_fix::STATUS_TIME_ONLY)) || 71 | ((chr == 'K') && (m_fix.status == gps_fix::STATUS_EST)) || 72 | (((chr == '2') || (chr == '3')) && 73 | ((m_fix.status == gps_fix::STATUS_STD) || 74 | (m_fix.status == gps_fix::STATUS_DGPS))) || 75 | ((chr == 'F') && (m_fix.status == gps_fix::STATUS_NONE))) 76 | // status based on first char was ok guess 77 | m_fix.valid.status = true; 78 | 79 | else if ((chr == 'R') && (m_fix.status == gps_fix::STATUS_DGPS)) { 80 | m_fix.status = gps_fix::STATUS_EST; // oops, was DR instead 81 | m_fix.valid.status = true; 82 | } 83 | } 84 | 85 | return true; 86 | } 87 | -------------------------------------------------------------------------------- /doc/Examples.md: -------------------------------------------------------------------------------- 1 | Examples 2 | ====== 3 | Several programs are provided to demonstrate how to use the classes in these different styles: 4 | 5 | * [NMEA](examples/NMEA/NMEA.ino) - sync, polled, single, standard NMEA only 6 | * [NMEAblink](examples/NMEAblink/NMEAblink.ino) - sync, polled, single, standard NMEA only, minimal example, only blinks LED 7 | * [NMEAfused](examples/NMEAfused/NMEAfused.ino) - sync, polled, fused, standard NMEA only 8 | * [NMEAcoherent](examples/NMEAcoherent/NMEAcoherent.ino) - sync, polled, coherent, standard NMEA only 9 | * [PUBX](examples/PUBX/PUBX.ino) - sync, polled, coherent, standard NMEA + ublox proprietary NMEA 10 | * [ublox](examples/ublox/ublox.ino) - sync, polled, coherent, ublox protocol 11 | 12 | Preprocessor symbol `USE_FLOAT` can be used in [Streamers.cpp](Streamers.cpp) to select integer or floating-point output. 13 | 14 | A self-test test program is also provided: 15 | 16 | * [NMEAtest.ino](examples/NMEAtest/NMEAtest.ino) - sync, polled, not fused, standard NMEA only 17 | 18 | No GPS device is required; the bytes are streamed from PROGMEM character arrays. Various strings are passed to `decode` and the expected pass or fail results are displayed. If **NeoGPS** is correctly configured, you should see this on your SerialMonitor: 19 | 20 | ``` 21 | NMEA test: started 22 | fix object size = 44 23 | NMEAGPS object size = 82 24 | Test string length = 75 25 | PASSED 6 tests. 26 | ------ Samples ------ 27 | Results format: 28 | Status,Date/Time,Lat,Lon,Hdg,Spd,Alt,HDOP,VDOP,PDOP,Lat err,Lon err,Alt err,Sats,[sat],Rx ok,Rx err, 29 | 30 | Input: $GPGGA,092725.00,4717.11399,N,00833.91590,E,1,8,1.01,499.6,M,48.0,M,,0*5B 31 | Results: 3,2000-00-00 09:27:25.00,472852332,85652650,,,49960,1010,,,,,,8,[],1,0, 32 | 33 | Input: $GPRMC,092725.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A*5E 34 | Results: 3,2002-12-09 09:27:25.00,472852395,85652537,7752,4,,,,,,,,,[],2,0, 35 | 36 | Input: $GPGGA,064951.000,2307.1256,N,12016.4438,E,1,8,0.95,39.9,M,17.8,M,,*63 37 | Results: 3,2002-12-09 06:49:51.00,231187600,1202740633,,,3990,950,,,,,,8,[],3,0, 38 | 39 | Input: $GPRMC,064951.000,A,2307.1256,N,12016.4438,E,0.03,165.48,260406,3.05,W,A*2C 40 | Results: 3,2006-04-26 06:49:51.00,231187600,1202740633,16548,30,,,,,,,,,[],4,0, 41 | 42 | Input: $GPVTG,165.48,T,,M,0.03,N,0.06,K,A*36 43 | Results: 3,,,,16548,30,,,,,,,,,[],5,0, 44 | 45 | Input: $GPGSA,A,3,29,21,26,15,18,09,06,10,,,,,2.32,0.95,2.11*00 46 | Results: 3,,,,,,,950,2110,2320,,,,,[],6,0, 47 | 48 | Input: $GPGSV,3,1,09,29,36,029,42,21,46,314,43,26,44,020,43,15,21,321,39*7D 49 | Results: ,,,,,,,,,,,,,9,[],7,0, 50 | 51 | Input: $GPGSV,3,2,09,18,26,314,40,09,57,170,44,06,20,229,37,10,26,084,37*77 52 | Results: ,,,,,,,,,,,,,9,[],8,0, 53 | 54 | Input: $GPGSV,3,3,09,07,,,26*73 55 | Results: ,,,,,,,,,,,,,9,[29,21,26,15,18,9,6,10,7,],9,0, 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /doc/Performance.md: -------------------------------------------------------------------------------- 1 | Performance 2 | =========== 3 | 4 | ####**NeoGPS** is **35% to 70% faster**. 5 | 6 | For comparison, the following sentences were parsed by various [Configurations](/doc/Configurations.md) of **NeoGPS**, **TinyGPS** and **TinyGPSPlus** on a 16MHz Arduino Mega2560. 7 | 8 | ``` 9 | $GPGGA,092725.00,4717.11399,N,00833.91590,E,1,8,1.01,499.6,M,48.0,M,,0*5B 10 | $GPRMC,083559.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A*57 11 | $GPGSV,3,1,10,23,38,230,44,29,71,156,47,07,29,116,41,08,09,081,36*7F 12 | $GPGSV,3,2,10,10,07,189,,05,05,220,,09,34,274,42,18,25,309,44*72 13 | $GPGSV,3,3,10,26,82,187,47,28,43,056,46*77 14 | ``` 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
ConfigurationSentenceNeoGPSTinyGPS
Time (% faster)
TinyGPS++
Time (% faster)
Adafrut_GPS
Time (%faster)
MinimalGGA
RMC
436us
485us
- (70%)
- (66%)
- (72%)
- (69%)
DTLGGA
RMC
839us
859us
- (42%)
- (40%)
- (45%)
- (55%)
NominalGGA
RMC
885us
907us
1448us (39%)
1435us (37%)
1473us (40%)
1442us (38%)
1358us (35%)
1535us (41%)
FullGGA
RMC
GSV
1094us
1075us
2048us
- (25%)
- (25%)
- (-)
1523us (42%)
1560us (42%)
6651us (70%)
23 | 24 | ####Why is **NeoGPS** faster? 25 | 26 | Most libraries use extra buffers to accumulate parts of the sentence so they 27 | can be parsed all at once. For example, an extra field buffer may hold on 28 | to all the characters between commas. That buffer is then parsed into a 29 | single data item, like `heading`. Some libraries even hold on to the 30 | *entire* sentence before attempting to parse it. In addition to increasing 31 | the RAM requirements, this requires **extra CPU time** to copy the bytes and 32 | index through them... again. 33 | 34 | **NeoGPS** parses each character immediately into the data item. When the 35 | delimiting comma is received, the data item has been fully computed *in 36 | place* and is marked as valid. 37 | 38 | Most libraries parse all fields of their selected sentences. Although most 39 | people use GPS for obtaining lat/long, some need only time, or even just one 40 | pulse-per-second. 41 | 42 | **NeoGPS** configures each item separately. Disabled items are 43 | conditionally compiled, which means they will not use any RAM, program space 44 | or CPU time. The characters from those fields are simply skipped; they are 45 | never copied into a buffer or processed. 46 | 47 | While it is significantly faster and smaller than all NMEA parsers, these same improvements also make 48 | NeoGPS faster and smaller than _binary_ parsers. 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NeoGPS 2 | ====== 3 | 4 | This fully-configurable Arduino library uses _**minimal**_ RAM, PROGMEM and CPU time, 5 | requiring as few as _**10 bytes of RAM**_, **866 bytes of PROGMEM**, and **less than 1mS of CPU time** per sentence. 6 | 7 | It supports the following protocols and messages: 8 | 9 | ####NMEA 0183 10 | * GPGGA - GPS system fix data 11 | * GPGLL - Geographic Latitude and Longitude 12 | * GPGSA - GPS/GNSS DOP and active satellites 13 | * GPGST - GNSS Pseudo Range Error Statistics 14 | * GPGSV - GPS/GNSS Satellites in View 15 | * GPRMC - Recommended Minimum specific GPS/Transit data 16 | * GPVTG - Course over ground and Ground speed 17 | * GPZDA - UTC Time and Date 18 | 19 | GNRMC, GLRMC, etc. will also be correctly parsed. See discussion of Talker 20 | IDs in [Configurations](doc/Configurations.md). 21 | 22 | Most applications can be fully implemented with the standard NMEA messages above. They are supported by almost all GPS manufacturers. 23 | 24 | However, to illustrate how unique capabilities of a particular device can be utilized, derived classes are also provided for ublox devices. 25 | 26 | (This is the plain Arduino version of the [CosaGPS](https://github.com/SlashDevin/CosaGPS) library for [Cosa](https://github.com/mikaelpatel/Cosa).) 27 | 28 | Goals 29 | ====== 30 | In an attempt to be reusable in a variety of different programming styles, this library supports: 31 | * resource-constrained environments (e.g., ATTINY targets) 32 | * sync or async operation (main `loop()` vs interrupt processing) 33 | * event or polling (deferred handling vs. continuous fix() calls in `loop()`) 34 | * single, fused or coherent fixes (multiple reports into one) 35 | * optional buffering of fixes 36 | * optional floating point 37 | * configurable message sets, including hooks for implementing proprietary NMEA messages 38 | * configurable message fields 39 | * multiple protocols from same device 40 | * any kind of input stream (Serial, SoftwareSerial, PROGMEM arrays, etc.) 41 | 42 | Inconceivable! 43 | ============= 44 | 45 | Don't believe it? Check out these detailed sections: 46 | 47 | Section | Description 48 | -------- | ------------ 49 | [Installing] (doc/Installing.md) | Copying files 50 | [Data Model](doc/Data Model.md) | Aggregating pieces into a *fix* 51 | [Configurations](doc/Configurations.md) | Tailoring NeoGPS to your needs 52 | [Performance](doc/Performance.md) | 37% to 72% faster! Really! 53 | [RAM requirements](doc/RAM.md) | Doing it without buffers! 54 | [Program Space requirements](doc/Program.md) | Making it fit 55 | [Extending NeoGPS](doc/Extending.md) | Using specific devices 56 | [Tradeoffs](doc/Tradeoffs.md) | Comparing to other libraries 57 | [Examples](doc/Examples.md) | Programming styles 58 | [ublox](doc/ublox.md) | ublox-specific code 59 | [Troubleshooting](doc/Troubleshooting.md) | Troubleshooting 60 | [Acknowledgements](doc/Acknowledgements.md) | Thanks! 61 | -------------------------------------------------------------------------------- /doc/Extending.md: -------------------------------------------------------------------------------- 1 | Extending NeoGPS 2 | ========= 3 | Using features that are unique to your device fall into three categories: 4 | ####1. Configuring the device with special commands 5 | Many devices allow you to configure which standard messages are emitted, or the rate at which they are emitted. It may be as simple as sending a proprietary command to the device. Simply use the NMEAGPS `send` or `send_P` method. 6 | 7 | For example, to set the baudrate of the ublox NEO-6M gps device, send it a 8 | `UBX,41` message: 9 | ``` 10 | gps.send_P( F("PUBX,41,1,0007,0003,19200,0") ); 11 | ``` 12 | ####2. Parsing additional message types 13 | Some devices provide additional messages with extra information, or more efficient groupings. This will require deriving a class from `NMEAGPS`. The derived class needs to 14 | * declare a PROGMEM table of the new message types, 15 | * point that table back to the NMEAGPS table 16 | * override the `parseField` method to extract information from each new message type 17 | 18 | Please see ubxNMEA.h and .cpp for an example of adding two ublox-proprietary messages. 19 | 20 | ####3. Handling new protocols 21 | Some devices provide additional protocols. They are frequently binary, which requires 22 | fewer bytes than NMEA 0183. Because they can both be transmitted on the same port, it is 23 | very likely that they can be distinguished at the message framing level. 24 | 25 | For example, NMEA messages always start with a '$' and end with CR/LF. ublox messages start 26 | with 0xB5 and 0x62 bytes, a message class and id, and a 2-byte message length. There is no 27 | terminating character; the message completed when `length` bytes have been received. 28 | 29 | This will require deriving a class from `NMEAGPS`. The derived class needs 30 | to 31 | * define new `rxState` values for the protocol state machine. These should 32 | be unique from the NMEA state values, but they should share the IDLE state 33 | value. 34 | * override the `decode` method to watch for its messages. As bytes are 35 | received, it may transition out of the IDLE state and into its own set of 36 | state values. If the character is not valid for this protocol, it should 37 | delegate it to the NMEAGPS base clase, which may begin processing an NMEAGPS 38 | message. If the rxState is not one of the derived states (i.e., it is in 39 | one of the NMEAGPS states), the character should be delegated to 40 | NMEAGPS::decode. 41 | * implement something like the `parseField` method if parse-in-place 42 | behavior is desirable. This is not necessarily `virtual`, as it will only 43 | be called from the derived `decode`. 44 | * You are free to add methods and data members as required for handling the 45 | protocol. Only `decode` must be overridden. 46 | 47 | Please see ubxGPS.h and .cpp for an example of implementing the 48 | ublox-proprietary protocol, UBX. The derived `ubloxGPS` class provides both 49 | parse-in-place *and* buffered messages. See the `send` and `poll` methods. 50 | 51 | -------------------------------------------------------------------------------- /doc/Tradeoffs.md: -------------------------------------------------------------------------------- 1 | Tradeoffs 2 | ========= 3 | 4 | There's a price for everything, hehe... 5 | 6 | ####Configurability means that the code is littered with #ifdef sections. 7 | 8 | I've tried to increase white space and organization to make it more readable, but let's be honest... 9 | conditional compilation is ugly. 10 | 11 | ####Accumulating parts means knowing which parts are valid. 12 | 13 | Before accessing a part, you must check its `valid` flag. Fortunately, this adds only one bit per member. See GPSfix.cpp for an example of accessing every data member. 14 | 15 | ####Parsing without buffers, or *in place*, means that you must be more careful about when you access data items. 16 | 17 | In general, you should wait to access the fix until after the entire sentence has been parsed. Most of the examples simply `decode` until a sentence is COMPLETED, then do all their work with `fix`. See `loop()` in [NMEA.ino](examples/NMEA.ino). 18 | Member function `is_safe()` can also be used to determine when it is safe. 19 | 20 | If you need to access the fix at any time, you will have to double-buffer the fix: simply copy the `fix` when it is safe to do so. (See NMEAGPS.h comments regarding a 21 | `safe_fix`.) Also, received data errors can cause invalid field values to be set *before* the CRC is fully computed. The CRC will 22 | catch most of those, and the fix members will then be marked as invalid. 23 | 24 | ####Accumulating parts into *one* fix means less RAM but more complicated code 25 | 26 | By enabling `NMEAGPS_ACCUMULATE_FIX`, the fix will accumulate data from all received sentences. Each 27 | fix member will contain the last value received from any sentence that 28 | contains that information. While it avoids the need for a second copy of the merged fix, it has several restrictions: 29 | * Fix members can only be accessed while it `is_safe()`. There is no double-buffered fix. 30 | * Fix members may contain information from different time intervals (i.e., they are not 31 | coherent). It is possible to acheive coherency if the `fix` is re-initialzed at the correct time. 32 | * All fix members may be invalidated if a received sentence is rejected for any reason (e.g., CRC 33 | error). No members will be valid until new sentences are received, parsed, accepted and *safe*. Your application 34 | must accommodate possible gaps in fix availability. 35 | 36 | You are not restricted from having other instances of fix; you can copy or merge the accumulating fix into another copy if you want. This is just a way to minimize RAM requirements and still have a fused fix. 37 | 38 | ####Correlating timestamps for coherency means extra date/time comparisons for each sentence before it is fused. 39 | 40 | This is optional: compare NMEAcoherent.ino and NMEAfused.ino to see code that determines when a new time interval has been entered. 41 | 42 | ####Full C++ OO implementation is more advanced than most Arduino libraries. 43 | 44 | You've been warned! ;) 45 | 46 | ####"fast, good, cheap... pick two." 47 | 48 | Although most of the RAM reduction is due to eliminating buffers, some of it is from trading RAM 49 | for additional code (see **Nominal** Program Space above). And, as I mentioned, the readabilty (i.e., goodness) suffers from its configurability. 50 | 51 | -------------------------------------------------------------------------------- /ublox/ubxNMEA.h: -------------------------------------------------------------------------------- 1 | #ifndef _UBXNMEA_H_ 2 | #define _UBXNMEA_H_ 3 | 4 | /** 5 | * @file UBXNMEA.h 6 | * @version 2.1 7 | * 8 | * @section License 9 | * Copyright (C) 2014, SlashDevin 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | */ 21 | 22 | #include "NMEAGPS.h" 23 | #include "Streamers.h" 24 | 25 | /** 26 | * Enable/disable the parsing of specific NMEA sentences. 27 | * 28 | * Configuring out a sentence prevents its fields from being parsed. 29 | * However, the sentence type will still be recognized by /decode/ and 30 | * stored in member /nmeaMessage/. No valid flags would be available. 31 | * 32 | */ 33 | #define NMEAGPS_PARSE_PUBX_00 34 | #define NMEAGPS_PARSE_PUBX_04 35 | 36 | // Ublox proprietary messages do not have a message type. These 37 | // messages start with "$PUBX," which ends with the manufacturer ID. The 38 | // message type is actually specified by the first numeric field. In order 39 | // to parse these messages, /parse_mfr_ID/ must be overridden to set the 40 | // /nmeaMessage/ to PUBX_00 during /parseCommand/. When the first numeric 41 | // field is completed by /parseField/, it may change /nmeamessage/ to one 42 | // of the other PUBX message types. 43 | 44 | #if (defined(NMEAGPS_PARSE_PUBX_00) | defined(NMEAGPS_PARSE_PUBX_00)) \ 45 | & \ 46 | !defined(NMEAGPS_PARSE_MFR_ID) 47 | #error NMEAGPS_PARSE_MFR_ID must be defined in NMEAGPS.h in order to parse PUBX messages! 48 | #endif 49 | 50 | #ifndef NMEAGPS_DERIVED_TYPES 51 | #error You must "#define NMEAGPS_DERIVED_TYPES" in NMEAGPS.h! 52 | #endif 53 | 54 | /** 55 | * NMEA 0183 Parser for ublox Neo-6 GPS Modules. 56 | * 57 | * @section Limitations 58 | * Very limited support for ublox proprietary NMEA messages. 59 | * Only NMEA messages of types PUBX,00 and PUBX,04 are parsed. 60 | */ 61 | 62 | class ubloxNMEA : public NMEAGPS 63 | { 64 | ubloxNMEA( const ubloxNMEA & ); 65 | 66 | public: 67 | 68 | ubloxNMEA() {}; 69 | 70 | /** ublox proprietary NMEA message types. */ 71 | enum pubx_msg_t { 72 | PUBX_00 = NMEA_LAST_MSG+1, 73 | PUBX_04 = PUBX_00+4 74 | }; 75 | static const nmea_msg_t PUBX_FIRST_MSG = (nmea_msg_t) PUBX_00; 76 | static const nmea_msg_t PUBX_LAST_MSG = (nmea_msg_t) PUBX_04; 77 | 78 | protected: 79 | bool parseMfrID( char chr ) 80 | { bool ok; 81 | switch (chrCount) { 82 | case 1: ok = (chr == 'U'); break; 83 | case 2: ok = (chr == 'B'); break; 84 | default: if (chr == 'X') { 85 | ok = true; 86 | nmeaMessage = (nmea_msg_t) PUBX_00; 87 | } else 88 | ok = false; 89 | break; 90 | } 91 | return ok; 92 | }; 93 | 94 | bool parseField( char chr ); 95 | bool parseFix( char chr ); 96 | } NEOGPS_PACKED; 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /examples/NMEAcoherent/NMEAcoherent.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Serial is for trace output. 3 | Serial1 should be connected to the GPS device. 4 | */ 5 | 6 | #include 7 | 8 | #include "Streamers.h" 9 | 10 | // Set this to your debug output device. 11 | Stream & trace = Serial; 12 | 13 | #include "NMEAGPS.h" 14 | 15 | #if !defined( NMEAGPS_PARSE_GGA ) & !defined( NMEAGPS_PARSE_GLL ) & \ 16 | !defined( NMEAGPS_PARSE_GSA ) & !defined( NMEAGPS_PARSE_GSV ) & \ 17 | !defined( NMEAGPS_PARSE_RMC ) & !defined( NMEAGPS_PARSE_VTG ) & \ 18 | !defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST ) 19 | 20 | #if defined(GPS_FIX_DATE)| defined(GPS_FIX_TIME) 21 | #error No NMEA sentences enabled: no fix data available for fusing. 22 | #else 23 | #warning No NMEA sentences enabled: no fix data available for fusing,\n\ 24 | only pulse-per-second is available. 25 | #endif 26 | 27 | #endif 28 | 29 | #if defined(GPS_FIX_DATE) & !defined(GPS_FIX_TIME) 30 | // uncomment this to display just one pulse-per-day. 31 | //#define PULSE_PER_DAY 32 | #endif 33 | 34 | static NMEAGPS gps; 35 | 36 | static gps_fix fused; 37 | 38 | //-------------------------- 39 | 40 | static void sentenceReceived() 41 | { 42 | // See if we stepped into a different time interval, 43 | // or if it has finally become valid after a cold start. 44 | 45 | bool newInterval; 46 | #if defined(GPS_FIX_TIME) 47 | newInterval = (gps.fix().valid.time && 48 | (!fused.valid.time || 49 | (fused.dateTime.seconds != gps.fix().dateTime.seconds) || 50 | (fused.dateTime.minutes != gps.fix().dateTime.minutes) || 51 | (fused.dateTime.hours != gps.fix().dateTime.hours))); 52 | #elif defined(PULSE_PER_DAY) 53 | newInterval = (gps.fix().valid.date && 54 | (!fused.valid.date || 55 | (fused.dateTime.date != gps.fix().dateTime.date) || 56 | (fused.dateTime.month != gps.fix().dateTime.month) || 57 | (fused.dateTime.year != gps.fix().dateTime.year))); 58 | #else 59 | // No date/time configured, so let's assume it's a new interval 60 | // if the seconds have changed. 61 | static uint32_t last_sentence = 0L; 62 | 63 | newInterval = (seconds != last_sentence); 64 | last_sentence = seconds; 65 | #endif 66 | 67 | if (newInterval) { 68 | 69 | // Since we're into the next time interval, we throw away 70 | // all of the previous fix and start with what we 71 | // just received. 72 | fused = gps.fix(); 73 | 74 | gps.poll( &Serial1, NMEAGPS::NMEA_GST ); 75 | 76 | } else { 77 | // Accumulate all the reports in this time interval into a /coherent/ fix 78 | fused |= gps.fix(); 79 | } 80 | 81 | } // sentenceReceived 82 | 83 | //-------------------------- 84 | 85 | void setup() 86 | { 87 | // Start the normal trace output 88 | Serial.begin(9600); 89 | trace.print( F("NMEAcoherent: started\n") ); 90 | trace.print( F("fix object size = ") ); 91 | trace.println( sizeof(gps.fix()) ); 92 | trace.print( F("NMEAGPS object size = ") ); 93 | trace.println( sizeof(NMEAGPS) ); 94 | 95 | trace_header(); 96 | 97 | trace.flush(); 98 | 99 | // Start the UART for the GPS device 100 | Serial1.begin(9600); 101 | } 102 | 103 | //-------------------------- 104 | 105 | void loop() 106 | { 107 | static uint32_t last_rx = 0L; 108 | 109 | while (Serial1.available()) { 110 | last_rx = millis(); 111 | 112 | if (gps.decode( Serial1.read() ) == NMEAGPS::DECODE_COMPLETED) { 113 | 114 | // All enabled sentence types will be merged into one fix 115 | sentenceReceived(); 116 | 117 | if (gps.nmeaMessage == NMEAGPS::NMEA_RMC) 118 | // Use received GPRMC sentence as a pulse 119 | seconds++; 120 | } 121 | } 122 | 123 | // Print things out once per second, after the serial input has died down. 124 | // This prevents input buffer overflow during printing. 125 | 126 | static uint32_t last_trace = 0L; 127 | 128 | if ((last_trace != seconds) && (millis() - last_rx > 5)) { 129 | last_trace = seconds; 130 | 131 | // It's been 5ms since we received anything, log what we have so far... 132 | trace_all( gps, fused ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /examples/PUBX/PUBX.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Serial is for trace output. 3 | Serial1 should be connected to the GPS device. 4 | */ 5 | 6 | #include 7 | 8 | #include "Streamers.h" 9 | 10 | // Set this to your debug output device. 11 | Stream & trace = Serial; 12 | 13 | #include "ubxNMEA.h" 14 | 15 | #if !defined( NMEAGPS_PARSE_GGA) & !defined( NMEAGPS_PARSE_GLL) & \ 16 | !defined( NMEAGPS_PARSE_GSA) & !defined( NMEAGPS_PARSE_GSV) & \ 17 | !defined( NMEAGPS_PARSE_RMC) & !defined( NMEAGPS_PARSE_VTG) & \ 18 | !defined( NMEAGPS_PARSE_ZDA ) & !defined( NMEAGPS_PARSE_GST ) & \ 19 | !defined( NMEAGPS_PARSE_PUBX_00 ) & !defined( NMEAGPS_PARSE_PUBX_04 ) 20 | 21 | #if defined(GPS_FIX_DATE)| defined(GPS_FIX_TIME) 22 | #error No NMEA sentences enabled: no fix data available for fusing. 23 | #else 24 | #warning No NMEA sentences enabled: no fix data available for fusing,\n\ 25 | only pulse-per-second is available. 26 | #endif 27 | 28 | #elif !defined( NMEAGPS_PARSE_PUBX_00 ) & !defined( NMEAGPS_PARSE_PUBX_04 ) 29 | #error PUBX_00 or PUBX_04 messages must be defined in PUBX.ino! 30 | #endif 31 | 32 | #if defined(GPS_FIX_DATE) & !defined(GPS_FIX_TIME) 33 | // uncomment this to display just one pulse-per-day. 34 | //#define PULSE_PER_DAY 35 | #endif 36 | 37 | static ubloxNMEA gps; 38 | 39 | static gps_fix fused; 40 | 41 | //-------------------------- 42 | 43 | static void poll() 44 | { 45 | gps.send_P( &Serial1, PSTR("PUBX,00") ); 46 | gps.send_P( &Serial1, PSTR("PUBX,04") ); 47 | } 48 | 49 | //-------------------------- 50 | 51 | static void sentenceReceived() 52 | { 53 | // See if we stepped into a different time interval, 54 | // or if it has finally become valid after a cold start. 55 | 56 | bool newInterval; 57 | #if defined(GPS_FIX_TIME) 58 | newInterval = (gps.fix().valid.time && 59 | (!fused.valid.time || 60 | (fused.dateTime.seconds != gps.fix().dateTime.seconds) || 61 | (fused.dateTime.minutes != gps.fix().dateTime.minutes) || 62 | (fused.dateTime.hours != gps.fix().dateTime.hours))); 63 | #elif defined(PULSE_PER_DAY) 64 | newInterval = (gps.fix().valid.date && 65 | (!fused.valid.date || 66 | (fused.dateTime.date != gps.fix().dateTime.date) || 67 | (fused.dateTime.month != gps.fix().dateTime.month) || 68 | (fused.dateTime.year != gps.fix().dateTime.year))); 69 | #else 70 | // No date/time configured, so let's assume it's a new interval 71 | // if the seconds have changed. 72 | static uint32_t last_sentence = 0L; 73 | 74 | newInterval = (seconds != last_sentence); 75 | last_sentence = seconds; 76 | #endif 77 | 78 | if (newInterval) { 79 | // Since we're into the next time interval, we throw away 80 | // all of the previous fix and start with what we 81 | // just received. 82 | fused = gps.fix(); 83 | 84 | } else { 85 | // Accumulate all the reports in this time interval 86 | fused |= gps.fix(); 87 | } 88 | 89 | } // sentenceReceived 90 | 91 | 92 | //-------------------------- 93 | 94 | void setup() 95 | { 96 | // Start the normal trace output 97 | Serial.begin(9600); 98 | trace.print( F("PUBX: started\n") ); 99 | trace.print( F("fix object size = ") ); 100 | trace.println( sizeof(gps.fix()) ); 101 | trace.print( F("gps object size = ") ); 102 | trace.println( sizeof(gps) ); 103 | 104 | trace_header(); 105 | 106 | trace.flush(); 107 | 108 | // Start the UART for the GPS device 109 | Serial1.begin(9600); 110 | poll(); 111 | } 112 | 113 | //-------------------------- 114 | 115 | void loop() 116 | { 117 | static uint32_t last_rx = 0L; 118 | 119 | while (Serial1.available()) { 120 | last_rx = millis(); 121 | 122 | if (gps.decode( Serial1.read() ) == NMEAGPS::DECODE_COMPLETED) { 123 | 124 | // All enabled sentence types will be merged into one fix 125 | sentenceReceived(); 126 | 127 | if (gps.nmeaMessage == (NMEAGPS::nmea_msg_t) ubloxNMEA::PUBX_00) { 128 | // Use received PUBX,00 sentence as a pulse 129 | seconds++; 130 | poll(); 131 | } 132 | } 133 | } 134 | 135 | // Print things out once per second, after the serial input has died down. 136 | // This prevents input buffer overflow during printing. 137 | 138 | static uint32_t last_trace = 0L; 139 | 140 | if ((last_trace != seconds) && (millis() - last_rx > 5)) { 141 | last_trace = seconds; 142 | 143 | // It's been 5ms since we received anything, log what we have so far... 144 | trace_all( gps, fused ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Time.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Time.cpp 3 | * @version 1.0 4 | * 5 | * @section License 6 | * Copyright (C) 2013-2014, SlashDevin 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Lesser General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2.1 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Lesser General Public License for more details. 17 | * 18 | * This file is part of the NeoGPS project. Based on the execellent 19 | * framework, Cosa, by Mikael Patel. 20 | */ 21 | 22 | #include "Time.h" 23 | #include "Streamers.h" 24 | 25 | Stream& operator<<(Stream& outs, const NeoGPS::time_t& t) 26 | { 27 | outs << t.full_year( t.year ) << '-'; 28 | if (t.month < 10) outs << '0'; 29 | outs << t.month << '-'; 30 | if (t.date < 10) outs << '0'; 31 | outs << t.date << ' '; 32 | if (t.hours < 10) outs << '0'; 33 | outs << t.hours << ':'; 34 | if (t.minutes < 10) outs << '0'; 35 | outs << t.minutes << ':'; 36 | if (t.seconds < 10) outs << '0'; 37 | outs << t.seconds; 38 | return (outs); 39 | } 40 | 41 | using NeoGPS::time_t; 42 | 43 | bool 44 | time_t::parse(str_P s) 45 | { 46 | static size_t BUF_MAX = 32; 47 | char buf[BUF_MAX]; 48 | strcpy_P(buf, s); 49 | char* sp = &buf[0]; 50 | uint16_t value = strtoul(sp, &sp, 10); 51 | 52 | if (*sp != '-') return false; 53 | year = value % 100; 54 | if (full_year() != value) return false; 55 | 56 | value = strtoul(sp + 1, &sp, 10); 57 | if (*sp != '-') return false; 58 | month = value; 59 | 60 | value = strtoul(sp + 1, &sp, 10); 61 | if (*sp != ' ') return false; 62 | date = value; 63 | 64 | value = strtoul(sp + 1, &sp, 10); 65 | if (*sp != ':') return false; 66 | hours = value; 67 | 68 | value = strtoul(sp + 1, &sp, 10); 69 | if (*sp != ':') return false; 70 | minutes = value; 71 | 72 | value = strtoul(sp + 1, &sp, 10); 73 | if (*sp != 0) return false; 74 | seconds = value; 75 | 76 | return (is_valid()); 77 | } 78 | 79 | #ifdef TIME_EPOCH_MODIFIABLE 80 | uint16_t time_t::s_epoch_year = Y2K_EPOCH_YEAR; 81 | uint8_t time_t::s_epoch_offset = 0; 82 | uint8_t time_t::s_epoch_weekday = Y2K_EPOCH_WEEKDAY; 83 | uint8_t time_t::s_pivot_year = 0; 84 | #endif 85 | 86 | const uint8_t time_t::days_in[] __PROGMEM = { 87 | 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 88 | }; 89 | 90 | time_t::time_t(clock_t c, int8_t zone) 91 | { 92 | c += zone * (int32_t) SECONDS_PER_HOUR; 93 | uint16_t dayno = c / SECONDS_PER_DAY; 94 | c -= dayno * (uint32_t) SECONDS_PER_DAY; 95 | day = weekday_for(dayno); 96 | 97 | uint16_t y = epoch_year(); 98 | for (;;) { 99 | uint16_t days = days_per( y ); 100 | if (dayno < days) break; 101 | dayno -= days; 102 | y++; 103 | } 104 | bool leap_year = is_leap(y); 105 | y -= epoch_year(); 106 | y += epoch_offset(); 107 | while (y > 100) 108 | y -= 100; 109 | year = y; 110 | 111 | month = 1; 112 | for (;;) { 113 | uint8_t days = pgm_read_byte(&days_in[month]); 114 | if (leap_year && (month == 2)) days++; 115 | if (dayno < days) break; 116 | dayno -= days; 117 | month++; 118 | } 119 | date = dayno + 1; 120 | 121 | hours = c / SECONDS_PER_HOUR; 122 | 123 | uint16_t c_ms; 124 | if (hours < 18) // save 16uS 125 | c_ms = (uint16_t) c - (hours * (uint16_t) SECONDS_PER_HOUR); 126 | else 127 | c_ms = c - (hours * (uint32_t) SECONDS_PER_HOUR); 128 | minutes = c_ms / SECONDS_PER_MINUTE; 129 | seconds = c_ms - (minutes * SECONDS_PER_MINUTE); 130 | } 131 | 132 | void time_t::init() 133 | { 134 | seconds = 135 | hours = 136 | minutes = 0; 137 | date = 1; 138 | month = 1; 139 | year = epoch_year() % 100; 140 | day = epoch_weekday(); 141 | } 142 | 143 | time_t::operator clock_t() const 144 | { 145 | clock_t c = days() * SECONDS_PER_DAY; 146 | if (hours < 18) 147 | c += hours * (uint16_t) SECONDS_PER_HOUR; 148 | else 149 | c += hours * (uint32_t) SECONDS_PER_HOUR; 150 | c += minutes * (uint16_t) SECONDS_PER_MINUTE; 151 | c += seconds; 152 | 153 | return (c); 154 | } 155 | 156 | uint16_t time_t::days() const 157 | { 158 | uint16_t day_count = day_of_year(); 159 | 160 | uint16_t y = full_year(); 161 | while (y-- > epoch_year()) 162 | day_count += days_per(y); 163 | 164 | return (day_count); 165 | } 166 | 167 | uint16_t time_t::day_of_year() const 168 | { 169 | uint16_t dayno = date - 1; 170 | bool leap_year = is_leap(); 171 | 172 | for (uint8_t m = 1; m < month; m++) { 173 | dayno += pgm_read_byte(&days_in[m]); 174 | if (leap_year && (m == 2)) dayno++; 175 | } 176 | 177 | return (dayno); 178 | } 179 | 180 | #ifdef TIME_EPOCH_MODIFIABLE 181 | void time_t::use_fastest_epoch() 182 | { 183 | // Figure out when we were compiled and use the year for a really 184 | // fast epoch_year. Format "MMM DD YYYY" 185 | const char* compile_date = (const char *) PSTR(__DATE__); 186 | uint16_t compile_year = 0; 187 | for (uint8_t i = 7; i < 11; i++) 188 | compile_year = compile_year*10 + (pgm_read_byte(&compile_date[i]) - '0'); 189 | 190 | // Temporarily set a Y2K epoch so we can figure out the day for 191 | // January 1 of this year 192 | epoch_year ( Y2K_EPOCH_YEAR ); 193 | epoch_weekday ( Y2K_EPOCH_WEEKDAY ); 194 | 195 | time_t this_year(0); 196 | this_year.year = compile_year % 100; 197 | this_year.set_day(); 198 | uint8_t compile_weekday = this_year.day; 199 | 200 | epoch_year ( compile_year ); 201 | epoch_weekday( compile_weekday ); 202 | pivot_year ( this_year.year ); 203 | } 204 | #endif -------------------------------------------------------------------------------- /doc/Data Model.md: -------------------------------------------------------------------------------- 1 | Data Model 2 | ========== 3 | Rather than holding onto individual fields, the concept of a **fix** is used to group data members of the GPS acquisition. 4 | This also facilitates the merging of separately received packets into a fused or coherent position. 5 | 6 | ##Members 7 | The members of `gps_fix` include 8 | 9 | * fix status 10 | * date 11 | * time 12 | * latitude and longitude 13 | * altitude 14 | * speed 15 | * heading 16 | * number of satellites 17 | * horizontal, vertical and position dilutions of precision (HDOP, VDOP and PDOP) 18 | * latitude, longitude and altitude error in centimeters 19 | 20 | The members of `NMEAGPS` include 21 | * Talker ID (usually GP) 22 | * Manufacturer ID (from proprietary sentences) 23 | * Satellite Constellation (ID, azimuth, elevation, SNR and tracking) 24 | 25 | You should declare an instance of `NMEAGPS`, which contains an instance of `gps_fix`, called `fix()`: 26 | 27 | ``` 28 | NMEAGPS gps; 29 | 30 | void loop() 31 | { 32 | ... 33 | if (gps.fix().valid.status && gps.fix().status != gps_fix::STATUS_NONE) 34 | // We can hear satellites! 35 | ``` 36 | 37 | Except for `status`, each member is conditionally compiled; any, all, or *no* members can be selected for parsing, storing and fusing. This allows configuring an application to use the minimum amount of RAM for the particular `fix` members of interest. 38 | 39 | To access the members of the current `fix`, please note the following: 40 | 41 | ####Each member has its own validity flag. 42 | Even though you have enabled a particular member, it will not have a value until the related NMEA sentence sets it. Be sure to check the validity flag before attempting to use its value: 43 | ``` 44 | if (gps.fix().valid.altitude) { 45 | z2 = gps.fix().altitude_cm(); 46 | vz = (z2 - z1) / dt; 47 | z1 = z2; 48 | 49 | // Note: if you only care about meters, you could also do this: 50 | // z = gps.fix().alt.whole; 51 | } 52 | ``` 53 | You can also check a collection of flags before performing a calculation involving 54 | multiple members: 55 | ``` 56 | if (gps.fix().valid.altitude && gps.fix().valid.dateTime) { 57 | dt = (clock_t) gps.fix().dateTime - (clock_t) gps.fix().dateTime; 58 | dz = gps.fix().alt.whole - last_alt; // meters 59 | vz = dz / dt; // meters per second vertical velocity 60 | } 61 | ``` 62 | Bonus: The compiler will optimize this into a single bit mask operation. 63 | 64 | ####Integers are used for all members, retaining full precision of the original data. 65 | ``` 66 | if (gps.fix().valid.location) { 67 | // 32-bit ints have 10 significant digits, so you can detect very 68 | // small changes in position: 69 | d_lat = gps.fix().lat - last_lat; 70 | } 71 | ``` 72 | 73 | ####Optional floating-point accessors are provided. 74 | ``` 75 | float lat; 76 | if (gps.fix().valid.location) 77 | lat = gps.fix().latitude(); 78 | 79 | // floats only have about 6 significant digits, so this 80 | // computation is useless for detecting small movements: 81 | foobar = (lat - last_lat); 82 | ``` 83 | 84 | ##Merging 85 | There are several ways to use the GPS fix data: without merging, implicit merging, and explicit merging. 86 | 87 | ###1. On a per-sentence basis (no merging) 88 | If you are interested in a few pieces of information, and these pieces can be obtained from one or two sentences, you can wait for that specific sentence to arrive, and then use one or more members of the `fix()` at that time. See NMEA.ino. 89 | 90 | ###2. On a free-running basis (implicit merging) 91 | If you are interested in more pieces of information, perhaps requiring more kinds of sentences to be decoded, but don't really care about what time the pieces were received, you could enable `NMEAGPS_ACCUMULATE_FIX` (see [Configurations](Configurations.md#nmeagps) and 92 | [NMEAGPS.h](/NMEAGPS.h#L66)). The `fix()` data can be accessed only after DECODE_COMPLETED, or when it `is_safe()`.This data is not necessarily coherent. 93 | 94 | It is possible to achieve coherency if you detect the "quiet" time between batches of sentences. When new data starts coming in, simply call `gps.fix.init()`; all new sentences will set the fix members. Note that if the GPS device loses its fix on the satellites, you can be left without _any_ valid data. 95 | 96 | example/NMEA.ino can be used with implicit merging. However, NMEAfused.ino and NMEAcoherent.ino should not, because they perform their own explicit merging. 97 | 98 | ###3. In selective batches (explicit merging) 99 | If you are interested in pieces of information that are grouped by some criteria, you must perform explicit merging. Additionally, the `merged` object "buffers" the main loop from the constantly-changing `gps.fix()`. The `merged' copy is safe to access at any time: 100 | 101 | ``` 102 | gps_fix_t merged; 103 | 104 | void loop() 105 | { 106 | while (Serial1.available()) 107 | if (gps.decode( Serial1.read() ) == NMEAGPS::DECODE_COMPLETED) 108 | // ...and other criteria 109 | merged |= gps.fix(); 110 | 111 | check_position( merged ); 112 | ``` 113 | See [NMEAfused.ino](/examples/NMEAfused/NMEAfused.ino). 114 | 115 | For example, you may use the Talker ID to separate the fix data into independent groups. Obviously, explicit merging requires one or more extra `gps_fix` copies. 116 | 117 | Explicit merging is also required to implement coherency. Because a sentence has to be parsed to know its timestamp, invalidating old data (i.e., data from a previous update period) must be performed _before_ the sentence parsing begins. That can only be accomplished with a second 'safe' copy of the fix data and explicit merging. With implicit merging, new data has already been mixed with old data by the time DECODE_COMPLETED occurs and timestamps can be checked. 118 | 119 | To implement coherency, you should clear out the merged fix when a new time 120 | interval begins: 121 | ``` 122 | if (new_interval) 123 | merged = gps.fix(); // replace 124 | else 125 | merged |= gps.fix(); // merge 126 | ``` 127 | See [NMEAcoherent.ino](/examples/NMEAcoherent/NMEAcoherent.ino#L67) 128 | -------------------------------------------------------------------------------- /NMEAGPS_cfg.h: -------------------------------------------------------------------------------- 1 | #ifndef NMEAGPS_CFG_H 2 | #define NMEAGPS_CFG_H 3 | 4 | //------------------------------------------------------ 5 | // Enable/disable the parsing of specific sentences. 6 | // 7 | // Configuring out a sentence prevents its fields from being parsed. 8 | // However, the sentence type will still be recognized by /decode/ and 9 | // stored in member /nmeaMessage/. No valid flags would be available. 10 | // 11 | // Only RMC and ZDA contain date information. Other 12 | // sentences contain time information. Both date and time are 13 | // required if you will be doing time_t-to-clock_t operations. 14 | 15 | #define NMEAGPS_PARSE_GGA 16 | //#define NMEAGPS_PARSE_GLL 17 | //#define NMEAGPS_PARSE_GSA 18 | //#define NMEAGPS_PARSE_GSV 19 | //#define NMEAGPS_PARSE_GST 20 | #define NMEAGPS_PARSE_RMC 21 | //#define NMEAGPS_PARSE_VTG 22 | //#define NMEAGPS_PARSE_ZDA 23 | 24 | //------------------------------------------------------ 25 | // Enable/disable the talker ID and manufacturer ID processing. 26 | // 27 | // First, some background information. There are two kinds of NMEA sentences: 28 | // 29 | // 1. Standard NMEA sentences begin with "$ttccc", where 30 | // "tt" is the talker ID, and 31 | // "ccc" is the variable-length sentence type (i.e., command). 32 | // 33 | // For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long) 34 | // transmitted by talker "GP". This is the most common talker ID. Some 35 | // devices may report "$GNGLL,..." when a mix of GPS and non-GPS 36 | // satellites have been used to determine the GLL data. 37 | // 38 | // 2. Proprietary NMEA sentences (i.e., those unique to a particular 39 | // manufacturer) begin with "$Pmmmccc", where 40 | // "P" is the NMEA-defined prefix indicator for proprietary messages, 41 | // "mmm" is the 3-character manufacturer ID, and 42 | // "ccc" is the variable-length sentence type (it can be empty). 43 | // 44 | // No validation of manufacturer ID and talker ID is performed in this 45 | // base class. For example, although "GP" is a common talker ID, it is not 46 | // guaranteed to be transmitted by your particular device, and it IS NOT 47 | // REQUIRED. If you need validation of these IDs, or you need to use the 48 | // extra information provided by some devices, you have two independent 49 | // options: 50 | // 51 | // 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the 52 | // /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire 53 | // sentence will be parsed, perhaps modifying members of /fix/. You should 54 | // enable one or both IDs if you want the information in all sentences *and* 55 | // you also want to know the ID bytes. This add two bytes of RAM for the 56 | // talker ID, and 3 bytes of RAM for the manufacturer ID. 57 | // 58 | // 2. Enable PARSING the ID: The virtual /parse_talker_id/ and 59 | // /parse_mfr_id/ will receive each ID character as it is received. If it 60 | // is not a valid ID, return /false/ to abort processing the rest of the 61 | // sentence. No CPU time will be wasted on the invalid sentence, and no 62 | // /fix/ members will be modified. You should enable this if you want to 63 | // ignore some IDs. You must override /parse_talker_id/ and/or 64 | // /parse_mfr_id/ in a derived class. 65 | // 66 | 67 | //#define NMEAGPS_SAVE_TALKER_ID 68 | //#define NMEAGPS_PARSE_TALKER_ID 69 | 70 | //#define NMEAGPS_SAVE_MFR_ID 71 | #define NMEAGPS_PARSE_MFR_ID 72 | 73 | //------------------------------------------------------ 74 | // Enable/disable tracking the current satellite array and, 75 | // optionally, all the info for each satellite. 76 | // 77 | 78 | //#define NMEAGPS_PARSE_SATELLITES 79 | //#define NMEAGPS_PARSE_SATELLITE_INFO 80 | //#define NMEAGPS_MAX_SATELLITES (40) 81 | 82 | #ifdef NMEAGPS_PARSE_SATELLITES 83 | #ifndef GPS_FIX_SATELLITES 84 | #error GPS_FIX_SATELLITES must be defined in GPSfix.h! 85 | #endif 86 | #endif 87 | 88 | #if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \ 89 | !defined(NMEAGPS_PARSE_SATELLITES) 90 | #error NMEAGPS_PARSE_SATELLITES must be defined! 91 | #endif 92 | 93 | //------------------------------------------------------ 94 | // Enable/disable accumulating fix data across sentences. 95 | // 96 | // If not defined, the fix will contain data from only the last decoded sentence. 97 | // 98 | // If defined, the fix will contain data from all received sentences. Each 99 | // fix member will contain the last value received from any sentence that 100 | // contains that information. This means that fix members may contain 101 | // information from different time intervals (i.e., they are not coherent). 102 | // 103 | // ALSO NOTE: If a received sentence is rejected for any reason (e.g., CRC 104 | // error), all the values are suspect. The fix will be cleared; no members 105 | // will be valid until new sentences are received and accepted. 106 | // 107 | // This is an application tradeoff between keeping a merged copy of received 108 | // fix data (more RAM) vs. accommodating "gaps" in fix data (more code). 109 | // 110 | // SEE ALSO: NMEAfused.ino and NMEAcoherent.ino 111 | 112 | //#define NMEAGPS_ACCUMULATE_FIX 113 | 114 | #ifdef NMEAGPS_ACCUMULATE_FIX 115 | 116 | // When accumulating, nothing is done to the fix at the beginning of every sentence 117 | #define NMEAGPS_INIT_FIX(m) 118 | 119 | // ...but we invalidate one part when it starts to get parsed. It *may* get 120 | // validated when the parsing is finished. 121 | #define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false 122 | 123 | #else 124 | 125 | // When NOT accumulating, invalidate the entire fix at the beginning of every sentence 126 | #define NMEAGPS_INIT_FIX(m) m.valid.init() 127 | 128 | // ...so the individual parts do not need to be invalidated as they are parsed 129 | #define NMEAGPS_INVALIDATE(m) 130 | 131 | #endif 132 | 133 | 134 | //------------------------------------------------------ 135 | // Enable/disable gathering interface statistics: 136 | // CRC errors and number of sentences received 137 | 138 | //#define NMEAGPS_STATS 139 | 140 | //------------------------------------------------------ 141 | // Configuration item for allowing derived types of NMEAGPS. 142 | // If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES. 143 | // If not defined, virtuals are not used, with a slight size (2 bytes) and 144 | // execution time savings. 145 | 146 | //#define NMEAGPS_DERIVED_TYPES 147 | 148 | #ifdef NMEAGPS_DERIVED_TYPES 149 | #define NMEAGPS_VIRTUAL virtual 150 | #else 151 | #define NMEAGPS_VIRTUAL 152 | #endif 153 | 154 | #if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \ 155 | !defined(NMEAGPS_DERIVED_TYPES) 156 | #error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs! 157 | #endif 158 | 159 | #endif -------------------------------------------------------------------------------- /configs/DTL/NMEAGPS_cfg.h: -------------------------------------------------------------------------------- 1 | #ifndef NMEAGPS_CFG_H 2 | #define NMEAGPS_CFG_H 3 | 4 | //------------------------------------------------------ 5 | // Enable/disable the parsing of specific sentences. 6 | // 7 | // Configuring out a sentence prevents its fields from being parsed. 8 | // However, the sentence type will still be recognized by /decode/ and 9 | // stored in member /nmeaMessage/. No valid flags would be available. 10 | // 11 | // Only RMC and ZDA contain date information. Other 12 | // sentences contain time information. Both date and time are 13 | // required if you will be doing time_t-to-clock_t operations. 14 | 15 | #define NMEAGPS_PARSE_GGA 16 | //#define NMEAGPS_PARSE_GLL 17 | //#define NMEAGPS_PARSE_GSA 18 | //#define NMEAGPS_PARSE_GSV 19 | //#define NMEAGPS_PARSE_GST 20 | #define NMEAGPS_PARSE_RMC 21 | //#define NMEAGPS_PARSE_VTG 22 | //#define NMEAGPS_PARSE_ZDA 23 | 24 | //------------------------------------------------------ 25 | // Enable/disable the talker ID and manufacturer ID processing. 26 | // 27 | // First, some background information. There are two kinds of NMEA sentences: 28 | // 29 | // 1. Standard NMEA sentences begin with "$ttccc", where 30 | // "tt" is the talker ID, and 31 | // "ccc" is the variable-length sentence type (i.e., command). 32 | // 33 | // For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long) 34 | // transmitted by talker "GP". This is the most common talker ID. Some 35 | // devices may report "$GNGLL,..." when a mix of GPS and non-GPS 36 | // satellites have been used to determine the GLL data. 37 | // 38 | // 2. Proprietary NMEA sentences (i.e., those unique to a particular 39 | // manufacturer) begin with "$Pmmmccc", where 40 | // "P" is the NMEA-defined prefix indicator for proprietary messages, 41 | // "mmm" is the 3-character manufacturer ID, and 42 | // "ccc" is the variable-length sentence type (it can be empty). 43 | // 44 | // No validation of manufacturer ID and talker ID is performed in this 45 | // base class. For example, although "GP" is a common talker ID, it is not 46 | // guaranteed to be transmitted by your particular device, and it IS NOT 47 | // REQUIRED. If you need validation of these IDs, or you need to use the 48 | // extra information provided by some devices, you have two independent 49 | // options: 50 | // 51 | // 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the 52 | // /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire 53 | // sentence will be parsed, perhaps modifying members of /fix/. You should 54 | // enable one or both IDs if you want the information in all sentences *and* 55 | // you also want to know the ID bytes. This add two bytes of RAM for the 56 | // talker ID, and 3 bytes of RAM for the manufacturer ID. 57 | // 58 | // 2. Enable PARSING the ID: The virtual /parse_talker_id/ and 59 | // /parse_mfr_id/ will receive each ID character as it is received. If it 60 | // is not a valid ID, return /false/ to abort processing the rest of the 61 | // sentence. No CPU time will be wasted on the invalid sentence, and no 62 | // /fix/ members will be modified. You should enable this if you want to 63 | // ignore some IDs. You must override /parse_talker_id/ and/or 64 | // /parse_mfr_id/ in a derived class. 65 | // 66 | 67 | //#define NMEAGPS_SAVE_TALKER_ID 68 | //#define NMEAGPS_PARSE_TALKER_ID 69 | 70 | //#define NMEAGPS_SAVE_MFR_ID 71 | //#define NMEAGPS_PARSE_MFR_ID 72 | 73 | //------------------------------------------------------ 74 | // Enable/disable tracking the current satellite array and, 75 | // optionally, all the info for each satellite. 76 | // 77 | 78 | //#define NMEAGPS_PARSE_SATELLITES 79 | //#define NMEAGPS_PARSE_SATELLITE_INFO 80 | //#define NMEAGPS_MAX_SATELLITES (40) 81 | 82 | #ifdef NMEAGPS_PARSE_SATELLITES 83 | #ifndef GPS_FIX_SATELLITES 84 | #error GPS_FIX_SATELLITES must be defined in GPSfix.h! 85 | #endif 86 | #endif 87 | 88 | #if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \ 89 | !defined(NMEAGPS_PARSE_SATELLITES) 90 | #error NMEAGPS_PARSE_SATELLITES must be defined! 91 | #endif 92 | 93 | //------------------------------------------------------ 94 | // Enable/disable accumulating fix data across sentences. 95 | // 96 | // If not defined, the fix will contain data from only the last decoded sentence. 97 | // 98 | // If defined, the fix will contain data from all received sentences. Each 99 | // fix member will contain the last value received from any sentence that 100 | // contains that information. This means that fix members may contain 101 | // information from different time intervals (i.e., they are not coherent). 102 | // 103 | // ALSO NOTE: If a received sentence is rejected for any reason (e.g., CRC 104 | // error), all the values are suspect. The fix will be cleared; no members 105 | // will be valid until new sentences are received and accepted. 106 | // 107 | // This is an application tradeoff between keeping a merged copy of received 108 | // fix data (more RAM) vs. accommodating "gaps" in fix data (more code). 109 | // 110 | // SEE ALSO: NMEAfused.ino and NMEAcoherent.ino 111 | 112 | //#define NMEAGPS_ACCUMULATE_FIX 113 | 114 | #ifdef NMEAGPS_ACCUMULATE_FIX 115 | 116 | // When accumulating, nothing is done to the fix at the beginning of every sentence 117 | #define NMEAGPS_INIT_FIX(m) 118 | 119 | // ...but we invalidate one part when it starts to get parsed. It *may* get 120 | // validated when the parsing is finished. 121 | #define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false 122 | 123 | #else 124 | 125 | // When NOT accumulating, invalidate the entire fix at the beginning of every sentence 126 | #define NMEAGPS_INIT_FIX(m) m.valid.init() 127 | 128 | // ...so the individual parts do not need to be invalidated as they are parsed 129 | #define NMEAGPS_INVALIDATE(m) 130 | 131 | #endif 132 | 133 | 134 | //------------------------------------------------------ 135 | // Enable/disable gathering interface statistics: 136 | // CRC errors and number of sentences received 137 | 138 | //#define NMEAGPS_STATS 139 | 140 | //------------------------------------------------------ 141 | // Configuration item for allowing derived types of NMEAGPS. 142 | // If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES. 143 | // If not defined, virtuals are not used, with a slight size (2 bytes) and 144 | // execution time savings. 145 | 146 | //#define NMEAGPS_DERIVED_TYPES 147 | 148 | #ifdef NMEAGPS_DERIVED_TYPES 149 | #define NMEAGPS_VIRTUAL virtual 150 | #else 151 | #define NMEAGPS_VIRTUAL 152 | #endif 153 | 154 | #if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \ 155 | !defined(NMEAGPS_DERIVED_TYPES) 156 | #error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs! 157 | #endif 158 | 159 | #endif -------------------------------------------------------------------------------- /configs/Nominal/NMEAGPS_cfg.h: -------------------------------------------------------------------------------- 1 | #ifndef NMEAGPS_CFG_H 2 | #define NMEAGPS_CFG_H 3 | 4 | //------------------------------------------------------ 5 | // Enable/disable the parsing of specific sentences. 6 | // 7 | // Configuring out a sentence prevents its fields from being parsed. 8 | // However, the sentence type will still be recognized by /decode/ and 9 | // stored in member /nmeaMessage/. No valid flags would be available. 10 | // 11 | // Only RMC and ZDA contain date information. Other 12 | // sentences contain time information. Both date and time are 13 | // required if you will be doing time_t-to-clock_t operations. 14 | 15 | #define NMEAGPS_PARSE_GGA 16 | //#define NMEAGPS_PARSE_GLL 17 | //#define NMEAGPS_PARSE_GSA 18 | //#define NMEAGPS_PARSE_GSV 19 | //#define NMEAGPS_PARSE_GST 20 | #define NMEAGPS_PARSE_RMC 21 | //#define NMEAGPS_PARSE_VTG 22 | //#define NMEAGPS_PARSE_ZDA 23 | 24 | //------------------------------------------------------ 25 | // Enable/disable the talker ID and manufacturer ID processing. 26 | // 27 | // First, some background information. There are two kinds of NMEA sentences: 28 | // 29 | // 1. Standard NMEA sentences begin with "$ttccc", where 30 | // "tt" is the talker ID, and 31 | // "ccc" is the variable-length sentence type (i.e., command). 32 | // 33 | // For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long) 34 | // transmitted by talker "GP". This is the most common talker ID. Some 35 | // devices may report "$GNGLL,..." when a mix of GPS and non-GPS 36 | // satellites have been used to determine the GLL data. 37 | // 38 | // 2. Proprietary NMEA sentences (i.e., those unique to a particular 39 | // manufacturer) begin with "$Pmmmccc", where 40 | // "P" is the NMEA-defined prefix indicator for proprietary messages, 41 | // "mmm" is the 3-character manufacturer ID, and 42 | // "ccc" is the variable-length sentence type (it can be empty). 43 | // 44 | // No validation of manufacturer ID and talker ID is performed in this 45 | // base class. For example, although "GP" is a common talker ID, it is not 46 | // guaranteed to be transmitted by your particular device, and it IS NOT 47 | // REQUIRED. If you need validation of these IDs, or you need to use the 48 | // extra information provided by some devices, you have two independent 49 | // options: 50 | // 51 | // 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the 52 | // /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire 53 | // sentence will be parsed, perhaps modifying members of /fix/. You should 54 | // enable one or both IDs if you want the information in all sentences *and* 55 | // you also want to know the ID bytes. This add two bytes of RAM for the 56 | // talker ID, and 3 bytes of RAM for the manufacturer ID. 57 | // 58 | // 2. Enable PARSING the ID: The virtual /parse_talker_id/ and 59 | // /parse_mfr_id/ will receive each ID character as it is received. If it 60 | // is not a valid ID, return /false/ to abort processing the rest of the 61 | // sentence. No CPU time will be wasted on the invalid sentence, and no 62 | // /fix/ members will be modified. You should enable this if you want to 63 | // ignore some IDs. You must override /parse_talker_id/ and/or 64 | // /parse_mfr_id/ in a derived class. 65 | // 66 | 67 | //#define NMEAGPS_SAVE_TALKER_ID 68 | //#define NMEAGPS_PARSE_TALKER_ID 69 | 70 | //#define NMEAGPS_SAVE_MFR_ID 71 | #define NMEAGPS_PARSE_MFR_ID 72 | 73 | //------------------------------------------------------ 74 | // Enable/disable tracking the current satellite array and, 75 | // optionally, all the info for each satellite. 76 | // 77 | 78 | //#define NMEAGPS_PARSE_SATELLITES 79 | //#define NMEAGPS_PARSE_SATELLITE_INFO 80 | //#define NMEAGPS_MAX_SATELLITES (40) 81 | 82 | #ifdef NMEAGPS_PARSE_SATELLITES 83 | #ifndef GPS_FIX_SATELLITES 84 | #error GPS_FIX_SATELLITES must be defined in GPSfix.h! 85 | #endif 86 | #endif 87 | 88 | #if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \ 89 | !defined(NMEAGPS_PARSE_SATELLITES) 90 | #error NMEAGPS_PARSE_SATELLITES must be defined! 91 | #endif 92 | 93 | //------------------------------------------------------ 94 | // Enable/disable accumulating fix data across sentences. 95 | // 96 | // If not defined, the fix will contain data from only the last decoded sentence. 97 | // 98 | // If defined, the fix will contain data from all received sentences. Each 99 | // fix member will contain the last value received from any sentence that 100 | // contains that information. This means that fix members may contain 101 | // information from different time intervals (i.e., they are not coherent). 102 | // 103 | // ALSO NOTE: If a received sentence is rejected for any reason (e.g., CRC 104 | // error), all the values are suspect. The fix will be cleared; no members 105 | // will be valid until new sentences are received and accepted. 106 | // 107 | // This is an application tradeoff between keeping a merged copy of received 108 | // fix data (more RAM) vs. accommodating "gaps" in fix data (more code). 109 | // 110 | // SEE ALSO: NMEAfused.ino and NMEAcoherent.ino 111 | 112 | //#define NMEAGPS_ACCUMULATE_FIX 113 | 114 | #ifdef NMEAGPS_ACCUMULATE_FIX 115 | 116 | // When accumulating, nothing is done to the fix at the beginning of every sentence 117 | #define NMEAGPS_INIT_FIX(m) 118 | 119 | // ...but we invalidate one part when it starts to get parsed. It *may* get 120 | // validated when the parsing is finished. 121 | #define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false 122 | 123 | #else 124 | 125 | // When NOT accumulating, invalidate the entire fix at the beginning of every sentence 126 | #define NMEAGPS_INIT_FIX(m) m.valid.init() 127 | 128 | // ...so the individual parts do not need to be invalidated as they are parsed 129 | #define NMEAGPS_INVALIDATE(m) 130 | 131 | #endif 132 | 133 | 134 | //------------------------------------------------------ 135 | // Enable/disable gathering interface statistics: 136 | // CRC errors and number of sentences received 137 | 138 | //#define NMEAGPS_STATS 139 | 140 | //------------------------------------------------------ 141 | // Configuration item for allowing derived types of NMEAGPS. 142 | // If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES. 143 | // If not defined, virtuals are not used, with a slight size (2 bytes) and 144 | // execution time savings. 145 | 146 | //#define NMEAGPS_DERIVED_TYPES 147 | 148 | #ifdef NMEAGPS_DERIVED_TYPES 149 | #define NMEAGPS_VIRTUAL virtual 150 | #else 151 | #define NMEAGPS_VIRTUAL 152 | #endif 153 | 154 | #if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \ 155 | !defined(NMEAGPS_DERIVED_TYPES) 156 | #error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs! 157 | #endif 158 | 159 | #endif -------------------------------------------------------------------------------- /configs/Minimal/NMEAGPS_cfg.h: -------------------------------------------------------------------------------- 1 | #ifndef NMEAGPS_CFG_H 2 | #define NMEAGPS_CFG_H 3 | 4 | //------------------------------------------------------ 5 | // Enable/disable the parsing of specific sentences. 6 | // 7 | // Configuring out a sentence prevents its fields from being parsed. 8 | // However, the sentence type will still be recognized by /decode/ and 9 | // stored in member /nmeaMessage/. No valid flags would be available. 10 | // 11 | // Only RMC and ZDA contain date information. Other 12 | // sentences contain time information. Both date and time are 13 | // required if you will be doing time_t-to-clock_t operations. 14 | 15 | //#define NMEAGPS_PARSE_GGA 16 | //#define NMEAGPS_PARSE_GLL 17 | //#define NMEAGPS_PARSE_GSA 18 | //#define NMEAGPS_PARSE_GSV 19 | //#define NMEAGPS_PARSE_GST 20 | //#define NMEAGPS_PARSE_RMC 21 | //#define NMEAGPS_PARSE_VTG 22 | //#define NMEAGPS_PARSE_ZDA 23 | 24 | //------------------------------------------------------ 25 | // Enable/disable the talker ID and manufacturer ID processing. 26 | // 27 | // First, some background information. There are two kinds of NMEA sentences: 28 | // 29 | // 1. Standard NMEA sentences begin with "$ttccc", where 30 | // "tt" is the talker ID, and 31 | // "ccc" is the variable-length sentence type (i.e., command). 32 | // 33 | // For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long) 34 | // transmitted by talker "GP". This is the most common talker ID. Some 35 | // devices may report "$GNGLL,..." when a mix of GPS and non-GPS 36 | // satellites have been used to determine the GLL data. 37 | // 38 | // 2. Proprietary NMEA sentences (i.e., those unique to a particular 39 | // manufacturer) begin with "$Pmmmccc", where 40 | // "P" is the NMEA-defined prefix indicator for proprietary messages, 41 | // "mmm" is the 3-character manufacturer ID, and 42 | // "ccc" is the variable-length sentence type (it can be empty). 43 | // 44 | // No validation of manufacturer ID and talker ID is performed in this 45 | // base class. For example, although "GP" is a common talker ID, it is not 46 | // guaranteed to be transmitted by your particular device, and it IS NOT 47 | // REQUIRED. If you need validation of these IDs, or you need to use the 48 | // extra information provided by some devices, you have two independent 49 | // options: 50 | // 51 | // 1. Enable SAVING the ID: When /decode/ returns DECODE_COMPLETED, the 52 | // /talker_id/ and/or /mfr_id/ members will contain ID bytes. The entire 53 | // sentence will be parsed, perhaps modifying members of /fix/. You should 54 | // enable one or both IDs if you want the information in all sentences *and* 55 | // you also want to know the ID bytes. This add two bytes of RAM for the 56 | // talker ID, and 3 bytes of RAM for the manufacturer ID. 57 | // 58 | // 2. Enable PARSING the ID: The virtual /parse_talker_id/ and 59 | // /parse_mfr_id/ will receive each ID character as it is received. If it 60 | // is not a valid ID, return /false/ to abort processing the rest of the 61 | // sentence. No CPU time will be wasted on the invalid sentence, and no 62 | // /fix/ members will be modified. You should enable this if you want to 63 | // ignore some IDs. You must override /parse_talker_id/ and/or 64 | // /parse_mfr_id/ in a derived class. 65 | // 66 | 67 | //#define NMEAGPS_SAVE_TALKER_ID 68 | //#define NMEAGPS_PARSE_TALKER_ID 69 | 70 | //#define NMEAGPS_SAVE_MFR_ID 71 | //#define NMEAGPS_PARSE_MFR_ID 72 | 73 | //------------------------------------------------------ 74 | // Enable/disable tracking the current satellite array and, 75 | // optionally, all the info for each satellite. 76 | // 77 | 78 | //#define NMEAGPS_PARSE_SATELLITES 79 | //#define NMEAGPS_PARSE_SATELLITE_INFO 80 | //#define NMEAGPS_MAX_SATELLITES (40) 81 | 82 | #ifdef NMEAGPS_PARSE_SATELLITES 83 | #ifndef GPS_FIX_SATELLITES 84 | #error GPS_FIX_SATELLITES must be defined in GPSfix.h! 85 | #endif 86 | #endif 87 | 88 | #if defined(NMEAGPS_PARSE_SATELLITE_INFO) & \ 89 | !defined(NMEAGPS_PARSE_SATELLITES) 90 | #error NMEAGPS_PARSE_SATELLITES must be defined! 91 | #endif 92 | 93 | //------------------------------------------------------ 94 | // Enable/disable accumulating fix data across sentences. 95 | // 96 | // If not defined, the fix will contain data from only the last decoded sentence. 97 | // 98 | // If defined, the fix will contain data from all received sentences. Each 99 | // fix member will contain the last value received from any sentence that 100 | // contains that information. This means that fix members may contain 101 | // information from different time intervals (i.e., they are not coherent). 102 | // 103 | // ALSO NOTE: If a received sentence is rejected for any reason (e.g., CRC 104 | // error), all the values are suspect. The fix will be cleared; no members 105 | // will be valid until new sentences are received and accepted. 106 | // 107 | // This is an application tradeoff between keeping a merged copy of received 108 | // fix data (more RAM) vs. accommodating "gaps" in fix data (more code). 109 | // 110 | // SEE ALSO: NMEAfused.ino and NMEAcoherent.ino 111 | 112 | //#define NMEAGPS_ACCUMULATE_FIX 113 | 114 | #ifdef NMEAGPS_ACCUMULATE_FIX 115 | 116 | // When accumulating, nothing is done to the fix at the beginning of every sentence 117 | #define NMEAGPS_INIT_FIX(m) 118 | 119 | // ...but we invalidate one part when it starts to get parsed. It *may* get 120 | // validated when the parsing is finished. 121 | #define NMEAGPS_INVALIDATE(m) m_fix.valid.m = false 122 | 123 | #else 124 | 125 | // When NOT accumulating, invalidate the entire fix at the beginning of every sentence 126 | #define NMEAGPS_INIT_FIX(m) m.valid.init() 127 | 128 | // ...so the individual parts do not need to be invalidated as they are parsed 129 | #define NMEAGPS_INVALIDATE(m) 130 | 131 | #endif 132 | 133 | 134 | //------------------------------------------------------ 135 | // Enable/disable gathering interface statistics: 136 | // CRC errors and number of sentences received 137 | 138 | //#define NMEAGPS_STATS 139 | 140 | //------------------------------------------------------ 141 | // Configuration item for allowing derived types of NMEAGPS. 142 | // If you derive classes from NMEAGPS, you *must* define NMEAGPS_DERIVED_TYPES. 143 | // If not defined, virtuals are not used, with a slight size (2 bytes) and 144 | // execution time savings. 145 | 146 | //#define NMEAGPS_DERIVED_TYPES 147 | 148 | #ifdef NMEAGPS_DERIVED_TYPES 149 | #define NMEAGPS_VIRTUAL virtual 150 | #else 151 | #define NMEAGPS_VIRTUAL 152 | #endif 153 | 154 | #if (defined(NMEAGPS_PARSE_TALKER_ID) | defined(NMEAGPS_PARSE_MFR_ID)) & \ 155 | !defined(NMEAGPS_DERIVED_TYPES) 156 | #error You must define NMEAGPS_DERIVED_TYPES in NMEAGPS.h in order to parse Talker and/or Mfr IDs! 157 | #endif 158 | 159 | #endif -------------------------------------------------------------------------------- /doc/Configurations.md: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============= 3 | All configuration items are conditional compilations: a `#define` controls an `#if`/`#endif` section. 4 | Delete or comment out any items to be excluded from your build. Where 5 | possible, checks are performed to verify that you have chosen a "valid" 6 | configuration: you may see `#error` messages in the build log. See also **Troubleshooting** section below. 7 | 8 | ####gps_fix 9 | The following configuration items are near the top of GPSfix_cfg.h: 10 | ``` 11 | // Enable/Disable individual parts of a fix, as parsed from fields of a $GPxxx sentence 12 | #define GPS_FIX_DATE 13 | #define GPS_FIX_TIME 14 | #define GPS_FIX_LOCATION 15 | #define GPS_FIX_ALTITUDE 16 | #define GPS_FIX_SPEED 17 | #define GPS_FIX_HEADING 18 | #define GPS_FIX_SATELLITES 19 | #define GPS_FIX_HDOP 20 | #define GPS_FIX_VDOP 21 | #define GPS_FIX_PDOP 22 | #define GPS_FIX_LAT_ERR 23 | #define GPS_FIX_LON_ERR 24 | #define GPS_FIX_ALT_ERR 25 | ``` 26 | ####NMEAGPS 27 | The following configuration items are near the top of NMEAGPS_cfg.h. 28 | ####Enable/Disable parsing the fields of a $GPxxx sentence 29 | ``` 30 | #define NMEAGPS_PARSE_GGA 31 | #define NMEAGPS_PARSE_GLL 32 | #define NMEAGPS_PARSE_GSA 33 | #define NMEAGPS_PARSE_GSV 34 | #define NMEAGPS_PARSE_GST 35 | #define NMEAGPS_PARSE_RMC 36 | #define NMEAGPS_PARSE_VTG 37 | #define NMEAGPS_PARSE_ZDA 38 | ``` 39 | ### Enable/disable the talker ID and manufacturer ID processing. 40 | There are two kinds of NMEA sentences: 41 | 42 | 1. Standard NMEA sentences begin with "$ttccc", where 43 | "tt" is the talker ID (e.g., GP), and 44 | "ccc" is the variable-length sentence type (e.g., RMC). 45 | 46 | For example, "$GPGLL,..." is a GLL sentence (Geographic Lat/Long) 47 | transmitted by talker "GP". This is the most common talker ID. Some devices 48 | may report "$GNGLL,..." when a mix of GPS and non-GPS satellites have been 49 | used to determine the GLL data (e.g., GLONASS+GPS). 50 | 51 | 2. Proprietary NMEA sentences (i.e., those unique to a particular manufacturer) 52 | begin with "$Pmmmccc", where 53 | "P" is the NMEA-defined prefix indicator for proprietary messages, 54 | "mmm" is the 3-character manufacturer ID, and 55 | "ccc" is the variable-length sentence type (it can be empty). 56 | 57 | No validation of manufacturer ID and talker ID is performed in this 58 | base class. For example, although "GP" is a common talker ID, it is not 59 | guaranteed to be transmitted by your particular device, and it IS NOT REQUIRED. 60 | If you need validation of these IDs, or you need to use the extra information 61 | provided by some devices, you have two independent options: 62 | 63 | 1. Enable SAVING the ID: When `decode` returns DECODE_COMPLETED, the `talker_id` 64 | and/or `mfr_id` members will contain ID bytes. The entire sentence will be 65 | parsed, perhaps modifying members of `fix`. You should enable one or both IDs 66 | if you want the information in all sentences *and* you also want to know the ID 67 | bytes. This adds 2 bytes of RAM for the talker ID, and 3 bytes of RAM for the 68 | manufacturer ID. 69 | 70 | 2. Enable PARSING the ID: The virtual `parse_talker_id` and `parse_mfr_id` will 71 | receive each ID character as it is received. If it is not a valid ID, return 72 | `false` to abort processing the rest of the sentence. No CPU time will be wasted 73 | on the invalid sentence, and no `fix` members will be modified. You should 74 | enable this if you want to ignore some IDs. You must override `parse_talker_id` 75 | and/or `parse_mfr_id` in a derived class. 76 | 77 | ``` 78 | #define NMEAGPS_SAVE_TALKER_ID 79 | #define NMEAGPS_PARSE_TALKER_ID 80 | 81 | #define NMEAGPS_SAVE_MFR_ID 82 | #define NMEAGPS_PARSE_MFR_ID 83 | ``` 84 | 85 | ###Disable derived types 86 | Although normally enabled, this can be disabled if you *do not* 87 | derive any classes from NMEAGPS, for slightly smaller/faster code. 88 | ``` 89 | #define NMEAGPS_DERIVED_TYPES 90 | ``` 91 | 92 | The ublox-specific files require this define (see [ublox](doc/ublox.md) section). 93 | 94 | ###Enable/disable tracking the current satellite array 95 | You can also enable tracking the detailed information for each satellite, and how many satellites you want to track. 96 | Although many GPS receivers claim to have 66 channels of tracking, 16 is usually the maximum number of satellites 97 | tracked at any one time. 98 | ``` 99 | #define NMEAGPS_PARSE_SATELLITES 100 | #define NMEAGPS_PARSE_SATELLITE_INFO 101 | #define NMEAGPS_MAX_SATELLITES (20) 102 | ``` 103 | 104 | ###Enable/disable accumulating fix data across sentences. 105 | When enabled, `decode` will perform implicit merging of fix data as it is parsed from a new sentence. Each field of a new sentence will invalidate, set and then validate the corresponding member of `fix()`. Any other fix data that was filled by a previous sentence _is not_ invalidated. To enable implicit merging, uncomment this define: 106 | 107 | ``` 108 | #define NMEAGPS_ACCUMULATE_FIX 109 | ``` 110 | 111 | Implicit merging can eliminate the need for a second `fix` (i.e., reduced RAM), but it could prevent coherency. See [Data Model - Merging](Data%20Model.md#Merging) for a discussion of the different types of merging. 112 | 113 | ####Floating-point output. 114 | Streamers.cpp is used by the example programs for printing members of `fix()`. It is not required for parsing the GPS data stream, and this file may be deleted. It is an example of checking validity flags and formatting the various members of `fix()` for textual streams (e.g., Serial prints or SD writes). 115 | 116 | Streamers.cpp has one configuration item: 117 | ``` 118 | #define USE_FLOAT 119 | ``` 120 | This is local to this file, and is only used by the example programs. This file is _not_ required unless you need to stream one of these types: bool, char, uint8_t, int16_t, uint16_t, int32_t, uint32_t, F() strings, `gps_fix` or `NMEAGPS`. 121 | 122 | Most example programs have a choice for displaying fix information once per day. (Untested!) 123 | ``` 124 | #define PULSE_PER_DAY 125 | ``` 126 | ####Typical configurations 127 | A few common configurations are defined as follows 128 | 129 | **Minimal**: no fix members, no messages (pulse-per-second only) 130 | 131 | **DTL**: date, time, lat/lon, GPRMC messsage only. 132 | 133 | **Nominal**: date, time, lat/lon, altitude, speed, heading, number of 134 | satellites, HDOP, GPRMC and GPGGA messages. 135 | 136 | **Full**: Nominal plus talker ID, VDOP, PDOP, lat/lon/alt errors, satellite array with satellite info, all messages, and parser statistics. 137 | 138 | These configurations are available in the [configs](configs) subdirectory. 139 | ______________ 140 | 141 | ####Configurations of other libraries 142 | 143 | **TinyGPS** uses the **Nominal** configuration + a second `fix`. 144 | 145 | **TinyGPSPlus** uses the **Nominal** configuration + statistics + a second fix + timestamps for each `fix` member. 146 | 147 | **Adafruit_GPS** uses the **Nominal** configuration + geoid height and IGNORES CHECKSUM!) 148 | -------------------------------------------------------------------------------- /Streamers.cpp: -------------------------------------------------------------------------------- 1 | #include "Streamers.h" 2 | 3 | #include "NMEAGPS.h" 4 | 5 | uint32_t seconds = 0L; 6 | 7 | //#define USE_FLOAT 8 | 9 | Stream& operator <<( Stream &outs, const bool b ) 10 | { outs.print( b ? 't' : 'f' ); return outs; } 11 | 12 | Stream& operator <<( Stream &outs, const char c ) { outs.print(c); return outs; } 13 | 14 | Stream& operator <<( Stream &outs, const uint16_t v ) { outs.print(v); return outs; } 15 | 16 | Stream& operator <<( Stream &outs, const uint32_t v ) { outs.print(v); return outs; } 17 | 18 | Stream& operator <<( Stream &outs, const int32_t v ) { outs.print(v); return outs; } 19 | 20 | Stream& operator <<( Stream &outs, const uint8_t v ) { outs.print(v); return outs; } 21 | 22 | Stream& operator <<( Stream &outs, const __FlashStringHelper *s ) 23 | { outs.print(s); return outs; } 24 | 25 | //------------------------------------------ 26 | 27 | const char gps_fix_header[] __PROGMEM = 28 | "Status," 29 | 30 | #if defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME) 31 | 32 | #if defined(GPS_FIX_DATE) 33 | "Date" 34 | #endif 35 | #if defined(GPS_FIX_DATE) & defined(GPS_FIX_TIME) 36 | "/" 37 | #endif 38 | #if defined(GPS_FIX_TIME) 39 | "Time" 40 | #endif 41 | 42 | #else 43 | "s" 44 | #endif 45 | "," 46 | 47 | #ifdef GPS_FIX_LOCATION 48 | "Lat,Lon," 49 | #endif 50 | 51 | #if defined(GPS_FIX_HEADING) 52 | "Hdg," 53 | #endif 54 | 55 | #if defined(GPS_FIX_SPEED) 56 | "Spd," 57 | #endif 58 | 59 | #if defined(GPS_FIX_ALTITUDE) 60 | "Alt," 61 | #endif 62 | 63 | #if defined(GPS_FIX_HDOP) 64 | "HDOP," 65 | #endif 66 | 67 | #if defined(GPS_FIX_VDOP) 68 | "VDOP," 69 | #endif 70 | 71 | #if defined(GPS_FIX_PDOP) 72 | "PDOP," 73 | #endif 74 | 75 | #if defined(GPS_FIX_LAT_ERR) 76 | "Lat err," 77 | #endif 78 | 79 | #if defined(GPS_FIX_LON_ERR) 80 | "Lon err," 81 | #endif 82 | 83 | #if defined(GPS_FIX_ALT_ERR) 84 | "Alt err," 85 | #endif 86 | 87 | #if defined(GPS_FIX_SATELLITES) 88 | "Sats," 89 | #endif 90 | 91 | ; 92 | 93 | //............... 94 | 95 | Stream & operator <<( Stream &outs, const gps_fix &fix ) 96 | { 97 | if (fix.valid.status) 98 | outs << (uint8_t) fix.status; 99 | outs << ','; 100 | 101 | #if defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME) 102 | bool someTime = false; 103 | #if defined(GPS_FIX_DATE) 104 | someTime |= fix.valid.date; 105 | #endif 106 | #if defined(GPS_FIX_TIME) 107 | someTime |= fix.valid.time; 108 | #endif 109 | 110 | if (someTime) { 111 | outs << fix.dateTime << '.'; 112 | if (fix.dateTime_cs < 10) 113 | outs << '0'; 114 | outs << fix.dateTime_cs; 115 | } 116 | outs << ','; 117 | 118 | #else 119 | 120 | // Date/Time not enabled, just output the interval number 121 | trace << seconds << ','; 122 | 123 | #endif 124 | 125 | #ifdef USE_FLOAT 126 | #ifdef GPS_FIX_LOCATION 127 | if (fix.valid.location) { 128 | outs.print( fix.latitude(), 6 ); 129 | outs << ','; 130 | outs.print( fix.longitude(), 6 ); 131 | } else 132 | outs << ','; 133 | outs << ','; 134 | #endif 135 | #ifdef GPS_FIX_HEADING 136 | if (fix.valid.heading) 137 | outs.print( fix.heading(), 2 ); 138 | outs << ','; 139 | #endif 140 | #ifdef GPS_FIX_SPEED 141 | if (fix.valid.speed) 142 | outs.print( fix.speed(), 3 ); 143 | outs << ','; 144 | #endif 145 | #ifdef GPS_FIX_ALTITUDE 146 | if (fix.valid.altitude) 147 | outs.print( fix.altitude(), 2 ); 148 | outs << ','; 149 | #endif 150 | 151 | #ifdef GPS_FIX_HDOP 152 | if (fix.valid.hdop) 153 | outs.print( (fix.hdop * 0.001), 3 ); 154 | outs << ','; 155 | #endif 156 | #ifdef GPS_FIX_VDOP 157 | if (fix.valid.vdop) 158 | outs.print( (fix.vdop * 0.001), 3 ); 159 | outs << ','; 160 | #endif 161 | #ifdef GPS_FIX_PDOP 162 | if (fix.valid.pdop) 163 | outs.print( (fix.pdop * 0.001), 3 ); 164 | outs << ','; 165 | #endif 166 | 167 | #ifdef GPS_FIX_LAT_ERR 168 | if (fix.valid.lat_err) 169 | outs.print( fix.lat_err(), 2 ); 170 | outs << ','; 171 | #endif 172 | #ifdef GPS_FIX_LON_ERR 173 | if (fix.valid.lon_err) 174 | outs.print( fix.lon_err(), 2 ); 175 | outs << ','; 176 | #endif 177 | #ifdef GPS_FIX_ALT_ERR 178 | if (fix.valid.alt_err) 179 | outs.print( fix.alt_err(), 2 ); 180 | outs << ','; 181 | #endif 182 | 183 | #else 184 | 185 | // not USE_FLOAT ---------------------- 186 | 187 | #ifdef GPS_FIX_LOCATION 188 | if (fix.valid.location) 189 | outs << fix.latitudeL() << ',' << fix.longitudeL(); 190 | else 191 | outs << ','; 192 | outs << ','; 193 | #endif 194 | #ifdef GPS_FIX_HEADING 195 | if (fix.valid.heading) 196 | outs << fix.heading_cd(); 197 | outs << ','; 198 | #endif 199 | #ifdef GPS_FIX_SPEED 200 | if (fix.valid.speed) 201 | outs << fix.speed_mkn(); 202 | outs << ','; 203 | #endif 204 | #ifdef GPS_FIX_ALTITUDE 205 | if (fix.valid.altitude) 206 | outs << fix.altitude_cm(); 207 | outs << ','; 208 | #endif 209 | 210 | #ifdef GPS_FIX_HDOP 211 | if (fix.valid.hdop) 212 | outs << fix.hdop; 213 | outs << ','; 214 | #endif 215 | #ifdef GPS_FIX_VDOP 216 | if (fix.valid.vdop) 217 | outs << fix.vdop; 218 | outs << ','; 219 | #endif 220 | #ifdef GPS_FIX_PDOP 221 | if (fix.valid.pdop) 222 | outs << fix.pdop; 223 | outs << ','; 224 | #endif 225 | 226 | #ifdef GPS_FIX_LAT_ERR 227 | if (fix.valid.lat_err) 228 | outs << fix.lat_err_cm; 229 | outs << ','; 230 | #endif 231 | #ifdef GPS_FIX_LON_ERR 232 | if (fix.valid.lon_err) 233 | outs << fix.lon_err_cm; 234 | outs << ','; 235 | #endif 236 | #ifdef GPS_FIX_ALT_ERR 237 | if (fix.valid.alt_err) 238 | outs << fix.alt_err_cm; 239 | outs << ','; 240 | #endif 241 | 242 | #endif 243 | 244 | #ifdef GPS_FIX_SATELLITES 245 | if (fix.valid.satellites) 246 | outs << fix.satellites; 247 | outs << ','; 248 | #endif 249 | 250 | return outs; 251 | } 252 | 253 | //----------------------------- 254 | 255 | static const char NMEAGPS_header[] __PROGMEM = 256 | #if defined(NMEAGPS_PARSE_SATELLITES) 257 | "[sat" 258 | #if defined(NMEAGPS_PARSE_SATELLITE_INFO) 259 | " elev/az @ SNR" 260 | #endif 261 | "]," 262 | #endif 263 | 264 | #ifdef NMEAGPS_STATS 265 | "Rx ok,Rx err," 266 | #endif 267 | 268 | ""; 269 | 270 | void trace_header() 271 | { 272 | trace.print( (const __FlashStringHelper *) &gps_fix_header[0] ); 273 | trace.print( (const __FlashStringHelper *) &NMEAGPS_header[0] ); 274 | 275 | trace << '\n'; 276 | } 277 | 278 | //-------------------------- 279 | 280 | void trace_all( const NMEAGPS &gps, const gps_fix &fix ) 281 | { 282 | trace << fix; 283 | 284 | #if defined(NMEAGPS_PARSE_SATELLITES) 285 | trace << '['; 286 | 287 | for (uint8_t i=0; i < gps.sat_count; i++) { 288 | trace << gps.satellites[i].id; 289 | #if defined(NMEAGPS_PARSE_SATELLITE_INFO) 290 | trace << ' ' << 291 | gps.satellites[i].elevation << '/' << gps.satellites[i].azimuth; 292 | trace << '@'; 293 | if (gps.satellites[i].tracked) 294 | trace << gps.satellites[i].snr; 295 | else 296 | trace << '-'; 297 | #endif 298 | trace << ','; 299 | } 300 | 301 | trace << F("],"); 302 | #endif 303 | 304 | #ifdef NMEAGPS_STATS 305 | trace << gps.statistics.ok << ',' << gps.statistics.crc_errors << ','; 306 | #endif 307 | 308 | trace << '\n'; 309 | 310 | } // trace_all 311 | -------------------------------------------------------------------------------- /Time.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Time.h 3 | * @version 1.0 4 | * 5 | * @section License 6 | * Copyright (C) 2014, SlashDevin 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Lesser General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2.1 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Lesser General Public License for more details. 17 | * 18 | * This file is part of the NeoGPS project. Based on the execellent 19 | * framework, Cosa, by Mikael Patel. 20 | */ 21 | 22 | #ifndef TIME_H 23 | #define TIME_H 24 | 25 | #include 26 | 27 | #include "NeoGPS_cfg.h" 28 | #include "CosaCompat.h" 29 | 30 | namespace NeoGPS { 31 | 32 | //------------------------------------------------------ 33 | // Enable/disable run-time modification of the epoch. 34 | // If this is defined, epoch mutators are available. 35 | // If this is not defined, the epoch is a hard-coded constant. 36 | // Only epoch accessors are available. 37 | //#define TIME_EPOCH_MODIFIABLE 38 | 39 | /** 40 | * Number of seconds elapsed since January 1 of the Epoch Year, 41 | * 00:00:00 +0000 (UTC). 42 | */ 43 | typedef uint32_t clock_t; 44 | 45 | const uint32_t SECONDS_PER_DAY = 86400L; 46 | const uint16_t SECONDS_PER_HOUR = 3600; 47 | const uint8_t SECONDS_PER_MINUTE = 60; 48 | const uint8_t DAYS_PER_WEEK = 7; 49 | 50 | /** 51 | * Common date/time structure 52 | */ 53 | struct time_t { 54 | 55 | enum weekday_t { 56 | SUNDAY = 1, 57 | MONDAY = 2, 58 | TUESDAY = 3, 59 | WEDNESDAY = 4, 60 | THURSDAY = 5, 61 | FRIDAY = 6, 62 | SATURDAY = 7 63 | }; 64 | 65 | // NTP epoch year and weekday (Monday) 66 | static const uint16_t NTP_EPOCH_YEAR = 1900; 67 | static const uint8_t NTP_EPOCH_WEEKDAY = MONDAY; 68 | 69 | // POSIX epoch year and weekday (Thursday) 70 | static const uint16_t POSIX_EPOCH_YEAR = 1970; 71 | static const uint8_t POSIX_EPOCH_WEEKDAY = THURSDAY; 72 | 73 | // Y2K epoch year and weekday (Saturday) 74 | static const uint16_t Y2K_EPOCH_YEAR = 2000; 75 | static const uint8_t Y2K_EPOCH_WEEKDAY = SATURDAY; 76 | 77 | uint8_t seconds; //!< 00-59 Seconds. 78 | uint8_t minutes; //!< 00-59 Minutes. 79 | uint8_t hours; //!< 00-23 Hours. 80 | uint8_t day; //!< 01-07 Day. 81 | uint8_t date; //!< 01-31 Date. 82 | uint8_t month; //!< 01-12 Month. 83 | uint8_t year; //!< 00-99 Year. 84 | 85 | /** 86 | * Constructor. 87 | */ 88 | time_t() {} 89 | 90 | /** 91 | * Construct from seconds from the Epoch. 92 | * @param[in] c clock. 93 | * @param[in] zone time (hours adjustment from UTC). 94 | */ 95 | time_t(clock_t c, int8_t zone = 0); 96 | 97 | /** 98 | * Initialize to January 1 of the Epoch Year, 00:00:00 99 | */ 100 | void init(); 101 | 102 | /** 103 | * Convert to seconds. 104 | * @return seconds from epoch. 105 | */ 106 | operator clock_t() const; 107 | 108 | /** 109 | * Set day member from current value. This is a relatively expensive 110 | * operation, so the weekday is only calculated when requested. 111 | */ 112 | void set_day() 113 | { 114 | day = weekday_for(days()); 115 | } 116 | 117 | /** 118 | * Convert to days. 119 | * @return days from January 1 of the epoch year. 120 | */ 121 | uint16_t days() const; 122 | 123 | /** 124 | * Calculate day of the current year. 125 | * @return days from January 1, which is day zero. 126 | */ 127 | uint16_t day_of_year() const; 128 | 129 | /** 130 | * Calculate 4-digit year from internal 2-digit year member. 131 | * @return 4-digit year. 132 | */ 133 | uint16_t full_year() const 134 | { 135 | return full_year(year); 136 | } 137 | 138 | /** 139 | * Calculate 4-digit year from a 2-digit year 140 | * @param[in] year (4-digit). 141 | * @return true if /year/ is a leap year. 142 | */ 143 | static uint16_t full_year( uint8_t year ) 144 | { 145 | uint16_t y = year; 146 | 147 | if (y < pivot_year()) 148 | y += 100 * (epoch_year()/100 + 1); 149 | else 150 | y += 100 * (epoch_year()/100); 151 | 152 | return y; 153 | } 154 | 155 | /** 156 | * Determine whether the current year is a leap year. 157 | * @returns true if the two-digit /year/ member is a leap year. 158 | */ 159 | bool is_leap() const 160 | { 161 | return is_leap(full_year()); 162 | } 163 | 164 | /** 165 | * Determine whether the 4-digit /year/ is a leap year. 166 | * @param[in] year (4-digit). 167 | * @return true if /year/ is a leap year. 168 | */ 169 | static bool is_leap(uint16_t year) 170 | { 171 | if (year % 4) return false; 172 | uint16_t y = year % 400; 173 | return (y == 0) || ((y != 100) && (y != 200) && (y != 300)); 174 | } 175 | 176 | /** 177 | * Calculate how many days are in the specified year. 178 | * @param[in] year (4-digit). 179 | * @return number of days. 180 | */ 181 | static uint16_t days_per(uint16_t year) 182 | { 183 | return (365 + is_leap(year)); 184 | } 185 | 186 | /** 187 | * Determine the day of the week for the specified day number 188 | * @param[in] day number as counted from January 1 of the epoch year. 189 | * @return weekday number 1..7, as for the /day/ member. 190 | */ 191 | static uint8_t weekday_for(uint16_t dayno) 192 | { 193 | return ((dayno+epoch_weekday()-1) % DAYS_PER_WEEK) + 1; 194 | } 195 | 196 | /** 197 | * Check that all members are set to a coherent date/time. 198 | * @return true if valid date/time. 199 | */ 200 | bool is_valid() const 201 | { 202 | return 203 | ((year <= 99) && 204 | (1 <= month) && (month <= 12) && 205 | ((1 <= date) && 206 | ((date <= pgm_read_byte(&days_in[month])) || 207 | ((month == 2) && is_leap() && (date == 29)))) && 208 | (hours <= 23) && 209 | (minutes <= 59) && 210 | (seconds <= 59)); 211 | } 212 | 213 | /** 214 | * Set the epoch year for all time_t operations. Note that the pivot 215 | * year defaults to the epoch_year % 100. Valid years will be in the 216 | * range epoch_year..epoch_year+99. Selecting a different pivot year 217 | * will slide this range to the right. 218 | * @param[in] y epoch year to set. 219 | * See also /full_year/. 220 | */ 221 | #ifdef TIME_EPOCH_MODIFIABLE 222 | static void epoch_year(uint16_t y) 223 | { 224 | s_epoch_year = y; 225 | epoch_offset( s_epoch_year % 100 ); 226 | pivot_year( epoch_offset() ); 227 | } 228 | #endif 229 | 230 | /** 231 | * Get the epoch year. 232 | * @return year. 233 | */ 234 | static uint16_t epoch_year() 235 | { 236 | return (s_epoch_year); 237 | } 238 | 239 | static uint8_t epoch_weekday() { return s_epoch_weekday; }; 240 | #ifdef TIME_EPOCH_MODIFIABLE 241 | static void epoch_weekday( uint8_t ew ) { s_epoch_weekday = ew; }; 242 | #endif 243 | 244 | /** 245 | * The pivot year determine the range of years WRT the epoch_year 246 | * For example, an epoch year of 2000 and a pivot year of 80 will 247 | * allow years in the range 1980 to 2079. Default 0 for Y2K_EPOCH. 248 | */ 249 | static uint8_t pivot_year() { return s_pivot_year; }; 250 | #ifdef TIME_EPOCH_MODIFIABLE 251 | static void pivot_year( uint8_t py ) { s_pivot_year = py; }; 252 | #endif 253 | 254 | /** 255 | * Use the current year for the epoch year. This will result in the 256 | * best performance of conversions, but dates/times before January 1 257 | * of the epoch year cannot be represented. 258 | */ 259 | #ifdef TIME_EPOCH_MODIFIABLE 260 | static void use_fastest_epoch(); 261 | #endif 262 | 263 | /** 264 | * Parse a character string and fill out members. 265 | * @param[in] s PROGMEM character string with format "YYYY-MM-DD HH:MM:SS". 266 | * @return success. 267 | */ 268 | bool parse(str_P s); 269 | 270 | static const uint8_t days_in[] PROGMEM; // month index is 1..12, PROGMEM 271 | 272 | protected: 273 | static uint8_t epoch_offset() { return s_epoch_offset; }; 274 | 275 | #ifdef TIME_EPOCH_MODIFIABLE 276 | static void epoch_offset( uint8_t eo ) { s_epoch_offset = eo; }; 277 | 278 | static uint16_t s_epoch_year; 279 | static uint8_t s_pivot_year; 280 | static uint8_t s_epoch_offset; 281 | static uint8_t s_epoch_weekday; 282 | #else 283 | static const uint16_t s_epoch_year = Y2K_EPOCH_YEAR; 284 | static const uint8_t s_pivot_year = 00; 285 | static const uint8_t s_epoch_offset = 00; 286 | static const uint8_t s_epoch_weekday = Y2K_EPOCH_WEEKDAY; 287 | #endif 288 | 289 | } NEOGPS_PACKED; 290 | 291 | }; // namespace NeoGPS 292 | 293 | class Stream; 294 | 295 | /** 296 | * Print the date/time to the given stream with the format "YYYY-MM-DD HH:MM:SS". 297 | * @param[in] outs output stream. 298 | * @param[in] t time structure. 299 | * @return iostream. 300 | */ 301 | Stream & operator <<( Stream & outs, const NeoGPS::time_t &t ); 302 | 303 | #endif 304 | -------------------------------------------------------------------------------- /GPSfix.h: -------------------------------------------------------------------------------- 1 | #ifndef GPSFIX_H 2 | #define GPSFIX_H 3 | 4 | /** 5 | * @file GPSfix.h 6 | * @version 2.1 7 | * 8 | * @section License 9 | * Copyright (C) 2014, SlashDevin 10 | * 11 | * This library is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU Lesser General Public 13 | * License as published by the Free Software Foundation; either 14 | * version 2.1 of the License, or (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * Lesser General Public License for more details. 20 | */ 21 | 22 | #include "Time.h" 23 | #include "GPSfix_cfg.h" 24 | 25 | /** 26 | * A structure for holding a GPS fix: time, position, velocity, etc. 27 | * 28 | * Because GPS devices report various subsets of a coherent fix, 29 | * this class tracks which members of the fix are being reported: 30 | * each part has its own validity flag. Also, operator |= implements 31 | * merging multiple reports into one consolidated report. 32 | * 33 | * @section Limitations 34 | * Reports are not really fused with an algorithm; if present in 35 | * the source, they are simply replaced in the destination. 36 | * 37 | */ 38 | 39 | class gps_fix { 40 | public: 41 | 42 | gps_fix() { init(); }; 43 | 44 | /** 45 | * A structure for holding the two parts of a floating-point number. 46 | * This is used for Altitude, Heading and Speed, which require more 47 | * significant digits than a 16-bit number. The decimal point is 48 | * used as a field separator for these two parts. This is more efficient 49 | * than calling the 32-bit math subroutines on a single scaled long integer. 50 | * This requires knowing the exponent on the fraction when a simple type 51 | * (e.g., float or int) is needed. 52 | */ 53 | 54 | struct whole_frac { 55 | int16_t whole; 56 | int16_t frac; 57 | void init() { whole = 0; frac = 0; }; 58 | int32_t int32_00() const { return ((int32_t)whole) * 100L + frac; }; 59 | int16_t int16_00() const { return whole * 100 + frac; }; 60 | int32_t int32_000() const { return whole * 1000L + frac; }; 61 | float float_00() const { return ((float)whole) + ((float)frac)*0.01; }; 62 | float float_000() const { return ((float)whole) + ((float)frac)*0.001; }; 63 | } NEOGPS_PACKED; 64 | 65 | #ifdef GPS_FIX_LOCATION 66 | int32_t lat; // degrees * 1e7, negative is South 67 | int32_t lon; // degrees * 1e7, negative is West 68 | 69 | int32_t latitudeL() const { return lat; }; 70 | float latitude () const { return ((float) lat) * 1.0e-7; }; // accuracy loss 71 | 72 | int32_t longitudeL() const { return lon; }; 73 | float longitude () const { return ((float) lon) * 1.0e-7; }; // accuracy loss 74 | #endif 75 | 76 | #ifdef GPS_FIX_ALTITUDE 77 | whole_frac alt; // .01 meters 78 | 79 | int32_t altitude_cm() const { return alt.int32_00(); }; 80 | float altitude () const { return alt.float_00(); }; 81 | #endif 82 | 83 | #ifdef GPS_FIX_SPEED 84 | whole_frac spd; // .001 nautical miles per hour 85 | 86 | uint32_t speed_mkn() const { return spd.int32_000(); }; 87 | float speed () const { return spd.float_000(); }; 88 | #endif 89 | 90 | #ifdef GPS_FIX_HEADING 91 | whole_frac hdg; // .01 degrees 92 | 93 | uint16_t heading_cd() const { return hdg.int16_00(); }; 94 | float heading () const { return hdg.float_00(); }; 95 | #endif 96 | 97 | /** 98 | * Dilution of Precision is a measure of the current satellite 99 | * constellation geometry WRT how 'good' it is for determining a position. This 100 | * is _independent_ of signal strength and many other factors that may be 101 | * internal to the receiver. It _cannot_ be used to determine position accuracy 102 | * in meters. 103 | */ 104 | #ifdef GPS_FIX_HDOP 105 | uint16_t hdop; // Horizontal Dilution of Precision x 1000 106 | #endif 107 | #ifdef GPS_FIX_VDOP 108 | uint16_t vdop; // Horizontal Dilution of Precision x 1000 109 | #endif 110 | #ifdef GPS_FIX_PDOP 111 | uint16_t pdop; // Horizontal Dilution of Precision x 1000 112 | #endif 113 | 114 | #ifdef GPS_FIX_LAT_ERR 115 | uint16_t lat_err_cm; 116 | float lat_err() const { return lat_err_cm / 100.0; } 117 | #endif 118 | 119 | #ifdef GPS_FIX_LON_ERR 120 | uint16_t lon_err_cm; 121 | float lon_err() const { return lon_err_cm / 100.0; } 122 | #endif 123 | 124 | #ifdef GPS_FIX_ALT_ERR 125 | uint16_t alt_err_cm; 126 | float alt_err() const { return alt_err_cm / 100.0; } 127 | #endif 128 | 129 | #ifdef GPS_FIX_SATELLITES 130 | uint8_t satellites; 131 | #endif 132 | 133 | #if defined(GPS_FIX_DATE) | defined(GPS_FIX_TIME) 134 | NeoGPS::time_t dateTime; 135 | uint8_t dateTime_cs; // hundredths of a second 136 | #endif 137 | 138 | /** 139 | * The current fix status or mode of the GPS device. Unfortunately, the NMEA 140 | * sentences are a little inconsistent in their use of "status" and "mode". 141 | * Both fields are mapped onto this enumerated type. Be aware that 142 | * different manufacturers interpret them differently. This can cause 143 | * problems in sentences which include both types (e.g., GPGLL). 144 | * 145 | * Note: Sorted by increasing accuracy. See also /operator |=/. 146 | */ 147 | 148 | enum status_t { 149 | STATUS_NONE, 150 | STATUS_EST, 151 | STATUS_TIME_ONLY, 152 | STATUS_STD, 153 | STATUS_DGPS 154 | }; 155 | 156 | status_t status NEOGPS_BF(8); 157 | 158 | // Flags to indicate which members of this fix are valid. 159 | 160 | struct valid_t { 161 | bool status NEOGPS_BF(1); 162 | 163 | #if defined(GPS_FIX_DATE) 164 | bool date NEOGPS_BF(1); 165 | #endif 166 | 167 | #if defined(GPS_FIX_TIME) 168 | bool time NEOGPS_BF(1); 169 | #endif 170 | 171 | #ifdef GPS_FIX_LOCATION 172 | bool location NEOGPS_BF(1); 173 | #endif 174 | 175 | #ifdef GPS_FIX_ALTITUDE 176 | bool altitude NEOGPS_BF(1); 177 | #endif 178 | 179 | #ifdef GPS_FIX_SPEED 180 | bool speed NEOGPS_BF(1); 181 | #endif 182 | 183 | #ifdef GPS_FIX_HEADING 184 | bool heading NEOGPS_BF(1); 185 | #endif 186 | 187 | #ifdef GPS_FIX_SATELLITES 188 | bool satellites NEOGPS_BF(1); 189 | #endif 190 | 191 | #ifdef GPS_FIX_HDOP 192 | bool hdop NEOGPS_BF(1); 193 | #endif 194 | #ifdef GPS_FIX_VDOP 195 | bool vdop NEOGPS_BF(1); 196 | #endif 197 | #ifdef GPS_FIX_PDOP 198 | bool pdop NEOGPS_BF(1); 199 | #endif 200 | 201 | #ifdef GPS_FIX_LAT_ERR 202 | bool lat_err NEOGPS_BF(1); 203 | #endif 204 | 205 | #ifdef GPS_FIX_LON_ERR 206 | bool lon_err NEOGPS_BF(1); 207 | #endif 208 | 209 | #ifdef GPS_FIX_ALT_ERR 210 | bool alt_err NEOGPS_BF(1); 211 | #endif 212 | 213 | void init() 214 | { 215 | uint8_t *all = (uint8_t *) this; 216 | for (uint8_t i=0; i 27 | #include 28 | 29 | // NOTE: millis() is used for ACK timing 30 | 31 | /** 32 | * Enable/disable the parsing of specific UBX messages. 33 | * 34 | * Configuring out a message prevents its fields from being parsed. 35 | * However, the message type will still be recognized by /decode/ and 36 | * stored in member /rx_msg/. No valid flags would be available. 37 | */ 38 | 39 | #define UBLOX_PARSE_STATUS 40 | #define UBLOX_PARSE_TIMEGPS 41 | #define UBLOX_PARSE_TIMEUTC 42 | #define UBLOX_PARSE_POSLLH 43 | #define UBLOX_PARSE_VELNED 44 | #define UBLOX_PARSE_SVINFO 45 | //#define UBLOX_PARSE_CFGNAV5 46 | //#define UBLOX_PARSE_MONVER 47 | 48 | 49 | 50 | class ubloxGPS : public ubloxNMEA 51 | { 52 | ubloxGPS( const ubloxGPS & ); 53 | 54 | public: 55 | 56 | // Constructor needs to know the device to handle the UBX binary protocol 57 | ubloxGPS( Stream *device ) 58 | : 59 | storage( (ublox::msg_t *) NULL ), 60 | reply( (ublox::msg_t *) NULL ), 61 | reply_expected( false ), 62 | ack_expected( false ), 63 | m_device( device ) 64 | {}; 65 | 66 | /** 67 | * Process one character of ublox message. The internal state 68 | * machine tracks what part of the sentence has been received. As the 69 | * tracks what part of the sentence has been received so far. As the 70 | * sentence is received, members of the /fix/ structure are updated. 71 | * @return DECODE_COMPLETED when a sentence has been completely received. 72 | */ 73 | decode_t decode( char c ); 74 | 75 | /** 76 | * Received message header. Payload is only stored if /storage/ is 77 | * overridden for that message type. 78 | */ 79 | ublox::msg_t & rx() { return m_rx_msg; } 80 | 81 | bool enable_msg( ublox::msg_class_t msg_class, ublox::msg_id_t msg_id ) 82 | { 83 | return send( ublox::cfg_msg_t( msg_class, msg_id, 1 ) ); 84 | } 85 | bool disable_msg( ublox::msg_class_t msg_class, ublox::msg_id_t msg_id ) 86 | { 87 | return send( ublox::cfg_msg_t( msg_class, msg_id, 0 ) ); 88 | } 89 | 90 | /** 91 | * Send a message (non-blocking). 92 | * Although multiple /send_request/s can be issued, 93 | * replies will cause multiple dispatches to /on_event/ 94 | */ 95 | bool send_request( const ublox::msg_t & msg ) 96 | { 97 | write( msg ); 98 | return true; 99 | }; 100 | bool send_request_P( const ublox::msg_t & msg ) 101 | { 102 | write_P( msg ); 103 | return true; 104 | }; 105 | 106 | /** 107 | * Send a message and wait for a reply (blocking). 108 | * No event will be generated for the reply. 109 | * If /msg/ is a UBX_CFG, this will wait for a UBX_CFG_ACK/NAK 110 | * and return true if ACKed. 111 | * If /msg/ is a poll, this will wait for the reply. 112 | * If /msg/ is neither, this will return true immediately. 113 | * If /msg/ is both, this will wait for both the reply and the ACK/NAK. 114 | * If /storage_for/ is implemented, those messages will continue 115 | * to be saved while waiting for this reply. 116 | */ 117 | bool send( const ublox::msg_t & msg, ublox::msg_t *reply_msg = (ublox::msg_t *) NULL ); 118 | bool send_P( const ublox::msg_t & msg, ublox::msg_t *reply_msg = (ublox::msg_t *) NULL ); 119 | 120 | // Ask for a specific message (non-blocking). 121 | // /on_event/ will receive the header later. 122 | // See also /send_request/. 123 | bool poll_request( const ublox::msg_t & msg ) 124 | { 125 | //trace << '?' << msg.msg_class << '/' << msg.msg_id << ' '; 126 | ublox::msg_t poll_msg( msg.msg_class, msg.msg_id, 0 ); 127 | return send_request( poll_msg ); 128 | }; 129 | bool poll_request_P( const ublox::msg_t & msg ) 130 | { 131 | ublox::msg_t poll_msg( (ublox::msg_class_t) pgm_read_byte( &msg.msg_class ), 132 | (ublox::msg_id_t) pgm_read_byte( &msg.msg_id ), 0 ); 133 | return send_request( poll_msg ); 134 | }; 135 | 136 | // Ask for a specific message (blocking). 137 | // See also /send/. 138 | bool poll( ublox::msg_t & msg ) 139 | { 140 | ublox::msg_t poll_msg( msg.msg_class, msg.msg_id, 0 ); 141 | return send( poll_msg, &msg ); 142 | }; 143 | bool poll_P( const ublox::msg_t & msg, ublox::msg_t *reply_msg = (ublox::msg_t *) NULL ) 144 | { 145 | ublox::msg_t poll_msg( (ublox::msg_class_t) pgm_read_byte( &msg.msg_class ), 146 | (ublox::msg_id_t) pgm_read_byte( &msg.msg_id ), 0 ); 147 | return send( poll_msg, reply_msg ); 148 | }; 149 | 150 | // Return the Stream that was passed into the constructor. 151 | Stream *Device() const { return (Stream *)m_device; }; 152 | 153 | protected: 154 | 155 | #if defined( GPS_FIX_LAT_ERR ) | defined( GPS_FIX_LON_ERR ) | \ 156 | defined( GPS_FIX_ALT_ERR ) 157 | // Well, crud. The NAV_POSLLH message has 4-byte received errors in mm. 158 | // These must be converted to the 2-byte gps_fix errors in cm. 159 | // There's no easy way to perform this conversion as the bytes are 160 | // being received, especially when the LSB is received first. 161 | // Hold them here, then divide. 162 | union { 163 | uint32_t U4; 164 | uint16_t U2[2]; 165 | uint8_t U1[4]; 166 | }; 167 | #endif 168 | 169 | /* 170 | * Some UBX messages can be larger than 256 bytes, so 171 | * hide the 8-bit NMEAGPS::chrCount with this 16-bit version. 172 | */ 173 | uint16_t chrCount; 174 | 175 | bool parseField( char chr ); 176 | 177 | enum ubxState_t { 178 | UBX_IDLE = NMEA_IDLE, 179 | UBX_SYNC2 = NMEA_LAST_STATE+1, 180 | UBX_HEAD, 181 | UBX_RECEIVING_DATA, 182 | UBX_CRC_A, 183 | UBX_CRC_B 184 | }; 185 | static const ubxState_t UBX_FIRST_STATE = UBX_SYNC2; 186 | static const ubxState_t UBX_LAST_STATE = UBX_CRC_B; 187 | 188 | inline void write 189 | ( uint8_t c, uint8_t & crc_a, uint8_t & crc_b ) const 190 | { 191 | m_device->print( (char) c ); 192 | crc_a += c; 193 | crc_b += crc_a; 194 | }; 195 | void write( const ublox::msg_t & msg ); 196 | void write_P( const ublox::msg_t & msg ); 197 | 198 | void wait_for_idle(); 199 | bool wait_for_ack(); 200 | bool waiting() const 201 | { 202 | return (ack_expected && (!ack_received && !nak_received)) || 203 | (reply_expected && !reply_received); 204 | } 205 | bool receiving() const 206 | { 207 | return (rxState != (rxState_t)UBX_IDLE) || (m_device && m_device->available()); 208 | } 209 | 210 | // /run/ is called from inside /wait_for_idle/. 211 | // 212 | // If a derived class processes incoming chars in the background 213 | // (e.g., a derived IOStream::Device::/putchar/ called from the IRQ), 214 | // this default method is sufficient. 215 | // 216 | // If /this/ instance processes characters in the foreground, 217 | // /run/ must be provided to continue decoding the input stream while 218 | // waiting for UBX replies. For example, 219 | // 220 | // while (uart.available()) 221 | // decode( uart.getchar() ); 222 | 223 | virtual void run() { }; 224 | 225 | // Override this if the contents of a particular message need to be saved. 226 | // This may execute in an interrupt context, so be quick! 227 | // NOTE: the ublox::msg_t.length will get stepped on, so you may need to 228 | // set it every time if you are using a union for your storage. 229 | virtual ublox::msg_t *storage_for( const ublox::msg_t & rx_msg ) 230 | { return (ublox::msg_t *)NULL; }; 231 | 232 | private: 233 | ublox::msg_t *storage; // cached ptr to hold a received msg. 234 | 235 | // Storage for a specific received message. 236 | // Used internally by send & poll variants. 237 | // Checked and used before /storage_for/ is called. 238 | ublox::msg_t *reply; 239 | 240 | struct { 241 | bool reply_expected NEOGPS_BF(1); 242 | bool reply_received NEOGPS_BF(1); 243 | bool ack_expected NEOGPS_BF(1); 244 | bool ack_received NEOGPS_BF(1); 245 | bool nak_received NEOGPS_BF(1); 246 | bool ack_same_as_sent NEOGPS_BF(1); 247 | } NEOGPS_PACKED; 248 | struct ublox::msg_hdr_t sent; 249 | 250 | struct rx_msg_t : ublox::msg_t 251 | { 252 | uint8_t crc_a; // accumulated as packet received 253 | uint8_t crc_b; // accumulated as packet received 254 | 255 | rx_msg_t() 256 | { 257 | init(); 258 | } 259 | 260 | void init() 261 | { 262 | msg_class = ublox::UBX_UNK; 263 | msg_id = ublox::UBX_ID_UNK; 264 | length = 0; 265 | crc_a = 0; 266 | crc_b = 0; 267 | } 268 | 269 | } NEOGPS_PACKED; 270 | 271 | rx_msg_t m_rx_msg; 272 | 273 | void rxBegin(); 274 | bool rxEnd(); 275 | 276 | static const uint8_t SYNC_1 = 0xB5; 277 | static const uint8_t SYNC_2 = 0x62; 278 | 279 | Stream *m_device; 280 | 281 | bool parseFix( uint8_t c ); 282 | 283 | bool parseTOW( uint8_t chr ) 284 | { 285 | #if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) 286 | if (chrCount == 0) { 287 | m_fix.valid.date = 288 | m_fix.valid.time = false; 289 | } 290 | ((uint8_t *) &m_fix.dateTime)[ chrCount ] = chr; 291 | if (chrCount == 3) { 292 | uint32_t tow = *((uint32_t *) &m_fix.dateTime); 293 | //trace << PSTR("@ ") << tow; 294 | uint16_t ms; 295 | if (GPSTime::from_TOWms( tow, m_fix.dateTime, ms )) { 296 | m_fix.dateTime_cs = ms / 10; 297 | m_fix.valid.time = true; 298 | m_fix.valid.date = true; 299 | } else 300 | m_fix.dateTime.init(); 301 | //trace << PSTR(".") << m_fix.dateTime_cs; 302 | } 303 | #endif 304 | 305 | return true; 306 | } 307 | 308 | } NEOGPS_PACKED; 309 | 310 | #endif 311 | -------------------------------------------------------------------------------- /examples/ublox/ublox.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Serial is for trace output. 3 | Serial1 should be connected to the GPS device. 4 | */ 5 | 6 | #include 7 | 8 | #include "ubxGPS.h" 9 | #include "Streamers.h" 10 | 11 | // Set this to your debug output device. 12 | Stream & trace = Serial; 13 | 14 | 15 | // uncomment this to display just one pulse-per-day. 16 | //#define PULSE_PER_DAY 17 | 18 | //-------------------------- 19 | 20 | class MyGPS : public ubloxGPS 21 | { 22 | public: 23 | 24 | gps_fix merged; 25 | 26 | enum 27 | { 28 | GETTING_STATUS, 29 | GETTING_LEAP_SECONDS, 30 | GETTING_UTC, 31 | RUNNING 32 | } 33 | state NEOGPS_BF(8); 34 | 35 | uint32_t last_rx; 36 | uint32_t last_trace; 37 | uint32_t last_sentence; 38 | 39 | // Prevent recursive sentence processing while waiting for ACKs 40 | bool ok_to_process; 41 | 42 | MyGPS( Stream *device ) : ubloxGPS( device ) 43 | { 44 | state = GETTING_STATUS; 45 | last_rx = 0L; 46 | last_trace = 0L; 47 | last_sentence = 0L; 48 | ok_to_process = false; 49 | }; 50 | 51 | //-------------------------- 52 | 53 | void begin() 54 | { 55 | last_rx = millis(); 56 | last_trace = seconds; 57 | } 58 | 59 | //-------------------------- 60 | 61 | void run() 62 | { 63 | bool rx = false; 64 | 65 | while (Device()->available()) { 66 | rx = true; 67 | if (decode( Device()->read() ) == DECODE_COMPLETED) { 68 | if (ok_to_process) 69 | processSentence(); 70 | } 71 | } 72 | 73 | uint32_t ms = millis(); 74 | 75 | if (rx) 76 | last_rx = ms; 77 | 78 | else { 79 | if (ok_to_process && ((ms - last_rx) > 2000L)) { 80 | last_rx = ms; 81 | trace << F("RESTART!\n"); 82 | if (state != GETTING_STATUS) { 83 | state = GETTING_STATUS; 84 | enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_STATUS ); 85 | } 86 | } 87 | } 88 | } 89 | 90 | //-------------------------- 91 | 92 | void get_status() 93 | { 94 | static bool acquiring = false; 95 | 96 | if (fix().status == gps_fix::STATUS_NONE) { 97 | if (!acquiring) { 98 | acquiring = true; 99 | trace << F("Acquiring..."); 100 | } else 101 | trace << '.'; 102 | 103 | } else { 104 | if (acquiring) 105 | trace << '\n'; 106 | trace << F("Acquired status: ") << (uint8_t) fix().status << '\n'; 107 | 108 | #if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) & defined(UBLOX_PARSE_TIMEGPS) 109 | if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEGPS )) 110 | trace << F("enable TIMEGPS failed!\n"); 111 | 112 | state = GETTING_LEAP_SECONDS; 113 | #else 114 | start_running(); 115 | state = RUNNING; 116 | #endif 117 | } 118 | } // get_status 119 | 120 | //-------------------------- 121 | 122 | void get_leap_seconds() 123 | { 124 | #if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) & defined(UBLOX_PARSE_TIMEGPS) 125 | if (GPSTime::leap_seconds != 0) { 126 | trace << F("Acquired leap seconds: ") << GPSTime::leap_seconds << '\n'; 127 | 128 | if (!disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEGPS )) 129 | trace << F("disable TIMEGPS failed!\n"); 130 | 131 | #if defined(UBLOX_PARSE_TIMEUTC) 132 | if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC )) 133 | trace << F("enable TIMEUTC failed!\n"); 134 | state = GETTING_UTC; 135 | #else 136 | start_running(); 137 | #endif 138 | } 139 | #endif 140 | } // get_leap_seconds 141 | 142 | //-------------------------- 143 | 144 | void get_utc() 145 | { 146 | #if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) & defined(UBLOX_PARSE_TIMEUTC) 147 | if (GPSTime::start_of_week() != 0) { 148 | trace << F("Acquired UTC: ") << fix().dateTime << '\n'; 149 | trace << F("Acquired Start-of-Week: ") << GPSTime::start_of_week() << '\n'; 150 | 151 | start_running(); 152 | } 153 | #endif 154 | } // get_utc 155 | 156 | //-------------------------- 157 | 158 | void start_running() 159 | { 160 | bool enabled_msg_with_time = false; 161 | 162 | #if (defined(GPS_FIX_LOCATION) | defined(GPS_FIX_ALTITUDE)) & \ 163 | defined(UBLOX_PARSE_POSLLH) 164 | if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_POSLLH )) 165 | trace << F("enable POSLLH failed!\n"); 166 | enabled_msg_with_time = true; 167 | #endif 168 | 169 | #if (defined(GPS_FIX_SPEED) | defined(GPS_FIX_HEADING)) & \ 170 | defined(UBLOX_PARSE_VELNED) 171 | if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_VELNED )) 172 | trace << F("enable VELNED failed!\n"); 173 | enabled_msg_with_time = true; 174 | #endif 175 | 176 | #if defined(NMEAGPS_PARSE_SATELLITES) & \ 177 | defined(UBLOX_PARSE_SVINFO) 178 | if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_SVINFO )) 179 | trace << PSTR("enable SVINFO failed!\n"); 180 | enabled_msg_with_time = true; 181 | #endif 182 | 183 | #if defined(UBLOX_PARSE_TIMEUTC) 184 | #if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) 185 | if (enabled_msg_with_time && 186 | !disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC )) 187 | trace << F("disable TIMEUTC failed!\n"); 188 | 189 | #elif defined(GPS_FIX_TIME) | defined(GPS_FIX_DATE) 190 | // If both aren't defined, we can't convert TOW to UTC, 191 | // so ask for the separate UTC message. 192 | if (!enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC )) 193 | trace << F("enable TIMEUTC failed!\n"); 194 | #endif 195 | #endif 196 | state = RUNNING; 197 | trace_header(); 198 | } 199 | 200 | //-------------------------- 201 | 202 | bool is_new_interval() 203 | { 204 | // See if we stepped into a different time interval, 205 | // or if it has finally become valid after a cold start. 206 | 207 | bool new_interval; 208 | #if defined(GPS_FIX_TIME) 209 | new_interval = (fix().valid.time && 210 | (!merged.valid.time || 211 | (merged.dateTime.seconds != fix().dateTime.seconds) || 212 | (merged.dateTime.minutes != fix().dateTime.minutes) || 213 | (merged.dateTime.hours != fix().dateTime.hours))); 214 | #elif defined(PULSE_PER_DAY) 215 | new_interval = (fix().valid.date && 216 | (!merged.valid.date || 217 | (merged.dateTime.date != fix().dateTime.date) || 218 | (merged.dateTime.month != fix().dateTime.month) || 219 | (merged.dateTime.year != fix().dateTime.year))); 220 | #else 221 | // No date/time configured, so let's assume it's a new 222 | // if the seconds have changed. 223 | new_interval = (seconds != last_sentence); 224 | #endif 225 | return new_interval; 226 | 227 | } // is_new_interval 228 | 229 | //-------------------------- 230 | 231 | bool processSentence() 232 | { 233 | bool old_otp = ok_to_process; 234 | ok_to_process = false; 235 | 236 | bool ok = false; 237 | 238 | if (!ok && (nmeaMessage >= (nmea_msg_t)ubloxGPS::PUBX_00)) 239 | ok = true; 240 | 241 | if (!ok && (rx().msg_class != ublox::UBX_UNK)) { 242 | ok = true; 243 | 244 | // Use the STATUS message as a pulse-per-second 245 | if ((rx().msg_class == ublox::UBX_NAV) && 246 | (rx().msg_id == ublox::UBX_NAV_STATUS)) 247 | seconds++; 248 | } 249 | 250 | if (ok) { 251 | 252 | switch (state) { 253 | case GETTING_STATUS : get_status (); break; 254 | 255 | case GETTING_LEAP_SECONDS: get_leap_seconds(); break; 256 | 257 | case GETTING_UTC : get_utc (); break; 258 | 259 | case RUNNING: 260 | if (is_new_interval()) { 261 | 262 | // Since we're into the next time interval, we throw away 263 | // all of the previous fix and start with what we 264 | // just received. 265 | merged = fix(); 266 | 267 | } else { 268 | // Accumulate all the reports in this time interval 269 | merged |= fix(); 270 | } 271 | break; 272 | } 273 | } 274 | 275 | last_sentence = seconds; 276 | 277 | ok_to_process = old_otp; 278 | 279 | return ok; 280 | } 281 | 282 | //-------------------------- 283 | 284 | void traceIt() 285 | { 286 | if ((state == RUNNING) && (last_trace != 0)) 287 | trace_all( *this, merged ); 288 | 289 | last_trace = seconds; 290 | 291 | } // traceIt 292 | 293 | 294 | } NEOGPS_PACKED; 295 | 296 | // Construct the GPS object and hook it to the appropriate serial device 297 | static MyGPS gps( &Serial1 ); 298 | 299 | //-------------------------- 300 | 301 | void setup() 302 | { 303 | // Start the normal trace output 304 | Serial.begin(9600); 305 | trace << F("ublox binary protocol example started.\n"); 306 | trace << F("fix object size = ") << sizeof(gps.fix()) << '\n'; 307 | trace << F("ubloxGPS object size = ") << sizeof(ubloxGPS) << '\n'; 308 | trace << F("MyGPS object size = ") << sizeof(gps) << '\n'; 309 | trace.flush(); 310 | 311 | // Start the UART for the GPS device 312 | Serial1.begin(9600); 313 | 314 | gps.begin(); 315 | 316 | // Turn off the preconfigured NMEA standard messages 317 | for (uint8_t i=NMEAGPS::NMEA_FIRST_MSG; i<=NMEAGPS::NMEA_LAST_MSG; i++) { 318 | ublox::configNMEA( gps, (NMEAGPS::nmea_msg_t) i, 0 ); 319 | } 320 | 321 | // Turn on the UBX status message 322 | gps.enable_msg( ublox::UBX_NAV, ublox::UBX_NAV_STATUS ); 323 | 324 | // Turn off things that may be left on by a previous build 325 | gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEGPS ); 326 | gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_TIMEUTC ); 327 | gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_VELNED ); 328 | gps.disable_msg( ublox::UBX_NAV, ublox::UBX_NAV_POSLLH ); 329 | 330 | #if 0 331 | // Test a Neo M8 message -- should be rejected by Neo-6 and Neo7 332 | ublox::cfg_nmea_v1_t test; 333 | 334 | test.always_output_pos = false; // invalid or failed 335 | test.output_invalid_pos = false; 336 | test.output_invalid_time= false; 337 | test.output_invalid_date= false; 338 | test.use_GPS_only = false; 339 | test.output_heading = false; // even if frozen 340 | test.__not_used__ = false; 341 | 342 | test.nmea_version = ublox::cfg_nmea_v1_t::NMEA_V_4_0; 343 | test.num_sats_per_talker_id = ublox::cfg_nmea_v1_t::SV_PER_TALKERID_UNLIMITED; 344 | 345 | test.compatibility_mode = false; 346 | test.considering_mode = true; 347 | test.max_line_length_82 = false; 348 | test.__not_used_1__ = 0; 349 | 350 | test.filter_gps = false; 351 | test.filter_sbas = false; 352 | test.__not_used_2__= 0; 353 | test.filter_qzss = false; 354 | test.filter_glonass= false; 355 | test.filter_beidou = false; 356 | test.__not_used_3__= 0; 357 | 358 | test.proprietary_sat_numbering = false; 359 | test.main_talker_id = ublox::cfg_nmea_v1_t::MAIN_TALKER_ID_GP; 360 | test.gsv_uses_main_talker_id = true; 361 | test.beidou_talker_id[0] = 'G'; 362 | test.beidou_talker_id[1] = 'P'; 363 | 364 | trace << F("CFG_NMEA result = ") << gps.send( test ); 365 | #endif 366 | 367 | gps.ok_to_process = true; 368 | } 369 | 370 | //-------------------------- 371 | 372 | void loop() 373 | { 374 | gps.run(); 375 | 376 | if ((gps.last_trace != seconds) && 377 | (millis() - gps.last_rx > 5)) { 378 | 379 | // It's been 5ms since we received anything, 380 | // log what we have so far... 381 | gps.traceIt(); 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /NMEAGPS.h: -------------------------------------------------------------------------------- 1 | #ifndef NMEAGPS_H 2 | #define NMEAGPS_H 3 | 4 | //------------------------------------------------------ 5 | // @file NMEAGPS.h 6 | // @version 2.1 7 | // 8 | // @section License 9 | // Copyright (C) 2014, SlashDevin 10 | // 11 | // This library is free software; you can redistribute it and/or 12 | // modify it under the terms of the GNU Lesser General Public 13 | // License as published by the Free Software Foundation; either 14 | // version 2.1 of the License, or (at your option) any later version. 15 | // 16 | // This library is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | // Lesser General Public License for more details. 20 | // 21 | 22 | #include 23 | 24 | #include "GPSfix.h" 25 | #include "NMEAGPS_cfg.h" 26 | 27 | //------------------------------------------------------ 28 | // 29 | // NMEA 0183 Parser for generic GPS Modules. As bytes are received from 30 | // the device, they affect the internal FSM and set various members 31 | // of the current /fix/. 32 | // 33 | // @section Limitations 34 | // 1) Only these NMEA messages are parsed: 35 | // GGA, GLL, GSA, GST, GSV, RMC, VTG, and ZDA. 36 | // 2) The current `fix` is only safe to access _after_ the complete message 37 | // is parsed and _before_ the next message begins to affect the members. 38 | // If you access `fix` at any other time, /is_safe()/ must be checked. 39 | // Otherwise, you should make a copy of `fix` after a sentence has been 40 | // completely DECODED. 41 | // 42 | 43 | class NMEAGPS 44 | { 45 | NMEAGPS( const NMEAGPS & ); 46 | 47 | public: 48 | 49 | /** 50 | * Constructor 51 | */ 52 | NMEAGPS(); 53 | 54 | /** 55 | * Return type for /decode/. As bytes are processed, they can be 56 | * categorized as INVALID (not part of this protocol), OK (accepted), 57 | * or COMPLETED (end-of-message). 58 | */ 59 | enum decode_t { DECODE_CHR_INVALID, DECODE_CHR_OK, DECODE_COMPLETED }; 60 | 61 | /** 62 | * Process one character of an NMEA GPS sentence. The internal state 63 | * machine tracks what part of the sentence has been received. As the 64 | * tracks what part of the sentence has been received so far. As the 65 | * sentence is received, members of the /fix/ structure are updated. 66 | * @return DECODE_COMPLETED when a sentence has been completely received. 67 | */ 68 | NMEAGPS_VIRTUAL decode_t decode( char c ); 69 | 70 | /** NMEA standard message types. */ 71 | enum nmea_msg_t { 72 | NMEA_UNKNOWN, 73 | NMEA_GGA, 74 | NMEA_GLL, 75 | NMEA_GSA, 76 | NMEA_GST, 77 | NMEA_GSV, 78 | NMEA_RMC, 79 | NMEA_VTG, 80 | NMEA_ZDA, 81 | }; 82 | static const nmea_msg_t NMEA_FIRST_MSG = NMEA_GGA; 83 | static const nmea_msg_t NMEA_LAST_MSG = NMEA_ZDA; 84 | 85 | /** 86 | * Most recent NMEA sentence type received. 87 | */ 88 | enum nmea_msg_t nmeaMessage NEOGPS_BF(8); 89 | 90 | #ifdef NMEAGPS_SAVE_TALKER_ID 91 | char talker_id[2]; 92 | #endif 93 | 94 | #ifdef NMEAGPS_SAVE_MFR_ID 95 | char mfr_id[3]; 96 | #endif 97 | 98 | // Current fix accessor. 99 | // /fix/ will be constantly changing as characters are received. 100 | // For example, fix().longitude() may return nonsense data if 101 | // characters for that field are currently being processed in /decode/. 102 | // /is_safe/ *must* be checked before accessing members of /fix/. 103 | // If you need access to the current /fix/ at any time, you must 104 | // take a snapshot while it is_safe, and then use the snapshot 105 | // later. 106 | 107 | gps_fix & fix() { return m_fix; }; 108 | 109 | // Determine whether the members of /fix/ are "currently" safe. 110 | // It will return true when a complete sentence and the CRC characters 111 | // have been received (or after a CR if no CRC is present). 112 | // It will return false after a sentence's command and comma 113 | // have been received (e.g., "$GPGGA,"). 114 | // If NMEAGPS processes characters in UART interrupts, /is_safe/ 115 | // could change at any time (i.e., it would be /volatile/). 116 | 117 | bool is_safe() const { return safe; } 118 | 119 | // Notes regarding a volatile /fix/: 120 | // 121 | // If an NMEAGPS instance is fed characters from a non-interrupt 122 | // context, the following method is safe: 123 | // 124 | // void loop() 125 | // { 126 | // while (uart.available()) { 127 | // if (gps.decode( uart.getchar() ) == DECODE_COMPLETED) { 128 | // // Got something new! Access only valid members here and/or... 129 | // 130 | // // ...save a snapshot for later 131 | // safe_fix = gps.fix(); 132 | // } 133 | // } 134 | // // Access valid members of /safe_fix/ anywhere, any time. 135 | // } 136 | 137 | #ifdef NMEAGPS_STATS 138 | /** 139 | * Statistics. 140 | */ 141 | struct { 142 | uint32_t ok; // count of successfully parsed sentences 143 | uint32_t crc_errors; // count of CRC errors 144 | } statistics; 145 | #endif 146 | 147 | /** 148 | * Request the specified NMEA sentence. Not all devices will respond. 149 | */ 150 | 151 | static void poll( Stream *device, nmea_msg_t msg ); 152 | 153 | /** 154 | * Send a message to the GPS device. 155 | * The '$' is optional, and the '*' and CS will be added automatically. 156 | */ 157 | 158 | static void send( Stream *device, const char *msg ); 159 | static void send_P( Stream *device, str_P msg ); 160 | 161 | protected: 162 | // Current fix 163 | gps_fix m_fix; 164 | 165 | /** 166 | * Current parser state 167 | */ 168 | uint8_t crc; // accumulated CRC in the sentence 169 | uint8_t fieldIndex; // index of current field in the sentence 170 | uint8_t chrCount; // index of current character in current field 171 | uint8_t decimal; // digits received after the decimal point 172 | struct { 173 | bool negative NEOGPS_BF(1); // field had a leading '-' 174 | bool safe NEOGPS_BF(1); // fix is safe to access 175 | bool comma_needed NEOGPS_BF(1); // field needs a comma to finish parsing 176 | bool group_valid NEOGPS_BF(1); // multi-field group valid 177 | bool proprietary NEOGPS_BF(1); // receiving proprietary message 178 | } NEOGPS_PACKED; 179 | 180 | /* 181 | * Internal FSM states 182 | */ 183 | enum rxState_t { 184 | NMEA_IDLE, // Waiting for initial '$' 185 | NMEA_RECEIVING_HEADER, // Parsing sentence type field 186 | NMEA_RECEIVING_DATA, // Parsing fields up to the terminating '*' 187 | NMEA_RECEIVING_CRC // Receiving two-byte transmitted CRC 188 | }; 189 | static const uint8_t NMEA_FIRST_STATE = NMEA_IDLE; 190 | static const uint8_t NMEA_LAST_STATE = NMEA_RECEIVING_CRC; 191 | 192 | rxState_t rxState NEOGPS_BF(8); 193 | 194 | /* 195 | * Table entry for NMEA sentence type string and its offset 196 | * in enumerated nmea_msg_t. Proprietary sentences can be implemented 197 | * in derived classes by adding a second table. Additional tables 198 | * can be singly-linked through the /previous/ member. The instantiated 199 | * class's table is the head, and should be returned by the derived 200 | * /msg_table/ function. Tables should be sorted alphabetically. 201 | */ 202 | struct msg_table_t { 203 | uint8_t offset; // nmea_msg_t enum starting value 204 | const msg_table_t *previous; 205 | uint8_t size; // number of entries in table array 206 | const char * const *table; // array of NMEA sentence strings 207 | }; 208 | 209 | static const msg_table_t nmea_msg_table __PROGMEM; 210 | 211 | NMEAGPS_VIRTUAL const msg_table_t *msg_table() const 212 | { return &nmea_msg_table; }; 213 | 214 | #ifdef NMEAGPS_PARSE_TALKER_ID 215 | NMEAGPS_VIRTUAL bool parseTalkerID( char chr ) { return true; }; 216 | #endif 217 | 218 | #ifdef NMEAGPS_PARSE_MFR_ID 219 | NMEAGPS_VIRTUAL bool parseMfrID( char chr ) { return true; }; 220 | #endif 221 | 222 | /* 223 | * Use the list of tables to recognize an NMEA sentence type. 224 | */ 225 | decode_t parseCommand( char c ); 226 | 227 | /* 228 | * Depending on the NMEA sentence type, parse one field of the expected type. 229 | */ 230 | NMEAGPS_VIRTUAL bool parseField( char chr ); 231 | 232 | /* 233 | * Parse the primary NMEA field types into /fix/ members. 234 | */ 235 | 236 | bool parseFix( char chr ); // aka STATUS or MODE 237 | bool parseTime( char chr ); 238 | bool parseDDMMYY( char chr ); 239 | bool parseLat( char chr ); 240 | bool parseNS( char chr ); 241 | bool parseLon( char chr ); 242 | bool parseEW( char chr ); 243 | bool parseSpeed( char chr ); 244 | bool parseHeading( char chr ); 245 | bool parseAlt(char chr ); 246 | bool parseHDOP( char chr ); 247 | bool parseVDOP( char chr ); 248 | bool parsePDOP( char chr ); 249 | bool parse_lat_err( char chr ); 250 | bool parse_lon_err( char chr ); 251 | bool parse_alt_err( char chr ); 252 | bool parseSatellites( char chr ); 253 | 254 | // Helper macro for parsing the 4 consecutive fields of a location 255 | #define PARSE_FIELD(i,f) case i: return parse##f( chr ); 256 | #define PARSE_LOC(i) PARSE_FIELD(i,Lat) \ 257 | PARSE_FIELD(i+1,NS); \ 258 | PARSE_FIELD(i+2,Lon); \ 259 | PARSE_FIELD(i+3,EW); 260 | 261 | // Optional SATELLITE VIEW array ----------------------- 262 | #ifdef NMEAGPS_PARSE_SATELLITES 263 | public: 264 | struct satellite_view_t 265 | { 266 | uint8_t id; 267 | #ifdef NMEAGPS_PARSE_SATELLITE_INFO 268 | uint8_t elevation; // 0..99 deg 269 | uint16_t azimuth; // 0..359 deg 270 | uint8_t snr NEOGPS_BF(7); // 0..99 dBHz 271 | bool tracked NEOGPS_BF(1); 272 | #endif 273 | } NEOGPS_PACKED; 274 | 275 | satellite_view_t satellites[ NMEAGPS_MAX_SATELLITES ]; 276 | uint8_t sat_count; 277 | 278 | bool satellites_valid() const { return (sat_count >= m_fix.satellites); } 279 | protected: 280 | #endif 281 | 282 | /** 283 | * Parse floating-point numbers into a /whole_frac/ 284 | * @return true when the value is fully populated. 285 | */ 286 | bool parseFloat( gps_fix::whole_frac & val, char chr, uint8_t max_decimal ); 287 | 288 | /** 289 | * Parse floating-point numbers into a uint16_t 290 | * @return true when the value is fully populated. 291 | */ 292 | bool parseFloat( uint16_t & val, char chr, uint8_t max_decimal ); 293 | 294 | /** 295 | * Parse NMEA lat/lon dddmm.mmmm degrees 296 | * @return true. 297 | */ 298 | bool parseDDDMM( int32_t & val, char chr ); 299 | 300 | /* 301 | * Parse integer into 8-bit int 302 | * @return true when non-empty value 303 | */ 304 | bool parseInt( uint8_t &val, uint8_t chr ) 305 | { 306 | bool is_comma = (chr == ','); 307 | if (chrCount == 0) { 308 | if (is_comma) 309 | return false; // empty field! 310 | val = (chr - '0'); 311 | } else if (!is_comma) 312 | val = (val*10) + (chr - '0'); 313 | return true; 314 | } 315 | 316 | /* 317 | * Parse integer into 16-bit int 318 | * @return true when non-empty value 319 | */ 320 | bool parseInt( uint16_t &val, uint8_t chr ) 321 | { 322 | bool is_comma = (chr == ','); 323 | if (chrCount == 0) { 324 | if (is_comma) 325 | return false; // empty field! 326 | val = (chr - '0'); 327 | } else if (!is_comma) 328 | val = (val*10) + (chr - '0'); 329 | return true; 330 | } 331 | 332 | private: 333 | void sentenceBegin (); 334 | void sentenceOk (); 335 | void sentenceInvalid (); 336 | void sentenceUnrecognized(); 337 | void headerReceived (); 338 | 339 | } NEOGPS_PACKED; 340 | 341 | #endif 342 | -------------------------------------------------------------------------------- /examples/NMEAtest/NMEAtest.ino: -------------------------------------------------------------------------------- 1 | /* 2 | This test program uses one GPGGA sentence to test the parser's: 3 | 1) robustness WRT dropped, inserted, and mangled characters 4 | 2) correctness WRT values extracted from the input stream 5 | 6 | Some care in testing must be taken because 7 | 1) The XOR-style checksum is not very good at catching errors. 8 | 2) The '*' is a special character for delimiting the CRC. If 9 | it is changed, a CR/LF will allow the sentence to pass. 10 | 11 | Serial is for trace output. 12 | */ 13 | 14 | #include 15 | 16 | #include "Streamers.h" 17 | 18 | // Set this to your debug output device. 19 | Stream & trace = Serial; 20 | 21 | #include "NMEAGPS.h" 22 | 23 | /* 24 | * Make sure gpsfix.h and NMEAGPS.h are configured properly. 25 | */ 26 | 27 | #ifndef NMEAGPS_PARSE_GGA 28 | #error NMEAGPS_PARSE_GGA must be defined in NMEAGPS.h! 29 | #endif 30 | 31 | #ifndef GPS_FIX_DATE 32 | #error GPS_FIX_DATE must be defined in gpsfix.h! 33 | #endif 34 | 35 | #ifndef GPS_FIX_TIME 36 | #error GPS_FIX_TIME must be defined in gpsfix.h! 37 | #endif 38 | 39 | #ifndef GPS_FIX_LOCATION 40 | #error GPS_FIX_LOCATION must be defined in gpsfix.h! 41 | #endif 42 | 43 | #ifndef GPS_FIX_ALTITUDE 44 | #error GPS_FIX_ALTITUDE must be defined in gpsfix.h! 45 | #endif 46 | 47 | #ifndef GPS_FIX_SPEED 48 | #error GPS_FIX_SPEED must be defined in gpsfix.h! 49 | #endif 50 | 51 | #ifndef GPS_FIX_HEADING 52 | #error GPS_FIX_HEADING must be defined in gpsfix.h! 53 | #endif 54 | 55 | #ifndef GPS_FIX_SATELLITES 56 | #error GPS_FIX_SATELLITES must be defined in gpsfix.h! 57 | #endif 58 | 59 | #ifndef GPS_FIX_HDOP 60 | #error GPS_FIX_HDOP must be defined in gpsfix.h! 61 | #endif 62 | 63 | static NMEAGPS gps; 64 | 65 | //-------------------------- 66 | // Example sentences 67 | 68 | const char validGGA[] __PROGMEM = 69 | "$GPGGA,092725.00,4717.11399,N,00833.91590,E," 70 | "1,8,1.01,499.6,M,48.0,M,,0*5B\r\n"; 71 | 72 | // Ayers Rock 73 | // -25.3448688,131.0324914 74 | // 2520.692128,S,13101.949484,E 75 | const char validRMC[] __PROGMEM = 76 | "$GPRMC,092725.00,A,2520.69213,S,13101.94948,E," 77 | "0.004,77.52,091202,,,A*43\r\n"; 78 | 79 | // Macchu Picchu 80 | // -13.162805, -72.545508 81 | // 13.162805,S,72.545508,W 82 | // 1309.7683,S,7232.7305,W 83 | 84 | const char validGGA2[] __PROGMEM = 85 | "$GPGGA,162254.00,1309.7683,S,7232.7305,W," 86 | "1,03,2.36,2430.2,M,-25.6,M,,*7E\r\n"; 87 | 88 | // Dexter MO 89 | // 36.794405, -89.958655 90 | // 36.794405,N,89.958655,W 91 | // 3647.6643,N,8957.5193,W 92 | 93 | const char validRMC2[] __PROGMEM = 94 | "$GPRMC,162254.00,A,3647.6643,N,8957.5193,W,0.820,188.36,110706,,,A*49\r\n"; 95 | 96 | const char mtk1[] __PROGMEM = 97 | "$GPGGA,064951.000,2307.1256,N,12016.4438,E,1,8,0.95,39.9,M,17.8,M,,*63\r\n"; 98 | const char mtk2[] __PROGMEM = 99 | "$GPRMC,064951.000,A,2307.1256,N,12016.4438,E,0.03,165.48,260406,3.05,W,A*2C\r\n"; 100 | const char mtk3[] __PROGMEM = 101 | "$GPVTG,165.48,T,,M,0.03,N,0.06,K,A*36\r\n"; 102 | const char mtk4[] __PROGMEM = 103 | "$GPGSA,A,3,29,21,26,15,18,09,06,10,,,,,2.32,0.95,2.11*00\r\n"; 104 | const char mtk5[] __PROGMEM = 105 | "$GPGSV,3,1,09,29,36,029,42,21,46,314,43,26,44,020,43,15,21,321,39*7D\r\n"; 106 | const char mtk6[] __PROGMEM = 107 | "$GPGSV,3,2,09,18,26,314,40,09,57,170,44,06,20,229,37,10,26,084,37*77\r\n"; 108 | const char mtk7[] __PROGMEM = 109 | "$GPGSV,3,3,09,07,,,26*73\r\n"; 110 | const char mtk8[] __PROGMEM = 111 | "$GNGST,082356.00,1.8,,,,1.7,1.3,2.2*60\r\n"; 112 | const char mtk9[] __PROGMEM = 113 | "$GNRMC,083559.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A,V*33\r\n"; 114 | const char mtk10[] __PROGMEM = 115 | "$GNGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*45\r\n"; 116 | 117 | const char fpGGA1[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.8169,W," 118 | "1,8,1.01,499.6,M,48.0,M,,0*49\r\n"; 119 | const char fpGGA2[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.8170,W," 120 | "1,8,1.01,499.6,M,48.0,M,,0*41\r\n"; 121 | const char fpGGA3[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.8171,W," 122 | "1,8,1.01,499.6,M,48.0,M,,0*40\r\n"; 123 | const char fpGGA4[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.8172,W," 124 | "1,8,1.01,499.6,M,48.0,M,,0*43\r\n"; 125 | const char fpGGA5[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.8173,W," 126 | "1,8,1.01,499.6,M,48.0,M,,0*42\r\n"; 127 | const char fpGGA6[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.8174,W," 128 | "1,8,1.01,499.6,M,48.0,M,,0*45\r\n"; 129 | const char fpGGA7[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.8175,W," 130 | "1,8,1.01,499.6,M,48.0,M,,0*44\r\n"; 131 | const char fpGGA8[] __PROGMEM = "$GPGGA,092725.00,3242.9000,N,11705.8176,W," 132 | "1,8,1.01,499.6,M,48.0,M,,0*47\r\n"; 133 | 134 | //-------------------------- 135 | 136 | static bool parse_P( const char *ptr ) 137 | { 138 | bool decoded = false; 139 | char c; 140 | 141 | gps.fix().init(); 142 | while ( (c = pgm_read_byte( ptr++ )) != '\0' ) { 143 | if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) { 144 | decoded = true; 145 | } 146 | } 147 | 148 | return decoded; 149 | } 150 | 151 | //-------------------------- 152 | 153 | static void traceSample( const char *ptr ) 154 | { 155 | trace << F("Input: ") << (const __FlashStringHelper *) ptr; 156 | 157 | bool decoded = parse_P( ptr ); 158 | 159 | if (decoded) 160 | trace << F("Results: "); 161 | else 162 | trace << F("Failed to decode! "); 163 | 164 | trace_all( gps, gps.fix() ); 165 | trace << '\n'; 166 | } 167 | 168 | //-------------------------- 169 | 170 | static uint8_t passed = 0; 171 | static uint8_t failed = 0; 172 | 173 | static void checkLatLon 174 | ( const char *msg, NMEAGPS::nmea_msg_t msg_type, int32_t lat, int32_t lon ) 175 | { 176 | const char *ptr = msg; 177 | for (;;) { 178 | char c = pgm_read_byte( ptr++ ); 179 | if (!c) { 180 | trace.print( F("FAILED to parse \"") ); 181 | trace.print( (const __FlashStringHelper *) msg ); 182 | trace.println( F("\"\n") ); 183 | failed++; 184 | break; 185 | } 186 | if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) { 187 | 188 | if (gps.nmeaMessage != msg_type) { 189 | trace.print( F("FAILED wrong message type ") ); 190 | trace.println( gps.nmeaMessage ); 191 | failed++; 192 | break; 193 | } 194 | if (gps.fix().latitudeL() != lat) { 195 | trace.print( F("FAILED wrong latitude ") ); 196 | trace.println( gps.fix().latitudeL() ); 197 | failed++; 198 | break; 199 | } 200 | if (gps.fix().longitudeL() != lon) { 201 | trace.print( F("FAILED wrong longitude ") ); 202 | trace.println( gps.fix().longitudeL() ); 203 | failed++; 204 | break; 205 | } 206 | 207 | passed++; 208 | break; 209 | } 210 | } 211 | } 212 | 213 | //-------------------------- 214 | 215 | void setup() 216 | { 217 | // Start the normal trace output 218 | Serial.begin(9600); 219 | trace.print( F("NMEA test: started\n") ); 220 | trace.print( F("fix object size = ") ); 221 | trace.println( sizeof(gps.fix()) ); 222 | trace.print( F("NMEAGPS object size = ") ); 223 | trace.println( sizeof(NMEAGPS) ); 224 | trace.flush(); 225 | 226 | 227 | // Some basic rejection tests 228 | for (uint16_t c=0; c < 256; c++) { 229 | if (c != '$') { 230 | if (NMEAGPS::DECODE_CHR_INVALID != gps.decode( (char)c )) { 231 | trace.print( F("FAILED to reject single character ") ); 232 | trace.println( c ); 233 | failed++; 234 | return; 235 | } 236 | } 237 | } 238 | passed++; 239 | 240 | for (uint16_t i=0; i < 256; i++) { 241 | if (NMEAGPS::DECODE_COMPLETED == gps.decode( '$' )) { 242 | trace.print( F("FAILED to reject multiple '$' characters\n") ); 243 | failed++; 244 | return; 245 | } 246 | } 247 | passed++; 248 | 249 | uint16_t validGGA_len = 0; 250 | 251 | // Insert a ' ' at each position of the test sentence 252 | uint16_t insert_at = 1; 253 | do { 254 | const char *ptr = validGGA; 255 | uint8_t j = 0; 256 | for (;;) { 257 | if (j++ == insert_at) { 258 | if (NMEAGPS::DECODE_COMPLETED == gps.decode( ' ' )) { 259 | trace.print( F("FAILED inserting ' ' @ pos ") ); 260 | trace.println( insert_at ); 261 | failed++; 262 | return; 263 | } 264 | } 265 | char c = pgm_read_byte( ptr++ ); 266 | if (!c) { 267 | if (validGGA_len == 0) { 268 | validGGA_len = j-1; 269 | trace.print( F("Test string length = ") ); 270 | trace.println( validGGA_len ); 271 | } 272 | break; 273 | } 274 | if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) { 275 | trace.print( F("FAILED inserting @ pos ") ); 276 | trace.println( insert_at ); 277 | failed++; 278 | return; 279 | } 280 | } 281 | } while (++insert_at < validGGA_len-2); 282 | passed++; 283 | 284 | // Drop one character from each position in example sentence 285 | for (uint16_t i=0; i < validGGA_len-3; i++) { 286 | const char *ptr = validGGA; 287 | uint8_t j = 0; 288 | char dropped = 0; 289 | for (;;) { 290 | char c = pgm_read_byte( ptr++ ); 291 | if (!c || (c == '*')) break; 292 | if (j == i) dropped = c; 293 | if ((j++ != i) && (gps.decode( c ) == NMEAGPS::DECODE_COMPLETED)) { 294 | trace.print( F("FAILED dropping '") ); 295 | trace << dropped; 296 | trace.print( F("' at pos ") ); 297 | trace.println( i ); 298 | failed++; 299 | break; 300 | //return; 301 | } 302 | } 303 | } 304 | passed++; 305 | 306 | // Mangle one character from each position in example sentence 307 | for (uint16_t i=0; i < validGGA_len-3; i++) { 308 | const char *ptr = validGGA; 309 | uint8_t j = 0; 310 | char replaced = 0; 311 | for (;;) { 312 | char c = pgm_read_byte( ptr++ ); 313 | if (!c || (c == '*')) break; 314 | if (j++ == i) 315 | replaced = c++; // mangle means increment 316 | if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) { 317 | trace.print( F("FAILED replacing '") ); 318 | trace << (uint8_t) replaced; 319 | trace.print( F("' with '") ); 320 | trace << (uint8_t) (replaced+1); 321 | trace.print( F("' at pos ") ); 322 | trace.println( i ); 323 | failed++; 324 | break; 325 | //return; 326 | } 327 | } 328 | } 329 | passed++; 330 | 331 | // Verify that exact values are extracted 332 | { 333 | const char *ptr = validGGA; 334 | for (;;) { 335 | char c = pgm_read_byte( ptr++ ); 336 | if (!c) { 337 | trace.print( F("FAILED to parse \"") ); 338 | trace.print( (str_P) validGGA ); 339 | trace.println( F("\"\n") ); 340 | failed++; 341 | break; 342 | } 343 | if (NMEAGPS::DECODE_COMPLETED == gps.decode( c )) { 344 | gps_fix expected; 345 | expected.dateTime.parse( PSTR("2002-12-09 09:27:25") ); 346 | expected.dateTime_cs = 0; 347 | 348 | if (gps.nmeaMessage != NMEAGPS::NMEA_GGA) { 349 | trace.print( F("FAILED wrong message type ") ); 350 | trace.println( gps.nmeaMessage ); 351 | failed++; 352 | break; 353 | } 354 | if ((gps.fix().dateTime.hours != expected.dateTime.hours ) || 355 | (gps.fix().dateTime.minutes != expected.dateTime.minutes) || 356 | (gps.fix().dateTime.seconds != expected.dateTime.seconds) || 357 | (gps.fix().dateTime_cs != expected.dateTime_cs)) { 358 | trace << F("FAILED wrong time ") << gps.fix().dateTime << '.' << gps.fix().dateTime_cs << F(" != ") << expected.dateTime << '.' << expected.dateTime_cs << '\n'; 359 | failed++; 360 | break; 361 | } 362 | if (gps.fix().latitudeL() != 472852332L) { 363 | trace.print( F("FAILED wrong latitude ") ); 364 | trace.println( gps.fix().latitudeL() ); 365 | failed++; 366 | break; 367 | } 368 | if (gps.fix().longitudeL() != 85652650L) { 369 | trace.print( F("FAILED wrong longitude ") ); 370 | trace.println( gps.fix().longitudeL() ); 371 | failed++; 372 | break; 373 | } 374 | if (gps.fix().status != gps_fix::STATUS_STD) { 375 | trace.print( F("FAILED wrong status ") ); 376 | trace.println( gps.fix().status ); 377 | failed++; 378 | break; 379 | } 380 | if (gps.fix().satellites != 8) { 381 | trace.print( F("FAILED wrong satellites ") ); 382 | trace.println( gps.fix().satellites ); 383 | failed++; 384 | break; 385 | } 386 | if (gps.fix().hdop != 1010) { 387 | trace.print( F("FAILED wrong HDOP ") ); 388 | trace.println( gps.fix().hdop ); 389 | failed++; 390 | break; 391 | } 392 | if (gps.fix().altitude_cm() != 49960) { 393 | trace.print( F("FAILED wrong altitude ") ); 394 | trace.println( gps.fix().longitudeL() ); 395 | failed++; 396 | break; 397 | } 398 | break; 399 | } 400 | } 401 | } 402 | passed++; 403 | 404 | checkLatLon( validRMC , NMEAGPS::NMEA_RMC, -253448688L, 1310324913L ); 405 | checkLatLon( validGGA2, NMEAGPS::NMEA_GGA, -131628050L, -725455083L ); 406 | checkLatLon( validRMC2, NMEAGPS::NMEA_RMC, 367944050L, -899586550L ); 407 | } 408 | 409 | //-------------------------- 410 | 411 | void loop() 412 | { 413 | trace.print( F("PASSED ") ); 414 | trace << passed; 415 | trace.println( F(" tests.") ); 416 | 417 | if (failed) { 418 | trace.print( F("FAILED ") ); 419 | trace << failed; 420 | trace.println( F(" tests.") ); 421 | } else { 422 | trace << F("------ Samples ------\nResults format:\n "); 423 | trace_header(); 424 | trace << '\n'; 425 | 426 | #ifdef NMEAGPS_STATS 427 | gps.statistics.ok = 0L; 428 | gps.statistics.crc_errors = 0L; 429 | #endif 430 | 431 | traceSample( validGGA ); 432 | traceSample( validRMC ); 433 | traceSample( mtk1 ); 434 | traceSample( mtk2 ); 435 | traceSample( mtk3 ); 436 | traceSample( mtk4 ); 437 | traceSample( mtk5 ); 438 | traceSample( mtk6 ); 439 | traceSample( mtk7 ); 440 | traceSample( mtk8 ); 441 | traceSample( mtk9 ); 442 | traceSample( mtk10 ); 443 | 444 | /** 445 | * This next section displays incremental longitudes. 446 | * If you have defined USE_FLOAT in Streamers.cpp, this will show 447 | * how the conversion to /float/ causes loss of accuracy compared 448 | * to the /uint32_t/ values. 449 | */ 450 | trace << F("--- floating point conversion tests ---\n\n"); 451 | 452 | traceSample( fpGGA1 ); 453 | traceSample( fpGGA2 ); 454 | traceSample( fpGGA3 ); 455 | traceSample( fpGGA4 ); 456 | traceSample( fpGGA5 ); 457 | traceSample( fpGGA6 ); 458 | traceSample( fpGGA7 ); 459 | traceSample( fpGGA8 ); 460 | } 461 | 462 | for (;;); 463 | } 464 | -------------------------------------------------------------------------------- /ublox/ubxmsg.h: -------------------------------------------------------------------------------- 1 | #ifndef UBXMSG_H 2 | #define UBXMSG_H 3 | 4 | #include "NMEAGPS.h" 5 | class ubloxGPS; 6 | 7 | namespace ublox { 8 | 9 | enum msg_class_t 10 | { UBX_NAV = 0x01, // Navigation results 11 | UBX_RXM = 0x02, // Receiver Manager messages 12 | UBX_INF = 0x04, // Informational messages 13 | UBX_ACK = 0x05, // ACK/NAK replies to CFG messages 14 | UBX_CFG = 0x06, // Configuration input messages 15 | UBX_MON = 0x0A, // Monitoring messages 16 | UBX_AID = 0x0B, // Assist Now aiding messages 17 | UBX_TIM = 0x0D, // Timing messages 18 | UBX_NMEA = 0xF0, // NMEA Standard messages 19 | UBX_UNK = 0xFF 20 | } __attribute__((packed)); 21 | 22 | enum msg_id_t 23 | { 24 | UBX_ACK_NAK = 0x00, // Reply to CFG messages 25 | UBX_ACK_ACK = 0x01, // Reply to CFG messages 26 | UBX_CFG_MSG = 0x01, // Configure which messages to send 27 | UBX_CFG_RATE = 0x08, // Configure message rate 28 | UBX_CFG_NMEA = 0x17, // Configure NMEA protocol 29 | UBX_CFG_NAV5 = 0x24, // Configure navigation engine settings 30 | UBX_MON_VER = 0x04, // Monitor Receiver/Software version 31 | UBX_NAV_POSLLH = 0x02, // Current Position 32 | UBX_NAV_STATUS = 0x03, // Receiver Navigation Status 33 | UBX_NAV_VELNED = 0x12, // Current Velocity 34 | UBX_NAV_TIMEGPS = 0x20, // Current GPS Time 35 | UBX_NAV_TIMEUTC = 0x21, // Current UTC Time 36 | UBX_NAV_SVINFO = 0x30, // Space Vehicle Information 37 | UBX_ID_UNK = 0xFF 38 | } __attribute__((packed)); 39 | 40 | struct msg_hdr_t { 41 | msg_class_t msg_class; 42 | msg_id_t msg_id; 43 | bool same_kind( const msg_hdr_t & msg ) const volatile 44 | { return (msg_class == msg.msg_class) && (msg_id == msg.msg_id); } 45 | } __attribute__((packed)); 46 | 47 | struct msg_t : msg_hdr_t { 48 | uint16_t length; // should be sizeof(this)-sizeof(msg+hdr_t) 49 | #define UBX_MSG_LEN(msg) (sizeof(msg) - sizeof(ublox::msg_t)) 50 | 51 | msg_t() 52 | { 53 | length = 0; 54 | }; 55 | msg_t( enum msg_class_t m, enum msg_id_t i, uint16_t l = 0 ) 56 | { 57 | msg_class = m; 58 | msg_id = i; 59 | length = l; 60 | } 61 | } __attribute__((packed)); 62 | 63 | /** 64 | * Configure message intervals. 65 | */ 66 | 67 | enum ubx_nmea_msg_t { // msg_id's for UBX_NMEA msg_class 68 | UBX_GPGGA = 0x00, 69 | UBX_GPGLL = 0x01, 70 | UBX_GPGSA = 0x02, 71 | UBX_GPGSV = 0x03, 72 | UBX_GPRMC = 0x04, 73 | UBX_GPVTG = 0x05, 74 | UBX_GPZDA = 0x08 75 | } __attribute__((packed)); 76 | 77 | struct cfg_msg_t : msg_t { 78 | msg_class_t cfg_msg_class; 79 | msg_id_t cfg_msg; 80 | uint8_t rate; 81 | 82 | cfg_msg_t( msg_class_t m, msg_id_t i, uint8_t r ) 83 | : msg_t( UBX_CFG, UBX_CFG_MSG, UBX_MSG_LEN(*this) ) 84 | { 85 | cfg_msg_class = m; 86 | cfg_msg = i; 87 | rate = r; 88 | }; 89 | } __attribute__((packed)); 90 | 91 | extern bool configNMEA( ubloxGPS &gps, NMEAGPS::nmea_msg_t msgType, uint8_t rate ); 92 | 93 | 94 | // Configure navigation rate 95 | enum time_ref_t { 96 | UBX_TIME_REF_UTC=0, 97 | UBX_TIME_REF_GPS=1 98 | } __attribute__((packed)); 99 | 100 | struct cfg_rate_t : msg_t { 101 | uint16_t GPS_meas_rate; 102 | uint16_t nav_rate; 103 | enum time_ref_t time_ref:16; 104 | 105 | cfg_rate_t( uint16_t gr, uint16_t nr, enum time_ref_t tr ) 106 | : msg_t( UBX_CFG, UBX_CFG_RATE, UBX_MSG_LEN(*this) ) 107 | { 108 | GPS_meas_rate = gr; 109 | nav_rate = nr; 110 | time_ref = tr; 111 | } 112 | } __attribute__((packed)); 113 | 114 | // Navigation Engine Expert Settings 115 | enum dyn_model_t { 116 | UBX_DYN_MODEL_PORTABLE = 0, 117 | UBX_DYN_MODEL_STATIONARY = 2, 118 | UBX_DYN_MODEL_PEDESTRIAN = 3, 119 | UBX_DYN_MODEL_AUTOMOTIVE = 4, 120 | UBX_DYN_MODEL_SEA = 5, 121 | UBX_DYN_MODEL_AIR_1G = 6, 122 | UBX_DYN_MODEL_AIR_2G = 7, 123 | UBX_DYN_MODEL_AIR_4G = 8 124 | } __attribute__((packed)); 125 | 126 | enum position_fix_t { 127 | UBX_POS_FIX_2D_ONLY = 1, 128 | UBX_POS_FIX_3D_ONLY = 2, 129 | UBX_POS_FIX_AUTO = 3 130 | } __attribute__((packed)); 131 | 132 | struct cfg_nav5_t : msg_t { 133 | struct parameter_mask_t { 134 | bool dyn_model :1; 135 | bool min_elev :1; 136 | bool fix :1; 137 | bool dr_limit :1; 138 | bool pos_mask :1; 139 | bool time_mask :1; 140 | bool static_hold_thr :1; 141 | bool dgps_timeout :1; 142 | int _unused_ :8; 143 | } __attribute__((packed)); 144 | 145 | union { 146 | struct parameter_mask_t apply; 147 | uint16_t apply_word; 148 | } __attribute__((packed)); 149 | 150 | enum dyn_model_t dyn_model:8; 151 | enum position_fix_t fix_mode:8; 152 | int32_t fixed_alt; // m MSL x0.01 153 | uint32_t fixed_alt_variance; // m^2 x0.0001 154 | int8_t min_elev; // deg 155 | uint8_t dr_limit; // s 156 | uint16_t pos_dop_mask; // x0.1 157 | uint16_t time_dop_mask; // x0.1 158 | uint16_t pos_acc_mask; // m 159 | uint16_t time_acc_mask; // m 160 | uint8_t static_hold_thr; // cm/s 161 | uint8_t dgps_timeout; // s 162 | uint32_t always_zero_1; 163 | uint32_t always_zero_2; 164 | uint32_t always_zero_3; 165 | 166 | cfg_nav5_t() : msg_t( UBX_CFG, UBX_CFG_NAV5, UBX_MSG_LEN(*this) ) 167 | { 168 | apply_word = 0xFF00; 169 | always_zero_1 = 170 | always_zero_2 = 171 | always_zero_3 = 0; 172 | } 173 | 174 | } __attribute__((packed)); 175 | 176 | // Geodetic Position Solution 177 | struct nav_posllh_t : msg_t { 178 | uint32_t time_of_week; // mS 179 | int32_t lon; // deg * 1e7 180 | int32_t lat; // deg * 1e7 181 | int32_t height_above_ellipsoid; // mm 182 | int32_t height_MSL; // mm 183 | uint32_t horiz_acc; // mm 184 | uint32_t vert_acc; // mm 185 | 186 | nav_posllh_t() : msg_t( UBX_NAV, UBX_NAV_POSLLH, UBX_MSG_LEN(*this) ) {}; 187 | } __attribute__((packed)); 188 | 189 | // Receiver Navigation Status 190 | struct nav_status_t : msg_t { 191 | uint32_t time_of_week; // mS 192 | enum status_t { 193 | NAV_STAT_NONE, 194 | NAV_STAT_DR_ONLY, 195 | NAV_STAT_2D, 196 | NAV_STAT_3D, 197 | NAV_STAT_GPS_DR, 198 | NAV_STAT_TIME_ONLY 199 | } __attribute__((packed)) 200 | status; 201 | 202 | struct flags_t { 203 | bool gps_fix:1; 204 | bool diff_soln:1; 205 | bool week:1; 206 | bool time_of_week:1; 207 | } __attribute__((packed)) 208 | flags; 209 | 210 | static gps_fix::status_t to_status( enum status_t status, flags_t flags ) 211 | { 212 | if (!flags.gps_fix) 213 | return gps_fix::STATUS_NONE; 214 | if (flags.diff_soln) 215 | return gps_fix::STATUS_DGPS; 216 | switch (status) { 217 | case NAV_STAT_DR_ONLY : return gps_fix::STATUS_EST; 218 | case NAV_STAT_2D : 219 | case NAV_STAT_3D : 220 | case NAV_STAT_GPS_DR : return gps_fix::STATUS_STD; 221 | case NAV_STAT_TIME_ONLY: return gps_fix::STATUS_TIME_ONLY; 222 | default : return gps_fix::STATUS_NONE; 223 | } 224 | } 225 | 226 | struct { 227 | bool dgps_input:1; 228 | bool _skip_:6; 229 | bool map_matching:1; 230 | } __attribute__((packed)) 231 | fix_status; 232 | 233 | enum { 234 | PSM_ACQUISITION, 235 | PSM_TRACKING, 236 | PSM_POWER_OPTIMIZED_TRACKING, 237 | PSM_INACTIVE 238 | } 239 | power_safe:2; // FW > v7.01 240 | 241 | uint32_t time_to_first_fix; // ms time tag 242 | uint32_t uptime; // ms since startup/reset 243 | 244 | nav_status_t() : msg_t( UBX_NAV, UBX_NAV_STATUS, UBX_MSG_LEN(*this) ) {}; 245 | } __attribute__((packed)); 246 | 247 | // Velocity Solution in North/East/Down 248 | struct nav_velned_t : msg_t { 249 | uint32_t time_of_week; // mS 250 | int32_t vel_north; // cm/s 251 | int32_t vel_east; // cm/s 252 | int32_t vel_down; // cm/s 253 | uint32_t speed_3D; // cm/s 254 | uint32_t speed_2D; // cm/s 255 | int32_t heading; // degrees * 1e5 256 | uint32_t speed_acc; // cm/s 257 | uint32_t heading_acc; // degrees * 1e5 258 | 259 | nav_velned_t() : msg_t( UBX_NAV, UBX_NAV_VELNED, UBX_MSG_LEN(*this) ) {}; 260 | } __attribute__((packed)); 261 | 262 | // GPS Time Solution 263 | struct nav_timegps_t : msg_t { 264 | uint32_t time_of_week; // mS 265 | int32_t fractional_ToW; // nS 266 | int16_t week; 267 | int8_t leap_seconds; // GPS-UTC 268 | struct valid_t { 269 | bool time_of_week:1; 270 | bool week:1; 271 | bool leap_seconds:1; 272 | } __attribute__((packed)) 273 | valid; 274 | 275 | nav_timegps_t() : msg_t( UBX_NAV, UBX_NAV_TIMEGPS, UBX_MSG_LEN(*this) ) {}; 276 | } __attribute__((packed)); 277 | 278 | // UTC Time Solution 279 | struct nav_timeutc_t : msg_t { 280 | uint32_t time_of_week; // mS 281 | uint32_t time_accuracy; // nS 282 | int32_t fractional_ToW; // nS 283 | uint16_t year; // 1999..2099 284 | uint8_t month; // 1..12 285 | uint8_t day; // 1..31 286 | uint8_t hour; // 0..23 287 | uint8_t minute; // 0..59 288 | uint8_t second; // 0..59 289 | struct valid_t { 290 | bool time_of_week:1; 291 | bool week_number:1; 292 | bool UTC:1; 293 | } __attribute__((packed)) 294 | valid; 295 | 296 | nav_timeutc_t() : msg_t( UBX_NAV, UBX_NAV_TIMEUTC, UBX_MSG_LEN(*this) ) {}; 297 | } __attribute__((packed)); 298 | 299 | // Space Vehicle Information 300 | struct nav_svinfo_t : msg_t { 301 | uint32_t time_of_week; // mS 302 | uint8_t num_channels; 303 | enum { ANTARIS_OR_4, UBLOX_5, UBLOX_6 } chipgen:8; 304 | uint16_t reserved2; 305 | struct sv_t { 306 | uint8_t channel; // 255 = no channel 307 | uint8_t id; // Satellite ID 308 | bool used_for_nav:1; 309 | bool diff_corr :1; // differential correction available 310 | bool orbit_avail :1; // orbit info available 311 | bool orbit_eph :1; // orbit info is ephemeris 312 | bool unhealthy :1; // SV should not be used 313 | bool orbit_alm :1; // orbit info is Almanac Plus 314 | bool orbit_AOP :1; // orbit info is AssistNow Autonomous 315 | bool smoothed :1; // Carrier smoothed pseudorange used 316 | enum { 317 | IDLE, 318 | SEARCHING, 319 | ACQUIRED, 320 | UNUSABLE, 321 | CODE_LOCK, 322 | CODE_AND_CARRIER_LOCK_1, 323 | CODE_AND_CARRIER_LOCK_2, 324 | CODE_AND_CARRIER_LOCK_3 325 | } 326 | quality:8; 327 | uint8_t snr; // dbHz 328 | uint8_t elevation; // degrees 329 | uint16_t azimuth; // degrees 330 | uint32_t pr_res; // pseudo range residual in cm 331 | }; 332 | 333 | // Calculate the number of bytes required to hold the 334 | // specified number of channels. 335 | static uint16_t size_for( uint8_t channels ) 336 | { return sizeof(nav_svinfo_t) + (uint16_t)channels * sizeof(sv_t); } 337 | 338 | // Initialze the msg_hdr for the specified number of channels 339 | void init( uint8_t max_channels ) 340 | { 341 | msg_class = UBX_NAV; 342 | msg_id = UBX_NAV_SVINFO; 343 | length = size_for( max_channels ) - sizeof(ublox::msg_t); 344 | } 345 | 346 | } __attribute__((packed)); 347 | 348 | struct cfg_nmea_t : msg_t { 349 | bool always_output_pos :1; // invalid or failed 350 | bool output_invalid_pos :1; 351 | bool output_invalid_time:1; 352 | bool output_invalid_date:1; 353 | bool use_GPS_only :1; 354 | bool output_heading :1; // even if frozen 355 | bool __not_used__ :2; 356 | enum { 357 | NMEA_V_2_1 = 0x21, 358 | NMEA_V_2_3 = 0x23, 359 | NMEA_V_4_0 = 0x40, // Neo M8 family only 360 | NMEA_V_4_1 = 0x41 // Neo M8 family only 361 | } 362 | nmea_version : 8; 363 | enum { 364 | SV_PER_TALKERID_UNLIMITED = 0, 365 | SV_PER_TALKERID_8 = 8, 366 | SV_PER_TALKERID_12 = 12, 367 | SV_PER_TALKERID_16 = 16 368 | } 369 | num_sats_per_talker_id : 8; 370 | bool compatibility_mode:1; 371 | bool considering_mode :1; 372 | bool max_line_length_82:1; // Neo M8 family only 373 | uint8_t __not_used_1__ :5; 374 | 375 | cfg_nmea_t() : msg_t( UBX_CFG, UBX_CFG_NMEA, UBX_MSG_LEN(*this) ) {}; 376 | 377 | } __attribute__((packed)); 378 | 379 | struct cfg_nmea_v1_t : cfg_nmea_t { 380 | bool filter_gps :1; 381 | bool filter_sbas :1; 382 | uint8_t __not_used_2__:2; 383 | bool filter_qzss :1; 384 | bool filter_glonass:1; 385 | bool filter_beidou :1; 386 | uint32_t __not_used_3__:25; 387 | 388 | bool proprietary_sat_numbering; // for non-NMEA satellites 389 | enum { 390 | MAIN_TALKER_ID_COMPUTED, 391 | MAIN_TALKER_ID_GP, 392 | MAIN_TALKER_ID_GL, 393 | MAIN_TALKER_ID_GN, 394 | MAIN_TALKER_ID_GA, 395 | MAIN_TALKER_ID_GB 396 | } 397 | main_talker_id : 8; 398 | bool gsv_uses_main_talker_id; // false means COMPUTED 399 | enum cfg_nmea_version_t { 400 | CFG_NMEA_V_0, // length = 12 401 | CFG_NMEA_V_1 // length = 20 (default) 402 | } 403 | version : 8; 404 | 405 | // Remaining fields are CFG_NMEA_V_1 only! 406 | char beidou_talker_id[2]; // NULs mean default 407 | uint8_t __reserved__[6]; 408 | 409 | cfg_nmea_v1_t( cfg_nmea_version_t v = CFG_NMEA_V_1 ) 410 | { 411 | version = v; 412 | if (version == CFG_NMEA_V_0) 413 | length = 12; 414 | else { 415 | length = 20; 416 | for (uint8_t i=0; i<8;) // fills 'reserved' too 417 | beidou_talker_id[i++] = 0; 418 | } 419 | }; 420 | 421 | } __attribute__((packed)); 422 | 423 | }; 424 | 425 | #endif -------------------------------------------------------------------------------- /ublox/ubxGPS.cpp: -------------------------------------------------------------------------------- 1 | #include "ubxGPS.h" 2 | #include "Streamers.h" 3 | 4 | using namespace ublox; 5 | 6 | //---------------------------------- 7 | 8 | void ubloxGPS::rxBegin() 9 | { 10 | m_rx_msg.init(); 11 | storage = (msg_t *) NULL; 12 | chrCount = 0; 13 | } 14 | 15 | bool ubloxGPS::rxEnd() 16 | { 17 | safe = true; 18 | 19 | bool visible_msg = false; 20 | 21 | if (rx().msg_class == UBX_ACK) { 22 | 23 | if (ack_expected && ack_same_as_sent) { 24 | if (rx().msg_id == UBX_ACK_ACK) 25 | ack_received = true; 26 | else if (rx().msg_id == UBX_ACK_NAK) 27 | nak_received = true; 28 | ack_expected = false; 29 | } 30 | 31 | } else if (rx().msg_class != UBX_UNK) { 32 | 33 | #ifdef NMEAGPS_STATS 34 | statistics.ok++; 35 | #endif 36 | 37 | visible_msg = true; 38 | //if (!visible_msg) trace << F("XXX"); 39 | 40 | if (storage) { 41 | if (reply_expected && (storage == reply)) { 42 | reply_expected = false; 43 | reply_received = true; 44 | reply = (msg_t *) NULL; 45 | visible_msg = false; 46 | } else { 47 | storage->msg_class = rx().msg_class; 48 | storage->msg_id = rx().msg_id; 49 | if (storage->length > rx().length) 50 | storage->length = rx().length; 51 | } 52 | storage = (msg_t *) NULL; 53 | } 54 | } 55 | 56 | return visible_msg; 57 | } 58 | static char toHexDigit( uint8_t val ) 59 | { 60 | val &= 0x0F; 61 | return (val >= 10) ? ((val - 10) + 'A') : (val + '0'); 62 | } 63 | 64 | 65 | ubloxGPS::decode_t ubloxGPS::decode( char c ) 66 | { 67 | decode_t res = DECODE_CHR_OK; 68 | uint8_t chr = c; 69 | 70 | //trace << '-' << rxState; 71 | switch ((ubxState_t) rxState) { 72 | 73 | case UBX_IDLE: 74 | //if ((c != '\r') && (c != '\n')) trace << toHexDigit(c >> 4) << toHexDigit(c); 75 | if (chr == SYNC_1) 76 | rxState = (rxState_t) UBX_SYNC2; 77 | else 78 | res = DECODE_CHR_INVALID; 79 | break; 80 | 81 | 82 | case UBX_SYNC2: 83 | //if ((c != '\r') && (c != '\n')) trace << '+' << toHexDigit(c >> 4) << toHexDigit(c); 84 | if (chr == SYNC_2) { 85 | rxBegin(); 86 | rxState = (rxState_t) UBX_HEAD; 87 | } else { 88 | rxState = (rxState_t) UBX_IDLE; 89 | } 90 | break; 91 | 92 | case UBX_HEAD: 93 | //if ((c != '\r') && (c != '\n')) trace << '&' << toHexDigit(c >> 4) << toHexDigit(c); 94 | m_rx_msg.crc_a += chr; 95 | m_rx_msg.crc_b += m_rx_msg.crc_a; 96 | 97 | switch (chrCount++) { 98 | case 0: 99 | rx().msg_class = (msg_class_t) chr; 100 | break; 101 | case 1: 102 | rx().msg_id = (msg_id_t) chr; 103 | break; 104 | case 2: 105 | rx().length = chr; 106 | break; 107 | case 3: 108 | rx().length += chr << 8; 109 | if (rx().length > 512) { 110 | rxBegin(); 111 | rxState = (rxState_t) UBX_IDLE; 112 | } 113 | chrCount = 0; 114 | rxState = (rxState_t) UBX_RECEIVING_DATA; 115 | 116 | NMEAGPS_INIT_FIX(m_fix); 117 | safe = false; 118 | 119 | if (rx().msg_class == UBX_ACK) { 120 | if (ack_expected) 121 | ack_same_as_sent = true; // so far... 122 | } else if (reply_expected && rx().same_kind( *reply )) 123 | storage = reply; 124 | else 125 | storage = storage_for( rx() ); 126 | break; 127 | } 128 | break; 129 | 130 | case UBX_RECEIVING_DATA: 131 | //trace << hex << chr; 132 | m_rx_msg.crc_a += chr; 133 | m_rx_msg.crc_b += m_rx_msg.crc_a; 134 | 135 | if (storage && (chrCount < storage->length)) 136 | ((uint8_t *)storage)[ sizeof(msg_t)+chrCount ] = chr; 137 | 138 | parseField( chr ); 139 | 140 | if (ack_same_as_sent) { 141 | if (((chrCount == 0) && (sent.msg_class != (msg_class_t)chr)) || 142 | ((chrCount == 1) && (sent.msg_id != (msg_id_t)chr))) 143 | ack_same_as_sent = false; 144 | } 145 | 146 | if (++chrCount >= rx().length) { 147 | // payload size received 148 | rxState = (rxState_t) UBX_CRC_A; 149 | } 150 | break; 151 | 152 | case UBX_CRC_A: 153 | if (chr != m_rx_msg.crc_a) { 154 | // All the values are suspect. Start over. 155 | m_fix.valid.init(); 156 | rx().msg_class = UBX_UNK; 157 | #ifdef NMEAGPS_STATS 158 | statistics.crc_errors++; 159 | #endif 160 | } 161 | rxState = (rxState_t) UBX_CRC_B; 162 | break; 163 | 164 | case UBX_CRC_B: 165 | if (chr != m_rx_msg.crc_b) { 166 | // All the values are suspect. Start over. 167 | m_fix.valid.init(); 168 | rx().msg_class = UBX_UNK; 169 | #ifdef NMEAGPS_STATS 170 | statistics.crc_errors++; 171 | #endif 172 | } else if (rxEnd()) { 173 | //trace << '!'; 174 | res = ubloxGPS::DECODE_COMPLETED; 175 | } 176 | rxState = (rxState_t) UBX_IDLE; 177 | break; 178 | 179 | default: 180 | res = DECODE_CHR_INVALID; 181 | break; 182 | } 183 | 184 | if (res == DECODE_CHR_INVALID) { 185 | //if ((c != '\r') && (c != '\n')) trace << 'x' << toHexDigit(c >> 4) << toHexDigit(c); 186 | if (rx().msg_class != UBX_UNK) 187 | m_rx_msg.init(); 188 | 189 | // Delegate 190 | res = NMEAGPS::decode( c ); 191 | } 192 | 193 | return res; 194 | } 195 | 196 | 197 | void ubloxGPS::wait_for_idle() 198 | { 199 | // Wait for the input buffer to be emptied 200 | for (uint8_t waits=0; waits < 8; waits++) { 201 | run(); 202 | if (!receiving() || !waiting()) 203 | break; 204 | } 205 | } 206 | 207 | 208 | bool ubloxGPS::wait_for_ack() 209 | { 210 | m_device->flush(); 211 | 212 | uint16_t sent = 0; 213 | uint16_t idle_time = 0; 214 | 215 | do { 216 | if (receiving()) { 217 | wait_for_idle(); 218 | sent = millis(); 219 | } else if (!waiting()) { 220 | return ack_received || reply_received || !nak_received; 221 | } else { 222 | // Idle, accumulate time 223 | uint16_t ms = millis(); 224 | if (sent != 0) 225 | idle_time += ms-sent; 226 | sent = ms; 227 | run(); 228 | } 229 | } while (idle_time < 100); 230 | 231 | return false; 232 | } 233 | 234 | void ubloxGPS::write( const msg_t & msg ) 235 | { 236 | m_device->print( (char) SYNC_1 ); 237 | m_device->print( (char) SYNC_2 ); 238 | 239 | uint8_t crc_a = 0; 240 | uint8_t crc_b = 0; 241 | uint8_t *ptr = (uint8_t *) &msg; 242 | uint16_t l = msg.length + sizeof(msg_t); 243 | while (l--) 244 | write( *ptr++, crc_a, crc_b ); 245 | 246 | m_device->print( (char) crc_a ); 247 | m_device->print( (char) crc_b ); 248 | 249 | sent.msg_class = msg.msg_class; 250 | sent.msg_id = msg.msg_id; 251 | //trace << F("::write ") << msg.msg_class << F("/") << msg.msg_id << endl; 252 | } 253 | 254 | void ubloxGPS::write_P( const msg_t & msg ) 255 | { 256 | m_device->print( (char) SYNC_1 ); 257 | m_device->print( (char) SYNC_2 ); 258 | 259 | uint8_t crc_a = 0; 260 | uint8_t crc_b = 0; 261 | uint8_t *ptr = (uint8_t *) &msg; 262 | uint16_t l = msg.length + sizeof(msg_t); 263 | uint32_t dword; 264 | 265 | while (l > 0) { 266 | if (l >= sizeof(dword)) { 267 | l -= sizeof(dword); 268 | dword = pgm_read_dword( ptr ); 269 | for (uint8_t i=sizeof(dword); i--;) { 270 | write( (uint8_t) dword, crc_a, crc_b ); 271 | dword >>= 8; 272 | } 273 | ptr += sizeof(dword); 274 | 275 | } else { 276 | write( pgm_read_byte( ptr++ ), crc_a, crc_b ); 277 | l--; 278 | } 279 | } 280 | 281 | m_device->print( (char) crc_a ); 282 | m_device->print( (char) crc_b ); 283 | 284 | sent.msg_class = msg.msg_class; 285 | sent.msg_id = msg.msg_id; 286 | } 287 | 288 | /** 289 | * send( msg_t & msg ) 290 | * Sends UBX command and optionally waits for the ack. 291 | */ 292 | 293 | bool ubloxGPS::send( const msg_t & msg, msg_t *reply_msg ) 294 | { 295 | //trace << F("::send - ") << (uint8_t) msg.msg_class << F(" ") << (uint8_t) msg.msg_id << F(" "); 296 | bool ok = true; 297 | 298 | write( msg ); 299 | 300 | if (msg.msg_class == UBX_CFG) { 301 | ack_received = false; 302 | nak_received = false; 303 | ack_same_as_sent = false; 304 | ack_expected = true; 305 | } 306 | 307 | if (reply_msg) { 308 | reply = reply_msg; 309 | reply_received = false; 310 | reply_expected = true; 311 | } 312 | 313 | if (waiting()) { 314 | ok = wait_for_ack(); 315 | /* 316 | if (ok) { 317 | if (ack_received) { 318 | trace << F("ACK!\n"); 319 | } else if (nak_received) { 320 | trace << F("NAK!\n"); 321 | } else { 322 | trace << F("ok!\n"); 323 | } 324 | } else 325 | trace << F("wait_for_ack failed!\n"); 326 | */ 327 | } 328 | 329 | return ok; 330 | } 331 | 332 | 333 | bool ubloxGPS::send_P( const msg_t & msg, msg_t *reply_msg ) 334 | { 335 | return false; 336 | } 337 | 338 | //--------------------------------------------- 339 | 340 | bool ubloxGPS::parseField( char c ) 341 | { 342 | uint8_t chr = c; 343 | bool ok = true; 344 | 345 | switch (rx().msg_class) { 346 | 347 | case UBX_NAV: //================================================= 348 | //if (chrCount == 0) trace << F( " NAV "); 349 | switch (rx().msg_id) { 350 | 351 | case UBX_NAV_STATUS: //-------------------------------------- 352 | //if (chrCount == 0) trace << F( "stat "); 353 | #ifdef UBLOX_PARSE_STATUS 354 | switch (chrCount) { 355 | case 0: case 1: case 2: case 3: 356 | ok = parseTOW( chr ); 357 | break; 358 | case 4: 359 | ok = parseFix( chr ); 360 | break; 361 | case 5: 362 | { 363 | ublox::nav_status_t::flags_t flags = 364 | *((ublox::nav_status_t::flags_t *) &chr); 365 | m_fix.status = 366 | ublox::nav_status_t::to_status 367 | ( (ublox::nav_status_t::status_t) m_fix.status, flags ); 368 | //trace << m_fix.status << ' '; 369 | } 370 | break; 371 | } 372 | #endif 373 | break; 374 | 375 | case UBX_NAV_POSLLH: //-------------------------------------- 376 | //if (chrCount == 0) trace << F( "velned "); 377 | #ifdef UBLOX_PARSE_POSLLH 378 | switch (chrCount) { 379 | 380 | case 0: case 1: case 2: case 3: 381 | ok = parseTOW( chr ); 382 | break; 383 | 384 | #ifdef GPS_FIX_LOCATION 385 | case 4: 386 | NMEAGPS_INVALIDATE( location ); 387 | case 5: case 6: case 7: 388 | ((uint8_t *)&m_fix.lon) [ chrCount-4 ] = chr; 389 | break; 390 | case 8: case 9: case 10: case 11: 391 | ((uint8_t *)&m_fix.lat) [ chrCount-8 ] = chr; 392 | if (chrCount == 11) 393 | m_fix.valid.location = true; 394 | break; 395 | #endif 396 | 397 | #ifdef GPS_FIX_ALTITUDE 398 | case 16: 399 | NMEAGPS_INVALIDATE( altitude ); 400 | case 17: case 18: case 19: 401 | ((uint8_t *)&m_fix.alt) [ chrCount-16 ] = chr; 402 | if (chrCount == 19) { 403 | gps_fix::whole_frac *altp = &m_fix.alt; 404 | int32_t height_MSLmm = *((int32_t *)altp); 405 | //trace << F(" alt = ") << height_MSLmm; 406 | m_fix.alt.whole = height_MSLmm / 1000UL; 407 | m_fix.alt.frac = ((uint16_t)(height_MSLmm - (m_fix.alt.whole * 1000UL)))/10; 408 | //trace << F(" = ") << m_fix.alt.whole << F("."); 409 | //if (m_fix.alt.frac < 10) trace << '0'; 410 | //trace << m_fix.alt.frac; 411 | m_fix.valid.altitude = true; 412 | } 413 | break; 414 | #endif 415 | 416 | #if defined( GPS_FIX_LAT_ERR ) | defined( GPS_FIX_LON_ERR ) 417 | case 20: 418 | #ifdef GPS_FIX_LAT_ERR 419 | NMEAGPS_INVALIDATE( lat_err ); 420 | #endif 421 | #ifdef GPS_FIX_LON_ERR 422 | NMEAGPS_INVALIDATE( lon_err ); 423 | #endif 424 | case 21: case 22: case 23: 425 | U1[ chrCount-20 ] = chr; 426 | if (chrCount == 23) { 427 | uint16_t err_cm = U4/100; 428 | #ifdef GPS_FIX_LAT_ERR 429 | m_fix.lat_err_cm = err_cm; 430 | m_fix.valid.lat_err = true; 431 | #endif 432 | #ifdef GPS_FIX_LON_ERR 433 | m_fix.lon_err_cm = err_cm; 434 | m_fix.valid.lon_err = true; 435 | #endif 436 | } 437 | break; 438 | #endif 439 | 440 | #ifdef GPS_FIX_ALT_ERR 441 | case 24: 442 | NMEAGPS_INVALIDATE( alt_err ); 443 | case 25: case 26: case 27: 444 | U1[ chrCount-24 ] = chr; 445 | if (chrCount == 27) { 446 | m_fix.alt_err_cm = U4/100; 447 | m_fix.valid.alt_err = true; 448 | } 449 | break; 450 | #endif 451 | } 452 | #endif 453 | break; 454 | 455 | case UBX_NAV_VELNED: //-------------------------------------- 456 | //if (chrCount == 0) trace << F( "velned "); 457 | #ifdef UBLOX_PARSE_VELNED 458 | switch (chrCount) { 459 | 460 | case 0: case 1: case 2: case 3: 461 | ok = parseTOW( chr ); 462 | break; 463 | 464 | #ifdef GPS_FIX_SPEED 465 | case 20: 466 | NMEAGPS_INVALIDATE( speed ); 467 | case 21: case 22: case 23: 468 | ((uint8_t *)&m_fix.spd) [ chrCount-20 ] = chr; 469 | if (chrCount == 23) { 470 | gps_fix::whole_frac *spdp = &m_fix.spd; 471 | uint32_t ui = (*((uint32_t *)spdp) * 9UL); 472 | m_fix.spd.whole = ui/250UL; // kph = cm/s * 3600/100000 473 | m_fix.spd.frac = ui - (m_fix.spd.whole * 1000UL); 474 | m_fix.valid.speed = true; 475 | //trace << F("spd = ") << m_fix.speed_mkn(); 476 | } 477 | break; 478 | #endif 479 | 480 | #ifdef GPS_FIX_HEADING 481 | case 24: 482 | NMEAGPS_INVALIDATE( heading ); 483 | case 25: case 26: case 27: 484 | ((uint8_t *)&m_fix.hdg) [ chrCount-24 ] = chr; 485 | if (chrCount == 27) { 486 | gps_fix::whole_frac *hdgp = &m_fix.hdg; 487 | uint32_t ui = (*((uint32_t *)hdgp) * 36UL); 488 | m_fix.hdg.whole = ui / 100000UL; 489 | m_fix.hdg.frac = (ui - (m_fix.hdg.whole * 100000UL))/1000UL; 490 | m_fix.valid.heading = true; 491 | //trace << F(" hdg = ") << m_fix.heading_cd(); 492 | } 493 | break; 494 | #endif 495 | } 496 | #endif 497 | break; 498 | 499 | case UBX_NAV_TIMEGPS: //-------------------------------------- 500 | //if (chrCount == 0) trace << F( "timegps "); 501 | #ifdef UBLOX_PARSE_TIMEGPS 502 | switch (chrCount) { 503 | 504 | #if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) 505 | case 0: case 1: case 2: case 3: 506 | ok = parseTOW( chr ); 507 | break; 508 | case 10: 509 | GPSTime::leap_seconds = (int8_t) chr; 510 | break; 511 | case 11: 512 | { 513 | ublox::nav_timegps_t::valid_t &v = 514 | *((ublox::nav_timegps_t::valid_t *) &chr); 515 | if (!v.leap_seconds) 516 | GPSTime::leap_seconds = 0; // oops! 517 | //else trace << F("leap ") << GPSTime::leap_seconds << ' '; 518 | if (GPSTime::leap_seconds != 0) { 519 | if (!v.time_of_week) { 520 | m_fix.valid.date = 521 | m_fix.valid.time = false; 522 | } else if ((GPSTime::start_of_week() == 0) && 523 | m_fix.valid.date && m_fix.valid.time) { 524 | GPSTime::start_of_week( m_fix.dateTime ); 525 | //trace << m_fix.dateTime << '.' << m_fix.dateTime_cs; 526 | } 527 | } 528 | } 529 | break; 530 | #endif 531 | } 532 | #endif 533 | break; 534 | 535 | case UBX_NAV_TIMEUTC: //-------------------------------------- 536 | //if (chrCount == 0) trace << F( " timeUTC "); 537 | #ifdef UBLOX_PARSE_TIMEUTC 538 | #if defined(GPS_FIX_TIME) | defined(GPS_FIX_DATE) 539 | switch (chrCount) { 540 | 541 | #if defined(GPS_FIX_DATE) 542 | case 12: NMEAGPS_INVALIDATE( date ); 543 | m_fix.dateTime.year = chr; break; 544 | case 13: m_fix.dateTime.year = 545 | ((((uint16_t)chr) << 8) + m_fix.dateTime.year) % 100; 546 | break; 547 | case 14: m_fix.dateTime.month = chr; break; 548 | case 15: m_fix.dateTime.date = chr; break; 549 | #endif 550 | #if defined(GPS_FIX_TIME) 551 | case 16: NMEAGPS_INVALIDATE( time ); 552 | m_fix.dateTime.hours = chr; break; 553 | case 17: m_fix.dateTime.minutes = chr; break; 554 | case 18: m_fix.dateTime.seconds = chr; break; 555 | #endif 556 | case 19: 557 | { 558 | ublox::nav_timeutc_t::valid_t &v = *((ublox::nav_timeutc_t::valid_t *) &chr); 559 | #if defined(GPS_FIX_DATE) 560 | m_fix.valid.date = (v.UTC & v.time_of_week); 561 | #endif 562 | #if defined(GPS_FIX_TIME) 563 | m_fix.valid.time = (v.UTC & v.time_of_week); 564 | #endif 565 | #if defined(GPS_FIX_TIME) & defined(GPS_FIX_DATE) 566 | if (m_fix.valid.date && 567 | (GPSTime::start_of_week() == 0) && 568 | (GPSTime::leap_seconds != 0)) 569 | GPSTime::start_of_week( m_fix.dateTime ); 570 | #endif 571 | //trace << m_fix.dateTime << F(".") << m_fix.dateTime_cs; 572 | //trace << ' ' << v.UTC << ' ' << v.time_of_week << ' ' << start_of_week(); 573 | } 574 | break; 575 | } 576 | #endif 577 | #endif 578 | break; 579 | 580 | case UBX_NAV_SVINFO: //-------------------------------------- 581 | //if (chrCount == 0) trace << PSTR( "svinfo "); 582 | #ifdef UBLOX_PARSE_SVINFO 583 | switch (chrCount) { 584 | 585 | case 0: case 1: case 2: case 3: 586 | ok = parseTOW( chr ); 587 | break; 588 | 589 | #ifdef GPS_FIX_SATELLITES 590 | case 4: 591 | m_fix.satellites = chr; 592 | m_fix.valid.satellites = true; 593 | #ifdef NMEAGPS_PARSE_SATELLITES 594 | sat_count = 0; 595 | break; 596 | default: 597 | if ((chrCount >= 8) && (sat_count < NMEAGPS_MAX_SATELLITES)) { 598 | uint8_t i = 599 | (uint8_t) (chrCount - 8 - (12 * (uint16_t)sat_count)); 600 | 601 | switch (i) { 602 | case 1: satellites[sat_count].id = chr; break; 603 | #ifdef NMEAGPS_PARSE_SATELLITE_INFO 604 | case 0: satellites[sat_count].tracked = (chr != 255); break; 605 | case 4: satellites[sat_count].snr = chr; break; 606 | case 5: satellites[sat_count].elevation = chr; break; 607 | case 6: satellites[sat_count].azimuth = chr; break; 608 | case 7: 609 | satellites[sat_count].azimuth += (chr << 8); 610 | break; 611 | #endif 612 | case 11: sat_count++; break; 613 | } 614 | } 615 | #endif 616 | break; 617 | #endif 618 | #endif 619 | } 620 | break; 621 | 622 | default: 623 | break; 624 | } 625 | break; 626 | case UBX_RXM: //================================================= 627 | case UBX_INF: //================================================= 628 | case UBX_ACK: //================================================= 629 | break; 630 | case UBX_CFG: //================================================= 631 | switch (rx().msg_id) { 632 | case UBX_CFG_MSG: //-------------------------------------- 633 | #ifdef UBLOX_PARSE_CFGMSG 634 | #endif 635 | break; 636 | case UBX_CFG_RATE: //-------------------------------------- 637 | #ifdef UBLOX_PARSE_CFGRATE 638 | #endif 639 | break; 640 | case UBX_CFG_NAV5: //-------------------------------------- 641 | #ifdef UBLOX_PARSE_CFGNAV5 642 | #endif 643 | break; 644 | default: 645 | break; 646 | } 647 | break; 648 | case UBX_MON: //================================================= 649 | switch (rx().msg_id) { 650 | case UBX_MON_VER: 651 | #ifdef UBLOX_PARSE_MONVER 652 | #endif 653 | break; 654 | default: 655 | break; 656 | } 657 | break; 658 | case UBX_AID: //================================================= 659 | case UBX_TIM: //================================================= 660 | case UBX_NMEA: //================================================= 661 | break; 662 | default: 663 | break; 664 | } 665 | 666 | return ok; 667 | } 668 | 669 | 670 | bool ubloxGPS::parseFix( uint8_t c ) 671 | { 672 | static const gps_fix::status_t ubx[] __PROGMEM = 673 | { 674 | gps_fix::STATUS_NONE, 675 | gps_fix::STATUS_EST, // dead reckoning only 676 | gps_fix::STATUS_STD, // 2D 677 | gps_fix::STATUS_STD, // 3D 678 | gps_fix::STATUS_STD, // GPS + dead reckoning 679 | gps_fix::STATUS_TIME_ONLY 680 | }; 681 | 682 | if (c >= sizeof(ubx)/sizeof(ubx[0])) 683 | return false; 684 | 685 | m_fix.status = (gps_fix::status_t) pgm_read_byte( &ubx[c] ); 686 | m_fix.valid.status = true; 687 | 688 | return true; 689 | } 690 | 691 | #if 0 692 | static const uint8_t cfg_msg_data[] __PROGMEM = 693 | { ubloxGPS::UBX_CFG, ubloxGPS::UBX_CFG_MSG, 694 | sizeof(ubloxGPS::cfg_msg_t), 0, 695 | ubloxGPS::UBX_NMEA, NMEAGPS::NMEA_VTG, 0 }; 696 | 697 | static const ubloxGPS::cfg_msg_t *cfg_msg_P = 698 | (const ubloxGPS::cfg_msg_t *) &cfg_msg_data[0]; 699 | 700 | send_P( *cfg_msg_P ); 701 | 702 | const ubloxGPS::msg_hdr_t test __PROGMEM = 703 | { ubloxGPS::UBX_CFG, ubloxGPS::UBX_CFG_RATE }; 704 | 705 | const uint8_t test2_data[] __PROGMEM = 706 | { ubloxGPS::UBX_CFG, ubloxGPS::UBX_CFG_RATE, 707 | sizeof(ubloxGPS::cfg_rate_t), 0, 708 | 0xE8, 0x03, 0x01, 0x00, ubloxGPS::UBX_TIME_REF_GPS, 0 }; 709 | 710 | const ubloxGPS::msg_t *test2 = (const ubloxGPS::msg_t *) &test2_data[0]; 711 | #endif 712 | --------------------------------------------------------------------------------