@@ -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 <SPI.h>
|
||||
#include <SD.h>
|
||||
#include <Wire.h> //Needed for I2C to GNSS
|
||||
|
||||
#include <SparkFun_u-blox_GNSS_Arduino_Library.h> //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
|
||||
}
|
||||
|
||||
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
}
|
||||
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,20 +7391,8 @@ 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
|
||||
// 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;
|
||||
@@ -7418,7 +7410,6 @@ boolean SFE_UBLOX_GNSS::getNavigationFrequencyInternal(uint16_t maxWait)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -928,6 +928,9 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ private:
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user