From 185c6c200aec7049d0f7ddacf3f1b6e7f1f420bd Mon Sep 17 00:00:00 2001 From: PaulZC Date: Wed, 12 Jan 2022 14:02:46 +0000 Subject: [PATCH] Add support for automatic NMEA --- .../Example1_getLatestNMEAGPGGA.ino | 125 +++++ src/SparkFun_u-blox_GNSS_Arduino_Library.cpp | 471 ++++++++++++++++++ src/SparkFun_u-blox_GNSS_Arduino_Library.h | 24 + src/u-blox_structs.h | 49 ++ 4 files changed, 669 insertions(+) create mode 100644 examples/Automatic_NMEA/Example1_getLatestNMEAGPGGA/Example1_getLatestNMEAGPGGA.ino diff --git a/examples/Automatic_NMEA/Example1_getLatestNMEAGPGGA/Example1_getLatestNMEAGPGGA.ino b/examples/Automatic_NMEA/Example1_getLatestNMEAGPGGA/Example1_getLatestNMEAGPGGA.ino new file mode 100644 index 0000000..bf525a9 --- /dev/null +++ b/examples/Automatic_NMEA/Example1_getLatestNMEAGPGGA/Example1_getLatestNMEAGPGGA.ino @@ -0,0 +1,125 @@ +/* + Get the GPGGA NMEA sentence using getLatestNMEAGPGGA + By: Paul Clark + SparkFun Electronics + Date: January 12th, 2021 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to turn on/off the NMEA sentences being output over I2C. + It then demonstrates how to use the new getLatestNMEAGPGGA function to retrieve the latest GPGGA message. + getLatestNMEAGPGGA returns immediately - it is not blocking. + It returns: + 0 if no data is available + 1 if the data is valid but is stale (you have read it before) + 2 if the data is valid and fresh + + This example turns off all sentences except for GPGGA. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GNSS and a RedBoard + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //Needed for I2C to GNSS + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +void setup() +{ + + Serial.begin(115200); + Serial.println(F("SparkFun u-blox GNSS Example")); + + Wire.begin(); + + //myGNSS.enableDebugging(); // Uncomment this line to enable debug messages on Serial + + if (myGNSS.begin() == false) + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + //Disable or enable various NMEA sentences over the I2C interface + myGNSS.setI2COutput(COM_TYPE_NMEA | COM_TYPE_UBX); // Turn on both UBX and NMEA sentences on I2C. (Turn off RTCM and SPARTN) + myGNSS.disableNMEAMessage(UBX_NMEA_GLL, COM_PORT_I2C); // Several of these are on by default on ublox board so let's disable them + myGNSS.disableNMEAMessage(UBX_NMEA_GSA, COM_PORT_I2C); + myGNSS.disableNMEAMessage(UBX_NMEA_GSV, COM_PORT_I2C); + myGNSS.disableNMEAMessage(UBX_NMEA_RMC, COM_PORT_I2C); + myGNSS.disableNMEAMessage(UBX_NMEA_VTG, COM_PORT_I2C); + myGNSS.enableNMEAMessage(UBX_NMEA_GGA, COM_PORT_I2C); // Leave only GGA enabled at current navigation rate + + //myGNSS.saveConfiguration(); //Optional: Save these settings to NVM + + Serial.println(F("Messages configured")); + + //myGNSS.setNMEAOutputPort(Serial); // Uncomment this line to echo all NMEA data to Serial for debugging +} + +void loop() +{ + // getLatestNMEAGPGGA calls checkUblox for us. We don't need to do it here + + NMEA_GGA_data_t data; // Storage for the GPGGA data + uint8_t result = myGNSS.getLatestNMEAGPGGA(&data); // Get the latest GPGGA data (if any) + + if (result == 0) + { + Serial.println(F("No GPGGA data available")); + } + else if (result == 1) + { + Serial.println(F("GPGGA data is available but is stale")); + } + else if (result == 2) + { + // Data contains .length and .nmea + Serial.print(F("NMEA: Length: ")); + Serial.print(data.length); + Serial.print(F("\tData: ")); + Serial.println((const char *)data.nmea); // .nmea is printable (NULL-terminated) + } + else + { + Serial.print(F("result == ")); + Serial.print(result); + Serial.println(F(". This should be impossible!")); + } + + result = myGNSS.getLatestNMEAGNGGA(&data); // Get the latest GNGGA data (if any) + + if (result == 0) + { + Serial.println(F("No GNGGA data available")); + } + else if (result == 1) + { + Serial.println(F("GNGGA data is available but is stale")); + } + else if (result == 2) + { + // Data contains .length and .nmea + Serial.print(F("NMEA: Length: ")); + Serial.print(data.length); + Serial.print(F("\tData: ")); + Serial.println((const char *)data.nmea); // .nmea is printable (NULL-terminated) + } + else + { + Serial.print(F("result == ")); + Serial.print(result); + Serial.println(F(". This should be impossible!")); + } + + delay(250); +} diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp index 5c3ae5c..82eeb09 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp @@ -406,6 +406,26 @@ void SFE_UBLOX_GNSS::end(void) packetUBXHNRPVT = NULL; // Redundant? } + if (storageNMEAGPGGA != NULL) + { + if (storageNMEAGPGGA->callbackCopy != NULL) + { + delete storageNMEAGPGGA->callbackCopy; + } + delete storageNMEAGPGGA; + storageNMEAGPGGA = NULL; // Redundant? + } + + if (storageNMEAGNGGA != NULL) + { + if (storageNMEAGNGGA->callbackCopy != NULL) + { + delete storageNMEAGNGGA->callbackCopy; + } + delete storageNMEAGNGGA; + storageNMEAGNGGA = NULL; // Redundant? + } + } //Allow the user to change packetCfgPayloadSize. Handy if you want to process big messages like RAWX @@ -1682,6 +1702,15 @@ void SFE_UBLOX_GNSS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t r _signsOfLife = isNMEAHeaderValid(); } + // Check if we have automatic storage for this message + if (isThisNMEAauto()) + { + uint8_t *lengthPtr = getNMEAWorkingLengthPtr(); // Get a pointer to the working copy length + uint8_t *nmeaPtr = getNMEAWorkingNMEAPtr(); // Get a pointer to the working copy NMEA data + *lengthPtr = 6; // Set the working copy length + memcpy(nmeaPtr, &nmeaAddressField[0], 6); // Copy the start character and address field into the working copy + } + // We've just received the end of the address field. Check if it is selected for logging if (logThisNMEA()) { @@ -1701,6 +1730,24 @@ void SFE_UBLOX_GNSS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t r if ((nmeaByteCounter > 5) || (nmeaByteCounter < 0)) // Should we add incoming to the file buffer and/or pass it to processNMEA? { + if (isThisNMEAauto()) + { + uint8_t *lengthPtr = getNMEAWorkingLengthPtr(); // Get a pointer to the working copy length + uint8_t *nmeaPtr = getNMEAWorkingNMEAPtr(); // Get a pointer to the working copy NMEA data + uint8_t nmeaMaxLength = getNMEAMaxLength(); + if (*lengthPtr < nmeaMaxLength) + { + *(nmeaPtr + *lengthPtr) = incoming; // Store the character + *lengthPtr = *lengthPtr + 1; // Increment the length + if (*lengthPtr == nmeaMaxLength) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("process: NMEA buffer is full!")); + } + } + } + } if (logThisNMEA()) storeFileBytes(&incoming, 1); // Add incoming to the file buffer if (processThisNMEA()) @@ -1716,7 +1763,85 @@ void SFE_UBLOX_GNSS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t r currentSentence = NONE; //Something went wrong. Reset. if (nmeaByteCounter == 0) // Check if we are done + { + if (isThisNMEAauto()) + { + uint8_t *workingLengthPtr = getNMEAWorkingLengthPtr(); // Get a pointer to the working copy length + uint8_t *workingNMEAPtr = getNMEAWorkingNMEAPtr(); // Get a pointer to the working copy NMEA data + uint8_t nmeaMaxLength = getNMEAMaxLength(); + // Check the checksum: the checksum is the exclusive-OR of all characters between the $ and the * + uint8_t nmeaChecksum = 0; + uint8_t charsChecked = 1; // Start after the $ + uint8_t thisChar = '\0'; + while ((charsChecked < (nmeaMaxLength - 1)) + && (charsChecked < ((*workingLengthPtr) - 4)) + && (thisChar != '*')) + { + thisChar = *(workingNMEAPtr + charsChecked); // Get a char from the working copy + if (thisChar != '*') // Ex-or the char into the checksum - but not if it is the '*' + nmeaChecksum ^= thisChar; + charsChecked++; // Increment the counter + } + if (thisChar == '*') // Make sure we found the * + { + uint8_t expectedChecksum1 = (nmeaChecksum >> 4) + '0'; + if (expectedChecksum1 >= ':') // Handle Hex correctly + expectedChecksum1 += 'A' - ':'; + uint8_t expectedChecksum2 = (nmeaChecksum & 0x0F) + '0'; + if (expectedChecksum2 >= ':') // Handle Hex correctly + expectedChecksum2 += 'A' - ':'; + if ((expectedChecksum1 == *(workingNMEAPtr + charsChecked)) + && (expectedChecksum2 == *(workingNMEAPtr + charsChecked + 1))) + { + uint8_t *completeLengthPtr = getNMEACompleteLengthPtr(); // Get a pointer to the complete copy length + uint8_t *completeNMEAPtr = getNMEACompleteNMEAPtr(); // Get a pointer to the complete copy NMEA data + memset(completeNMEAPtr, 0, nmeaMaxLength); // Clear the previous complete copy + memcpy(completeNMEAPtr, workingNMEAPtr, *workingLengthPtr); // Copy the working copy into the complete copy + *completeLengthPtr = *workingLengthPtr; // Update the length + nmeaAutomaticFlags *flagsPtr = getNMEAFlagsPtr(); // Get a pointer to the flags + nmeaAutomaticFlags flagsCopy = *flagsPtr; + flagsCopy.flags.bits.completeCopyValid = 1; // Set the complete copy valid flag + flagsCopy.flags.bits.completeCopyRead = 0; // Clear the complete copy read/stale flag + *flagsPtr = flagsCopy; // Update the flags + // Callback + if (doesThisNMEAHaveCallback()) // Do we need to copy the data into the callback copy? + { + if (flagsCopy.flags.bits.callbackCopyValid == 0) // Has the callback copy valid flag been cleared (by checkCallbacks) + { + uint8_t *callbackLengthPtr = getNMEACallbackLengthPtr(); // Get a pointer to the callback copy length + uint8_t *callbackNMEAPtr = getNMEACallbackNMEAPtr(); // Get a pointer to the callback copy NMEA data + memset(callbackNMEAPtr, 0, nmeaMaxLength); // Clear the previous callback copy + memcpy(callbackNMEAPtr, workingNMEAPtr, *workingLengthPtr); // Copy the working copy into the callback copy + *callbackLengthPtr = *workingLengthPtr; // Update the length + flagsCopy.flags.bits.callbackCopyValid = 1; // Set the callback copy valid flag + *flagsPtr = flagsCopy; // Update the flags + } + } + } + else + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("process: NMEA checksum fail (2)! Expected ")); + _debugSerial->write(expectedChecksum1); + _debugSerial->write(expectedChecksum2); + _debugSerial->print(F(" Got ")); + _debugSerial->write(*(workingNMEAPtr + charsChecked)); + _debugSerial->write(*(workingNMEAPtr + charsChecked + 1)); + _debugSerial->println(); + } + } + } + else + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("process: NMEA checksum fail (1)!")); + } + } + } currentSentence = NONE; // All done! + } } else if (currentSentence == RTCM) { @@ -1828,6 +1953,194 @@ void SFE_UBLOX_GNSS::processNMEA(char incoming) _nmeaOutputPort->write(incoming); //Echo this byte to the serial port } +// Check if the NMEA message (in nmeaAddressField) is "auto" (i.e. has RAM allocated for it) +bool SFE_UBLOX_GNSS::isThisNMEAauto() +{ + char thisNMEA[] = "GPGGA"; + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + if (storageNMEAGPGGA != NULL) + return true; + } + + strcpy(thisNMEA, "GNGGA"); + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + if (storageNMEAGNGGA != NULL) + return true; + } + + return false; +} + +// Do we need to copy the data into the callback copy? +bool SFE_UBLOX_GNSS::doesThisNMEAHaveCallback() +{ + char thisNMEA[] = "GPGGA"; + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + if (storageNMEAGPGGA != NULL) + if (storageNMEAGPGGA->callbackCopy != NULL) + if (storageNMEAGPGGA->callbackPointer != NULL) + return true; + } + + strcpy(thisNMEA, "GNGGA"); + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + if (storageNMEAGNGGA != NULL) + if (storageNMEAGNGGA->callbackCopy != NULL) + if (storageNMEAGNGGA->callbackPointer != NULL) + return true; + } + + return false; +} + +// Get a pointer to the working copy length +uint8_t * SFE_UBLOX_GNSS::getNMEAWorkingLengthPtr() +{ + char thisNMEA[] = "GPGGA"; + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGPGGA->workingCopy.length; + } + + strcpy(thisNMEA, "GNGGA"); + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGNGGA->workingCopy.length; + } + + return NULL; +} + +// Get a pointer to the working copy NMEA data +uint8_t * SFE_UBLOX_GNSS::getNMEAWorkingNMEAPtr() +{ + char thisNMEA[] = "GPGGA"; + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGPGGA->workingCopy.nmea[0]; + } + + strcpy(thisNMEA, "GNGGA"); + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGNGGA->workingCopy.nmea[0]; + } + + return NULL; +} + +// Get a pointer to the complete copy length +uint8_t * SFE_UBLOX_GNSS::getNMEACompleteLengthPtr() +{ + char thisNMEA[] = "GPGGA"; + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGPGGA->completeCopy.length; + } + + strcpy(thisNMEA, "GNGGA"); + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGNGGA->completeCopy.length; + } + + return NULL; +} + +// Get a pointer to the complete copy NMEA data +uint8_t * SFE_UBLOX_GNSS::getNMEACompleteNMEAPtr() +{ + char thisNMEA[] = "GPGGA"; + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGPGGA->completeCopy.nmea[0]; + } + + strcpy(thisNMEA, "GNGGA"); + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGNGGA->completeCopy.nmea[0]; + } + + return NULL; +} + +// Get a pointer to the callback copy length +uint8_t * SFE_UBLOX_GNSS::getNMEACallbackLengthPtr() +{ + char thisNMEA[] = "GPGGA"; + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGPGGA->callbackCopy->length; + } + + strcpy(thisNMEA, "GNGGA"); + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGNGGA->callbackCopy->length; + } + + return NULL; +} + +// Get a pointer to the callback copy NMEA data +uint8_t * SFE_UBLOX_GNSS::getNMEACallbackNMEAPtr() +{ + char thisNMEA[] = "GPGGA"; + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGPGGA->callbackCopy->nmea[0]; + } + + strcpy(thisNMEA, "GNGGA"); + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGNGGA->callbackCopy->nmea[0]; + } + + return NULL; +} + +// Get the maximum length of this NMEA message +uint8_t SFE_UBLOX_GNSS::getNMEAMaxLength() +{ + char thisNMEA[] = "GPGGA"; + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return NMEA_GGA_MAX_LENGTH; + } + + strcpy(thisNMEA, "GNGGA"); + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return NMEA_GGA_MAX_LENGTH; + } + + return 0; +} + +// Get a pointer to the automatic NMEA flags +nmeaAutomaticFlags * SFE_UBLOX_GNSS::getNMEAFlagsPtr() +{ + char thisNMEA[] = "GPGGA"; + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGPGGA->automaticFlags; + } + + strcpy(thisNMEA, "GNGGA"); + if (memcmp(thisNMEA, &nmeaAddressField[1], 5) == 0) + { + return &storageNMEAGNGGA->automaticFlags; + } + + return NULL; +} + //We need to be able to identify an RTCM packet and then the length //so that we know when the RTCM message is completely received and we then start //listening for other sentences (like NMEA or UBX) @@ -4309,6 +4622,17 @@ void SFE_UBLOX_GNSS::checkCallbacks(void) packetUBXHNRPVT->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale } + if ((storageNMEAGPGGA != NULL) // If RAM has been allocated for message storage + && (storageNMEAGPGGA->callbackCopy != NULL) // If RAM has been allocated for the copy of the data + && (storageNMEAGPGGA->callbackPointer != NULL) // If the pointer to the callback has been defined + && (storageNMEAGPGGA->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for GPGGA")); + storageNMEAGPGGA->callbackPointer(*storageNMEAGPGGA->callbackCopy); // Call the callback + storageNMEAGPGGA->automaticFlags.flags.bits.callbackCopyValid = 0; // Mark the data as stale + } + checkCallbacksReentrant = false; } @@ -11716,6 +12040,153 @@ uint32_t SFE_UBLOX_GNSS::getProcessNMEAMask() return (_processNMEA.all); } +// Initiate automatic storage of NMEA GPGGA messages + +// Get the most recent GPGGA message +// Return 0 if the message has not been received from the module +// Return 1 if the data is valid but has been read before +// Return 2 if the data is valid and is fresh/unread +uint8_t SFE_UBLOX_GNSS::getLatestNMEAGPGGA(NMEA_GGA_data_t *data) +{ + if (storageNMEAGPGGA == NULL) initStorageNMEAGPGGA(); //Check that RAM has been allocated for the message + if (storageNMEAGPGGA == NULL) //Bail if the RAM allocation failed + return (false); + + checkUbloxInternal(&packetCfg, 0, 0); // Call checkUbloxInternal to parse any incoming data. Use a fake UBX class and ID. + + memcpy(data, &storageNMEAGPGGA->completeCopy, sizeof(NMEA_GGA_data_t)); // Copy the complete copy + + uint8_t result = 0; + if (storageNMEAGPGGA->automaticFlags.flags.bits.completeCopyValid == 1) // Is the complete copy valid? + { + result = 1; + if (storageNMEAGPGGA->automaticFlags.flags.bits.completeCopyRead == 0) // Has the data already been read? + { + result = 2; + storageNMEAGPGGA->automaticFlags.flags.bits.completeCopyRead = 1; // Mark the data as read + } + } + + return (result); +} + +//Enable a callback on the arrival of a GPGGA message +bool SFE_UBLOX_GNSS::setNMEAGPGGAcallback(void (*callbackPointer)(NMEA_GGA_data_t)) +{ + if (storageNMEAGPGGA == NULL) initStorageNMEAGPGGA(); //Check that RAM has been allocated for the message + if (storageNMEAGPGGA == NULL) //Bail if the RAM allocation failed + return (false); + + if (storageNMEAGPGGA->callbackCopy == NULL) // Check if RAM has been allocated for the callback copy + { + storageNMEAGPGGA->callbackCopy = new NMEA_GGA_data_t; + } + + if (storageNMEAGPGGA->callbackCopy == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setNMEAGPGGAcallback: RAM alloc failed!")); + return (false); + } + + storageNMEAGPGGA->callbackPointer = callbackPointer; + return (true); +} + +// Private: allocate RAM for incoming NMEA GPGGA messages and initialize it +bool SFE_UBLOX_GNSS::initStorageNMEAGPGGA() +{ + storageNMEAGPGGA = new NMEA_GPGGA_t; //Allocate RAM for the main struct + if (storageNMEAGPGGA == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initStorageNMEAGPGGA: RAM alloc failed!")); + return (false); + } + + storageNMEAGPGGA->workingCopy.length = 0; // Clear the data length + memset(storageNMEAGPGGA->workingCopy.nmea, 0, NMEA_GGA_MAX_LENGTH); // Clear the nmea storage + storageNMEAGPGGA->completeCopy.length = 0; // Clear the data length + memset(storageNMEAGPGGA->completeCopy.nmea, 0, NMEA_GGA_MAX_LENGTH); // Clear the nmea storage + + storageNMEAGPGGA->callbackPointer = NULL; // Clear the callback pointers + storageNMEAGPGGA->callbackCopy = NULL; + + storageNMEAGPGGA->automaticFlags.flags.all = 0; // Mark the data as invalid/stale and unread + + return (true); +} + +uint8_t SFE_UBLOX_GNSS::getLatestNMEAGNGGA(NMEA_GGA_data_t *data) +{ + if (storageNMEAGNGGA == NULL) initStorageNMEAGNGGA(); //Check that RAM has been allocated for the message + if (storageNMEAGNGGA == NULL) //Bail if the RAM allocation failed + return (false); + + checkUbloxInternal(&packetCfg, 0, 0); // Call checkUbloxInternal to parse any incoming data. Use a fake UBX class and ID. + + memcpy(data, &storageNMEAGNGGA->completeCopy, sizeof(NMEA_GGA_data_t)); // Copy the complete copy + + uint8_t result = 0; + if (storageNMEAGNGGA->automaticFlags.flags.bits.completeCopyValid == 1) // Is the complete copy valid? + { + result = 1; + if (storageNMEAGNGGA->automaticFlags.flags.bits.completeCopyRead == 0) // Has the data already been read? + { + result = 2; + storageNMEAGNGGA->automaticFlags.flags.bits.completeCopyRead = 1; // Mark the data as read + } + } + + return (result); +} + +bool SFE_UBLOX_GNSS::setNMEAGNGGAcallback(void (*callbackPointer)(NMEA_GGA_data_t)) +{ + if (storageNMEAGNGGA == NULL) initStorageNMEAGNGGA(); //Check that RAM has been allocated for the message + if (storageNMEAGNGGA == NULL) //Bail if the RAM allocation failed + return (false); + + if (storageNMEAGNGGA->callbackCopy == NULL) // Check if RAM has been allocated for the callback copy + { + storageNMEAGNGGA->callbackCopy = new NMEA_GGA_data_t; + } + + if (storageNMEAGNGGA->callbackCopy == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setNMEAGNGGAcallback: RAM alloc failed!")); + return (false); + } + + storageNMEAGNGGA->callbackPointer = callbackPointer; + return (true); +} + +// Private: allocate RAM for incoming NMEA GNGGA messages and initialize it +bool SFE_UBLOX_GNSS::initStorageNMEAGNGGA() +{ + storageNMEAGNGGA = new NMEA_GNGGA_t; //Allocate RAM for the main struct + if (storageNMEAGNGGA == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initStorageNMEAGNGGA: RAM alloc failed!")); + return (false); + } + + storageNMEAGNGGA->workingCopy.length = 0; // Clear the data length + memset(storageNMEAGNGGA->workingCopy.nmea, 0, NMEA_GGA_MAX_LENGTH); // Clear the nmea storage + storageNMEAGNGGA->completeCopy.length = 0; // Clear the data length + memset(storageNMEAGNGGA->completeCopy.nmea, 0, NMEA_GGA_MAX_LENGTH); // Clear the nmea storage + + storageNMEAGNGGA->callbackPointer = NULL; // Clear the callback pointers + storageNMEAGNGGA->callbackCopy = NULL; + + storageNMEAGNGGA->automaticFlags.flags.all = 0; // Mark the data as invalid/stale and unread + + return (true); +} + // ***** CFG RATE Helper Functions //Set the rate at which the module will give us an updated navigation solution diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.h b/src/SparkFun_u-blox_GNSS_Arduino_Library.h index 8d94e8c..3d51197 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.h +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.h @@ -1323,6 +1323,13 @@ public: float getHNRpitch(uint16_t maxWait = defaultMaxWait); // Returned as degrees float getHNRheading(uint16_t maxWait = defaultMaxWait); // Returned as degrees + // Support for "auto" storage of NMEA messages + + uint8_t getLatestNMEAGPGGA(NMEA_GGA_data_t *data); // Return the most recent GPGGA: 0 = no data, 1 = stale data, 2 = fresh data + bool setNMEAGPGGAcallback(void (*callbackPointer)(NMEA_GGA_data_t)); //Enable a callback on the arrival of a GPGGA message + uint8_t getLatestNMEAGNGGA(NMEA_GGA_data_t *data); // Return the most recent GNGGA: 0 = no data, 1 = stale data, 2 = fresh data + bool setNMEAGNGGAcallback(void (*callbackPointer)(NMEA_GGA_data_t)); //Enable a callback on the arrival of a GPGGA message + // Functions to extract signed and unsigned 8/16/32-bit data from a ubxPacket // From v2.0: These are public. The user can call these to extract data from custom packets uint32_t extractLong(ubxPacket *msg, uint8_t spotToStart); //Combine four bytes from payload into long @@ -1373,6 +1380,9 @@ public: UBX_MGA_ACK_DATA0_t *packetUBXMGAACK = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary UBX_MGA_DBD_t *packetUBXMGADBD = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + NMEA_GPGGA_t *storageNMEAGPGGA = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + NMEA_GNGGA_t *storageNMEAGNGGA = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + uint16_t rtcmFrameCounter = 0; //Tracks the type of incoming byte inside RTCM frame private: @@ -1452,6 +1462,9 @@ private: bool initPacketUBXMGAACK(); // Allocate RAM for packetUBXMGAACK and initialize it bool initPacketUBXMGADBD(); // Allocate RAM for packetUBXMGADBD and initialize it + bool initStorageNMEAGPGGA(); // Allocate RAM for incoming NMEA GPGGA messages and initialize it + bool initStorageNMEAGNGGA(); // Allocate RAM for incoming NMEA GNGGA messages and initialize it + //Variables TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware Stream *_serialPort; //The generic connection to user's chosen Serial hardware @@ -1526,6 +1539,17 @@ private: bool processThisNMEA(); // Return true if we should pass this NMEA message to processNMEA bool isNMEAHeaderValid(); // Return true if the six byte NMEA header appears valid. Used to set _signsOfLife + bool isThisNMEAauto(); // Check if the NMEA message (in nmeaAddressField) is "auto" (i.e. has RAM allocated for it) + bool doesThisNMEAHaveCallback(); // Do we need to copy the data into the callback copy? + uint8_t *getNMEAWorkingLengthPtr(); // Get a pointer to the working copy length + uint8_t *getNMEAWorkingNMEAPtr(); // Get a pointer to the working copy NMEA data + uint8_t *getNMEACompleteLengthPtr(); // Get a pointer to the complete copy length + uint8_t *getNMEACompleteNMEAPtr(); // Get a pointer to the complete copy NMEA data + uint8_t *getNMEACallbackLengthPtr(); // Get a pointer to the callback copy length + uint8_t *getNMEACallbackNMEAPtr(); // Get a pointer to the callback copy NMEA data + uint8_t getNMEAMaxLength(); // Get the maximum length of this NMEA message + nmeaAutomaticFlags *getNMEAFlagsPtr(); // Get a pointer to the flags + uint16_t rtcmLen = 0; // Flag to prevent reentry into checkCallbacks diff --git a/src/u-blox_structs.h b/src/u-blox_structs.h index 84872bb..3d6dcaf 100644 --- a/src/u-blox_structs.h +++ b/src/u-blox_structs.h @@ -2232,4 +2232,53 @@ typedef struct UBX_HNR_INS_data_t *callbackData; } UBX_HNR_INS_t; +// NMEA-specific structs + +//Additional flags and pointers that need to be stored with each message type +struct nmeaAutomaticFlags +{ + union + { + uint8_t all; + struct + { + uint8_t completeCopyValid : 1; // Is the copy of the data struct used by the get function valid/fresh? 0 = invalid/stale, 1 = valid/fresh + uint8_t completeCopyRead : 1; // Has the complete copy been read? 0 = unread, 1 = read + uint8_t callbackCopyValid : 1; // Is the copy of the data struct used by the callback valid/fresh? 0 = invalid/stale, 1 = valid/fresh + } bits; + } flags; +}; + +// The max length for NMEA messages should be 82 bytes, but GGA messages can exceed that if they include the +// extra decimal places for "High Precision". +// +// To be safe, let's allocate 90 bytes to store the message + +const uint8_t NMEA_GGA_MAX_LENGTH = 90; + +typedef struct +{ + uint8_t length; // The number of bytes in nmea + uint8_t nmea[NMEA_GGA_MAX_LENGTH]; +} NMEA_GGA_data_t; + +typedef struct +{ + nmeaAutomaticFlags automaticFlags; + NMEA_GGA_data_t workingCopy; // Incoming data is added to the working copy + NMEA_GGA_data_t completeCopy; // The working copy is copied into the complete copy when all data has been received and the checksum is valid + void (*callbackPointer)(NMEA_GGA_data_t); + NMEA_GGA_data_t *callbackCopy; // The callback gets its own preserved copy of the complete copy +} NMEA_GPGGA_t; + +typedef struct +{ + nmeaAutomaticFlags automaticFlags; + NMEA_GGA_data_t workingCopy; // Incoming data is added to the working copy + NMEA_GGA_data_t completeCopy; // The working copy is copied into the complete copy when all data has been received and the checksum is valid + void (*callbackPointer)(NMEA_GGA_data_t); + NMEA_GGA_data_t *callbackCopy; // The callback gets its own preserved copy of the complete copy +} NMEA_GNGGA_t; + + #endif