diff --git a/examples/Data_Logging/DataLoggingExample6_NMEA/DataLoggingExample6_NMEA.ino b/examples/Data_Logging/DataLoggingExample6_NMEA/DataLoggingExample6_NMEA.ino new file mode 100644 index 0000000..80e2b62 --- /dev/null +++ b/examples/Data_Logging/DataLoggingExample6_NMEA/DataLoggingExample6_NMEA.ino @@ -0,0 +1,239 @@ +/* + Demonstrate how to log NMEA and UBX data simultaneously + By: Paul Clark + SparkFun Electronics + Date: April 13th, 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 configure the u-blox GNSS to send PVT reports automatically + and log those and any incoming NMEA messages to SD card in UBX format + + ** Please note: this example will only work on processors like the Artemis which have plenty of RAM available ** + + This code is intended to be run on the MicroMod Data Logging Carrier Board using the Artemis Processor + but can be adapted by changing the chip select pin and SPI definitions: + https://www.sparkfun.com/products/16829 + https://www.sparkfun.com/products/16401 + + Hardware Connections: + Please see: https://learn.sparkfun.com/tutorials/micromod-data-logging-carrier-board-hookup-guide + Insert the Artemis Processor into the MicroMod Data Logging Carrier Board and secure with the screw. + Connect your GNSS breakout to the Carrier Board using a Qwiic cable. + Connect an antenna to your GNSS board if required. + Insert a formatted micro-SD card into the socket on the Carrier Board. + Connect the Carrier Board to your computer using a USB-C cable. + Ensure you have the SparkFun Apollo3 boards installed: http://boardsmanager/All#SparkFun_Apollo3 + This code has been tested using version 1.2.1 of the Apollo3 boards on Arduino IDE 1.8.13. + Select "SparkFun Artemis MicroMod" as the board type. + Press upload to upload the code onto the Artemis. + Open the Serial Monitor at 115200 baud to see the output. + + To minimise I2C bus errors, it is a good idea to open the I2C pull-up split pad links on + both the MicroMod Data Logging Carrier Board and the u-blox module breakout. + + 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 + ZOE-M8Q: https://www.sparkfun.com/products/15193 + SAM-M8Q: https://www.sparkfun.com/products/15210 +*/ + +#include +#include +#include //Needed for I2C to GNSS + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +File myFile; //File that all GNSS data is written to + +#define sdChipSelect CS //Primary SPI Chip Select is CS for the MicroMod Artemis Processor. Adjust for your processor if necessary. + +#define sdWriteSize 512 // Write data to the SD card in blocks of 512 bytes +#define fileBufferSize 16384 // Allocate 16KBytes of RAM for UBX message storage + +unsigned long lastPrint; // Record when the last Serial print took place +unsigned long bytesWritten = 0; // Record how many bytes have been written to SD card + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + pinMode(LED_BUILTIN, OUTPUT); // Flash LED_BUILTIN each time we write to the SD card + digitalWrite(LED_BUILTIN, LOW); + + Wire.begin(); // Start I2C communication + +#if defined(AM_PART_APOLLO3) + Wire.setPullups(0); // On the Artemis, we can disable the internal I2C pull-ups too to help reduce bus errors +#endif + + while (Serial.available()) // Make sure the Serial buffer is empty + { + Serial.read(); + } + + Serial.println(F("Press any key to start logging.")); + + while (!Serial.available()) // Wait for the user to press a key + { + ; // Do nothing + } + + delay(100); // Wait, just in case multiple characters were sent + + while (Serial.available()) // Empty the Serial buffer + { + Serial.read(); + } + + Serial.println("Initializing SD card..."); + + // See if the card is present and can be initialized: + if (!SD.begin(sdChipSelect)) + { + Serial.println("Card failed, or not present. Freezing..."); + // don't do anything more: + while (1); + } + Serial.println("SD card initialized."); + + // Create or open a file called "PVT_NMEA.ubx" on the SD card. + // If the file already exists, the new data is appended to the end of the file. + myFile = SD.open("PVT_NMEA.ubx", FILE_WRITE); + if(!myFile) + { + Serial.println(F("Failed to create UBX data file! Freezing...")); + while (1); + } + + //myGNSS.enableDebugging(); // Uncomment this line to enable lots of helpful GNSS debug messages on Serial + //myGNSS.enableDebugging(Serial, true); // Or, uncomment this line to enable only the important GNSS debug messages on Serial + + //myGNSS.disableUBX7Fcheck(); // RAWX data can legitimately contain 0x7F. Uncomment this line to disable the "7F" check in checkUbloxI2C + + // SD cards can occasionally 'hiccup' and a write takes much longer than usual. The buffer needs to be big enough + // to hold the backlog of data if/when this happens. + // getMaxFileBufferAvail will tell us the maximum number of bytes which the file buffer has contained. + myGNSS.setFileBufferSize(fileBufferSize); // setFileBufferSize must be called _before_ .begin + + if (myGNSS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing...")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + // This will (re)enable the standard NMEA messages too + // This will also disable any "auto" UBX messages that were enabled and saved by other examples and reduce the load on the I2C bus + //myGNSS.factoryDefault(); delay(5000); + + myGNSS.setI2COutput(COM_TYPE_UBX | COM_TYPE_NMEA); //Set the I2C port to output both UBX and NMEA messages + + //myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Optional: save (only) the communications port settings to flash and BBR + + myGNSS.setNavigationFrequency(1); //Produce one navigation solution per second + + myGNSS.setAutoPVT(true, false); // Enable automatic NAV PVT messages: without callback; without implicit update + myGNSS.logNAVPVT(); // Enable NAV PVT data logging + + myGNSS.enableNMEAMessage(UBX_NMEA_GGA, COM_PORT_I2C, 1); // Ensure the GxGGA (Global positioning system fix data) message is enabled. Send every measurement. + myGNSS.enableNMEAMessage(UBX_NMEA_GSA, COM_PORT_I2C, 1); // Ensure the GxGSA (GNSS DOP and Active satellites) message is enabled. Send every measurement. + myGNSS.enableNMEAMessage(UBX_NMEA_GSV, COM_PORT_I2C, 1); // Ensure the GxGSV (GNSS satellites in view) message is enabled. Send every measurement. + myGNSS.logNMEA(); // Enable NMEA logging + + Serial.println(F("Press any key to stop logging.")); + + lastPrint = millis(); // Initialize lastPrint +} + +void loop() +{ + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + myGNSS.checkUblox(); // Check for the arrival of new data and process it. + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + while (myGNSS.fileBufferAvailable() >= sdWriteSize) // Check to see if we have at least sdWriteSize waiting in the buffer + { + digitalWrite(LED_BUILTIN, HIGH); // Flash LED_BUILTIN each time we write to the SD card + + uint8_t myBuffer[sdWriteSize]; // Create our own buffer to hold the data while we write it to SD card + + myGNSS.extractFileBufferData((uint8_t *)&myBuffer, sdWriteSize); // Extract exactly sdWriteSize bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, sdWriteSize); // Write exactly sdWriteSize bytes from myBuffer to the ubxDataFile on the SD card + + bytesWritten += sdWriteSize; // Update bytesWritten + + // In case the SD writing is slow or there is a lot of data to write, keep checking for the arrival of new data + myGNSS.checkUblox(); // Check for the arrival of new data and process it. + + digitalWrite(LED_BUILTIN, LOW); // Turn LED_BUILTIN off again + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if (millis() > (lastPrint + 1000)) // Print bytesWritten once per second + { + Serial.print(F("The number of bytes written to SD card is ")); // Print how many bytes have been written to SD card + Serial.println(bytesWritten); + + uint16_t maxBufferBytes = myGNSS.getMaxFileBufferAvail(); // Get how full the file buffer has been (not how full it is now) + + //Serial.print(F("The maximum number of bytes which the file buffer has contained is: ")); // It is a fun thing to watch how full the buffer gets + //Serial.println(maxBufferBytes); + + if (maxBufferBytes > ((fileBufferSize / 5) * 4)) // Warn the user if fileBufferSize was more than 80% full + { + Serial.println(F("Warning: the file buffer has been over 80% full. Some data may have been lost.")); + } + + lastPrint = millis(); // Update lastPrint + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if (Serial.available()) // Check if the user wants to stop logging + { + uint16_t remainingBytes = myGNSS.fileBufferAvailable(); // Check if there are any bytes remaining in the file buffer + + while (remainingBytes > 0) // While there is still data in the file buffer + { + digitalWrite(LED_BUILTIN, HIGH); // Flash LED_BUILTIN while we write to the SD card + + uint8_t myBuffer[sdWriteSize]; // Create our own buffer to hold the data while we write it to SD card + + uint16_t bytesToWrite = remainingBytes; // Write the remaining bytes to SD card sdWriteSize bytes at a time + if (bytesToWrite > sdWriteSize) + { + bytesToWrite = sdWriteSize; + } + + myGNSS.extractFileBufferData((uint8_t *)&myBuffer, bytesToWrite); // Extract bytesToWrite bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, bytesToWrite); // Write bytesToWrite bytes from myBuffer to the ubxDataFile on the SD card + + bytesWritten += bytesToWrite; // Update bytesWritten + + remainingBytes -= bytesToWrite; // Decrement remainingBytes + } + + digitalWrite(LED_BUILTIN, LOW); // Turn LED_BUILTIN off + + Serial.print(F("The total number of bytes written to SD card is ")); // Print how many bytes have been written to SD card + Serial.println(bytesWritten); + + myFile.close(); // Close the data file + + Serial.println(F("Logging stopped. Freezing...")); + while(1); // Do nothing more + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +} diff --git a/examples/Example25_MeasurementAndNavigationRate/Example25_MeasurementAndNavigationRate.ino b/examples/Example25_MeasurementAndNavigationRate/Example25_MeasurementAndNavigationRate.ino index c16f6ed..a67fc4e 100644 --- a/examples/Example25_MeasurementAndNavigationRate/Example25_MeasurementAndNavigationRate.ino +++ b/examples/Example25_MeasurementAndNavigationRate/Example25_MeasurementAndNavigationRate.ino @@ -81,9 +81,6 @@ void setup() while (1); } - // Another trick we can use is to mark the CFG RATE data as stale so we can be sure we read fresh data - myGNSS.packetUBXCFGRATE->moduleQueried.moduleQueried.all = 0; // Mark all of the CFG RATE data as stale - // Read and print the updated measurement rate and navigation rate rate = myGNSS.getMeasurementRate(); //Get the measurement rate of this module diff --git a/keywords.txt b/keywords.txt index f320104..f4ba8e0 100644 --- a/keywords.txt +++ b/keywords.txt @@ -382,12 +382,15 @@ initPacketUBXHNRPVT KEYWORD2 flushHNRPVT KEYWORD2 logHNRPVT KEYWORD2 +logNMEA KEYWORD2 + setNavigationFrequency KEYWORD2 getNavigationFrequency KEYWORD2 setMeasurementRate KEYWORD2 getMeasurementRate KEYWORD2 setNavigationRate KEYWORD2 getNavigationRate KEYWORD2 +flushCFGRATE KEYWORD2 getGeometricDOP KEYWORD2 getPositionDOP KEYWORD2 diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp index 2f71233..cfba10d 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp @@ -1243,6 +1243,10 @@ void SFE_UBLOX_GNSS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t r } else if (currentSentence == NMEA) { + //If _logNMEA is true, attempt to store incoming in the file buffer + if (_logNMEA) + storeFileBytes(&incoming, 1); + processNMEA(incoming); //Process each NMEA character } else if (currentSentence == RTCM) @@ -7387,38 +7391,25 @@ boolean SFE_UBLOX_GNSS::getNavigationFrequencyInternal(uint16_t maxWait) if (packetUBXCFGRATE == NULL) //Bail if the RAM allocation failed return (false); - if (packetUBXCFGRATE->automaticFlags.flags.bits.automatic && packetUBXCFGRATE->automaticFlags.flags.bits.implicitUpdate) - { - //The GPS is automatically reporting, we just check whether we got unread data - checkUbloxInternal(&packetCfg, UBX_CLASS_CFG, UBX_CFG_RATE); - return packetUBXCFGRATE->moduleQueried.moduleQueried.bits.all; - } - else if (packetUBXCFGRATE->automaticFlags.flags.bits.automatic && !packetUBXCFGRATE->automaticFlags.flags.bits.implicitUpdate) - { - //Someone else has to call checkUblox for us... - return (false); - } - else - { - //The GPS is not automatically reporting navigation rate so we have to poll explicitly - packetCfg.cls = UBX_CLASS_CFG; - packetCfg.id = UBX_CFG_RATE; - packetCfg.len = 0; - packetCfg.startingSpot = 0; - - //The data is parsed as part of processing the response - sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + // The CFG RATE message will never be produced automatically - that would be pointless. + // There is no setAutoCFGRATE function. We always need to poll explicitly. + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; - if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) - return (true); + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); - if (retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) - { - return (true); - } + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); - return (false); + if (retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) + { + return (true); } + + return (false); } // PRIVATE: Allocate RAM for packetUBXCFGRATE and initialize it @@ -7431,10 +7422,10 @@ boolean SFE_UBLOX_GNSS::initPacketUBXCFGRATE() _debugSerial->println(F("initPacketUBXCFGRATE: PANIC! RAM allocation failed!")); return (false); } - packetUBXCFGRATE->automaticFlags.flags.all = 0; - packetUBXCFGRATE->callbackPointer = NULL; - packetUBXCFGRATE->callbackData = NULL; - packetUBXCFGRATE->moduleQueried.moduleQueried.all = 0; + packetUBXCFGRATE->automaticFlags.flags.all = 0; // Redundant + packetUBXCFGRATE->callbackPointer = NULL; // Redundant + packetUBXCFGRATE->callbackData = NULL; // Redundant + packetUBXCFGRATE->moduleQueried.moduleQueried.all = 0; // Mark all data as stale/read return (true); } @@ -9086,6 +9077,14 @@ void SFE_UBLOX_GNSS::logHNRPVT(boolean enabled) packetUBXHNRPVT->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; } +// ***** Helper Functions for NMEA Logging + +//Log NMEA data in file buffer - if it exists! User needs to call setFileBufferSize before .begin +void SFE_UBLOX_GNSS::logNMEA(boolean enabled) +{ + _logNMEA = enabled; +} + // ***** CFG RATE Helper Functions //Set the rate at which the module will give us an updated navigation solution @@ -9114,7 +9113,11 @@ boolean SFE_UBLOX_GNSS::setNavigationFrequency(uint8_t navFreq, uint16_t maxWait payloadCfg[0] = measurementRate & 0xFF; //measRate LSB payloadCfg[1] = measurementRate >> 8; //measRate MSB - return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + boolean result = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + + flushCFGRATE(); // Mark the polled measurement and navigation rate data as stale + + return (result); } //Get the rate at which the module is outputting nav solutions @@ -9155,7 +9158,11 @@ boolean SFE_UBLOX_GNSS::setMeasurementRate(uint16_t rate, uint16_t maxWait) payloadCfg[0] = rate & 0xFF; //measRate LSB payloadCfg[1] = rate >> 8; //measRate MSB - return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + boolean result = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + + flushCFGRATE(); // Mark the polled measurement and navigation rate data as stale + + return (result); } //Return the elapsed time between GNSS measurements in milliseconds, which defines the rate @@ -9190,7 +9197,11 @@ boolean SFE_UBLOX_GNSS::setNavigationRate(uint16_t rate, uint16_t maxWait) payloadCfg[2] = rate & 0xFF; //navRate LSB payloadCfg[3] = rate >> 8; //navRate MSB - return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + boolean result = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + + flushCFGRATE(); // Mark the polled measurement and navigation rate data as stale + + return (result); } //Return the ratio between the number of measurements and the number of navigation solutions. Unit is cycles @@ -9208,6 +9219,13 @@ uint16_t SFE_UBLOX_GNSS::getNavigationRate(uint16_t maxWait) return (packetUBXCFGRATE->data.navRate); } +//Mark the CFG RATE data as read/stale +void SFE_UBLOX_GNSS::flushCFGRATE() +{ + if (packetUBXCFGRATE == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXCFGRATE->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + // ***** DOP Helper Functions uint16_t SFE_UBLOX_GNSS::getGeometricDOP(uint16_t maxWait) diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.h b/src/SparkFun_u-blox_GNSS_Arduino_Library.h index 8e8ab2d..06ea72c 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.h +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.h @@ -580,15 +580,15 @@ class SFE_UBLOX_GNSS boolean setPortOutput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof boolean setPortInput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof - boolean setI2CAddress(uint8_t deviceAddress, uint16_t maxTime = defaultMaxWait); //Changes the I2C address of the u-blox module + boolean setI2CAddress(uint8_t deviceAddress, uint16_t maxTime = defaultMaxWait); //Changes the I2C address of the u-blox module void setSerialRate(uint32_t baudrate, uint8_t uartPort = COM_PORT_UART1, uint16_t maxTime = defaultMaxWait); //Changes the serial baud rate of the u-blox module, uartPort should be COM_PORT_UART1/2 boolean setI2COutput(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure I2C port to output UBX, NMEA, RTCM3 or a combination thereof - boolean setUART1Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART1 port to output UBX, NMEA, RTCM3 or a combination thereof - boolean setUART2Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART2 port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUART1Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART1 port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUART2Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART2 port to output UBX, NMEA, RTCM3 or a combination thereof boolean setUSBOutput(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure USB port to output UBX, NMEA, RTCM3 or a combination thereof boolean setSPIOutput(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure SPI port to output UBX, NMEA, RTCM3 or a combination thereof - void setNMEAOutputPort(Stream &nmeaOutputPort); //Sets the internal variable for the port to direct NMEA characters to + void setNMEAOutputPort(Stream &nmeaOutputPort); //Sets the internal variable for the port to direct NMEA characters to //Reset to defaults @@ -928,6 +928,9 @@ class SFE_UBLOX_GNSS void flushHNRPVT(); //Mark all the data as read/stale void logHNRPVT(boolean enabled = true); // Log data to file buffer + // Helper function for NMEA logging + void logNMEA(boolean enabled = true); // Log NMEA data to file buffer + // Helper functions for CFG RATE boolean setNavigationFrequency(uint8_t navFreq, uint16_t maxWait = defaultMaxWait); //Set the number of nav solutions sent per second @@ -936,6 +939,7 @@ class SFE_UBLOX_GNSS uint16_t getMeasurementRate(uint16_t maxWait = defaultMaxWait); //Return the elapsed time between GNSS measurements in milliseconds boolean setNavigationRate(uint16_t rate, uint16_t maxWait = defaultMaxWait); //Set the ratio between the number of measurements and the number of navigation solutions. Unit is cycles. Max is 127 uint16_t getNavigationRate(uint16_t maxWait = defaultMaxWait); //Return the ratio between the number of measurements and the number of navigation solutions. Unit is cycles + void flushCFGRATE(); // Mark the measurement and navigation rate data as stale - used by the set rate functions // Helper functions for DOP @@ -1182,6 +1186,8 @@ class SFE_UBLOX_GNSS boolean ubx7FcheckDisabled = false; // Flag to indicate if the "7F" check should be ignored in checkUbloxI2C + boolean _logNMEA = false; // Flag to indicate if NMEA data should be added to the file buffer + //The packet buffers //These are pointed at from within the ubxPacket uint8_t payloadAck[2]; // Holds the requested ACK/NACK