diff --git a/README.md b/README.md
index 1eb267f..541f74d 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@
-u-blox makes some incredible GNSS receivers covering everything from low-cost, highly configurable modules such as the SAM-M8Q all the way up to the surveyor grade ZED-F9P with precision of the diameter of a dime. This library focuses on configuration and control of u-blox devices over I2C (called DDC by u-blox) and Serial. The UBX protocol is supported over both I2C and serial, and is a much easier and lighterweight interface to a GNSS module. Stop polling messages and parsing NMEA data! Simply ask for the datums you need and receive an automatic callback when they arrive.
+u-blox makes some incredible GNSS receivers covering everything from low-cost, highly configurable modules such as the SAM-M8Q all the way up to the surveyor grade ZED-F9P with precision of the diameter of a dime. This library supports configuration and control of u-blox devices over I2C (called DDC by u-blox), Serial and - as of v2.0.8 (thank you @aberridg) - SPI too! The UBX protocol is a much easier and lighterweight interface to a GNSS module. Stop polling messages and parsing NMEA data! Simply ask for the datums you need and receive an automatic callback when they arrive.
This library can be installed via the Arduino Library manager. Search for **SparkFun u-blox GNSS**.
@@ -53,6 +53,18 @@ Migrating to v2.0 is easy. There are two small changes all users will need to ma
If you are using the Dead Reckoning Sensor Fusion or High Dynamic Rate messages, you will need to make more small changes to your code. Please see the [dead reckoning examples](./examples/Dead_Reckoning) for more details. There is more detail available in [Theory.md](./Theory.md#migrating-your-code-to-v20) if you need it.
+## SPI Support
+
+In v2.0.8 we added support for SPI, based on a contribution by @aberridg. Thank you Andrew!
+
+We have tested the SPI interface on as many platforms and modules as we could pull together. It works perfectly on most but not quite all combinations.
+For reasons we don't understand yet, the ZED-F9P and Teensy 3.2 don't seem to get along. But Teensy 3.2 and the ZOE-M8Q do play nicely together.
+If you notice a combination that does not seem to work, please raise an [issue](https://github.com/sparkfun/SparkFun_u-blox_GNSS_Arduino_Library/issues) and we will investigate.
+
+The SPI examples have their [own folder](./examples/SPI).
+
+Please check the module datasheets for details on what clock speeds and data rates each module supports. The maximum clock speed is typically 5.5MHz and the maximum transfer rate is typically 125kBytes/s.
+
## Max (400kHz) I2C Support
To achieve 400kHz I2C speed please be sure to remove all pull-ups on the I2C bus. Most, if not all, u-blox modules include internal pull ups on the I2C lines (sometimes called DDC in their manuals). Cut all I2C pull up jumpers and/or remove them from peripheral boards. Otherwise, various data glitches can occur. See issues [38](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues/38) and [40](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues/40) for more information. If possible, run the I2C bus at 100kHz.
diff --git a/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino b/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino
index fc24259..fdef442 100644
--- a/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino
+++ b/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino
@@ -85,7 +85,7 @@ void loop()
if (myGNSS.powerSaveMode()) // Defaults to true
Serial.println(F("Power Save Mode enabled."));
else
- Serial.println(F("***!!! Power Save Mode FAILED !!!***"));
+ Serial.println(F("*** Power Save Mode FAILED ***"));
}
else if (incoming == '2')
{
@@ -93,14 +93,14 @@ void loop()
if (myGNSS.powerSaveMode(false))
Serial.println(F("Power Save Mode disabled."));
else
- Serial.println(F("***!!! Power Save Disable FAILED !!!***"));
+ Serial.println(F("*** Power Save Disable FAILED ***"));
}
// Read and print the new low power mode
uint8_t lowPowerMode = myGNSS.getPowerSaveMode();
if (lowPowerMode == 255)
{
- Serial.println(F("***!!! getPowerSaveMode FAILED !!!***"));
+ Serial.println(F("*** getPowerSaveMode FAILED ***"));
}
else
{
diff --git a/examples/Example19_DynamicModel/Example19_DynamicModel.ino b/examples/Example19_DynamicModel/Example19_DynamicModel.ino
index 9915647..1ccfc1b 100644
--- a/examples/Example19_DynamicModel/Example19_DynamicModel.ino
+++ b/examples/Example19_DynamicModel/Example19_DynamicModel.ino
@@ -68,7 +68,7 @@ void setup()
if (myGNSS.setDynamicModel(DYN_MODEL_PORTABLE) == false) // Set the dynamic model to PORTABLE
{
- Serial.println(F("***!!! Warning: setDynamicModel failed !!!***"));
+ Serial.println(F("*** Warning: setDynamicModel failed ***"));
}
else
{
@@ -79,7 +79,7 @@ void setup()
uint8_t newDynamicModel = myGNSS.getDynamicModel();
if (newDynamicModel == DYN_MODEL_UNKNOWN)
{
- Serial.println(F("***!!! Warning: getDynamicModel failed !!!***"));
+ Serial.println(F("*** Warning: getDynamicModel failed ***"));
}
else
{
diff --git a/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino b/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino
index f2c2368..55a7a23 100644
--- a/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino
+++ b/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino
@@ -39,6 +39,9 @@ void setup()
while (1);
}
+ myGNSS.setI2COutput(COM_TYPE_UBX | COM_TYPE_NMEA); //Set the I2C port to output both NMEA and UBX messages
+ myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR
+
//This will pipe all NMEA sentences to the serial port so we can see them
myGNSS.setNMEAOutputPort(Serial);
}
diff --git a/examples/Example24_GetUnixEpochAndMicros/Example24_GetUnixEpochAndMicros.ino b/examples/Example24_GetUnixEpochAndMicros/Example24_GetUnixEpochAndMicros.ino
index 2ebdc2a..8080f1a 100644
--- a/examples/Example24_GetUnixEpochAndMicros/Example24_GetUnixEpochAndMicros.ino
+++ b/examples/Example24_GetUnixEpochAndMicros/Example24_GetUnixEpochAndMicros.ino
@@ -91,6 +91,12 @@ void loop()
Serial.print(myGNSS.getSecond());
Serial.print(" Time is ");
+ if (myGNSS.getTimeFullyResolved() == false)
+ {
+ Serial.print("not fully resolved but ");
+ } else {
+ Serial.print("fully resolved and ");
+ }
if (myGNSS.getTimeValid() == false)
{
Serial.print("not ");
diff --git a/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino b/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino
index 48b691b..cf329a5 100644
--- a/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino
+++ b/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino
@@ -46,6 +46,9 @@ void setup()
while (1);
}
+ myGNSS.setI2COutput(COM_TYPE_UBX | COM_TYPE_NMEA); //Set the I2C port to output both NMEA and UBX messages
+ myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR
+
myGNSS.setProcessNMEAMask(SFE_UBLOX_FILTER_NMEA_ALL); // Make sure the library is passing all NMEA messages to processNMEA
myGNSS.setProcessNMEAMask(SFE_UBLOX_FILTER_NMEA_GGA); // Or, we can be kind to MicroNMEA and _only_ pass the GGA messages to it
diff --git a/examples/SPI/Example1_GetPosition/Example1_GetPosition.ino b/examples/SPI/Example1_GetPosition/Example1_GetPosition.ino
new file mode 100644
index 0000000..108a5ef
--- /dev/null
+++ b/examples/SPI/Example1_GetPosition/Example1_GetPosition.ino
@@ -0,0 +1,123 @@
+/*
+ Reading lat and long via UBX binary commands over SPI
+ By: Andrew Berridge
+ Date: 27th June 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 query a u-blox module for its lat/long/altitude over SPI. We also
+ turn off the NMEA output on the SPI port. This decreases the amount of SPI traffic
+ dramatically.
+
+ Note: Long/lat are large numbers because they are * 10^7. To convert lat/long
+ to something google maps understands simply divide the numbers by 10,000,000. We
+ do this so that we don't have to use floating point numbers.
+
+ Leave NMEA parsing behind. Now you can simply ask the module for the datums you want!
+
+ 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
+ NEO-M8U: https://www.sparkfun.com/products/16329
+ NEO-M9N: https://www.sparkfun.com/products/17285
+
+ Hardware Connections:
+ You need to connect the SPI pins from your microcontroller to the specific pins on your SparkFun product.
+ Connections will vary based on your microcontroller, but for reference please refer to this tutorial on SPI:
+ https://learn.sparkfun.com/tutorials/serial-peripheral-interface-spi/all
+
+ Most new boards now use the terms:
+ CS - Chip Select
+ COPI - Controller Out, Peripheral In
+ CIPO - Controller in, Peripheral Out
+ SCK - Serial Clock
+
+ You can choose any pin for Chip Select, but the others are likely either defined by the library you are using
+ (see here for the standard Arduino one: https://www.arduino.cc/en/reference/SPI) or the microcontroller. The
+ ESP32 has two standard, selectable SPI ports, for example.
+
+ To enable SPI communication, you will need to solder the DSEL/SPI jumper closed on your u-blox board.
+
+ IMPORTANT: there have been reports that some u-blox devices do not respond to the UBX protocol over SPI
+ with the factory settings. You may find you need to connect to the device via USB and u-center and set
+ the incoming protocol to UBX only. Make sure you disable all other protocols as inputs if you can't
+ get things to work! Hopefully this is just a bug in the u-blox firmware that will be fixed soon ;-)
+
+*/
+
+#include //Needed for SPI to GNSS
+
+#include //http://librarymanager/All#SparkFun_u-blox_GNSS
+SFE_UBLOX_GNSS myGNSS;
+
+// #########################################
+
+// Instantiate an instance of the SPI class.
+// Your configuration may be different, depending on the microcontroller you are using!
+
+#define spiPort SPI // This is the SPI port on standard Ardino boards. Comment this line if you want to use a different port.
+
+//SPIClass spiPort (HSPI); // This is the default SPI interface on some ESP32 boards. Uncomment this line if you are using ESP32.
+
+// #########################################
+
+const uint8_t csPin = 10; // On ATmega328 boards, SPI Chip Select is usually pin 10. Change this to match your board.
+
+// #########################################
+
+long lastTime = 0; //Simple local timer. Limits amount of SPI traffic to u-blox module.
+
+void setup()
+{
+ Serial.begin(115200);
+ while (!Serial); //Wait for user to open terminal
+ Serial.println(F("SparkFun u-blox Example"));
+
+ spiPort.begin(); // begin the SPI port
+
+ //myGNSS.enableDebugging(); // Uncomment this line to see helpful debug messages on Serial
+
+ // Connect to the u-blox module using SPI port, csPin and speed setting
+ // ublox devices generally work up to 5MHz. We'll use 4MHz for this example:
+ if (myGNSS.begin(spiPort, csPin, 4000000) == false)
+ {
+ Serial.println(F("u-blox GNSS not detected on SPI bus. Please check wiring. Freezing."));
+ while (1);
+ }
+
+ //myGNSS.factoryDefault(); delay(5000); // Uncomment this line to reset the module back to its factory defaults
+
+ myGNSS.setPortOutput(COM_PORT_SPI, COM_TYPE_UBX); //Set the SPI port to output UBX only (turn off NMEA noise)
+ myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR
+}
+
+void loop()
+{
+ //Query module only every second. Doing it more often will just cause SPI traffic.
+ //The module only responds when a new position is available
+ if (millis() - lastTime > 1000)
+ {
+ lastTime = millis(); //Update the timer
+
+ long latitude = myGNSS.getLatitude();
+ Serial.print(F("Lat: "));
+ Serial.print(latitude);
+
+ long longitude = myGNSS.getLongitude();
+ Serial.print(F(" Long: "));
+ Serial.print(longitude);
+ Serial.print(F(" (degrees * 10^-7)"));
+
+ long altitude = myGNSS.getAltitude();
+ Serial.print(F(" Alt: "));
+ Serial.print(altitude);
+ Serial.print(F(" (mm)"));
+
+ byte SIV = myGNSS.getSIV();
+ Serial.print(F(" SIV: "));
+ Serial.print(SIV);
+
+ Serial.println();
+ }
+}
diff --git a/examples/SPI/Example2_AutoPVT/Example2_AutoPVT.ino b/examples/SPI/Example2_AutoPVT/Example2_AutoPVT.ino
new file mode 100644
index 0000000..ff9d930
--- /dev/null
+++ b/examples/SPI/Example2_AutoPVT/Example2_AutoPVT.ino
@@ -0,0 +1,179 @@
+/*
+ Configuring the GNSS to automatically send position reports over SPI
+ Based on code by: Nathan Seidle and Thorsten von Eicken
+ SparkFun Electronics
+ Date: January 3rd, 2019
+ 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 the send navigation reports automatically
+ and retrieving the latest one via getPVT. This eliminates the blocking in getPVT while the GNSS
+ produces a fresh navigation solution at the expense of returning a slighly old solution.
+
+ 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
+ NEO-M8U: https://www.sparkfun.com/products/16329
+ NEO-M9N: https://www.sparkfun.com/products/17285
+
+ Hardware Connections:
+ You need to connect the SPI pins from your microcontroller to the specific pins on your SparkFun product.
+ Connections will vary based on your microcontroller, but for reference please refer to this tutorial on SPI:
+ https://learn.sparkfun.com/tutorials/serial-peripheral-interface-spi/all
+
+ Most new boards now use the terms:
+ CS - Chip Select
+ COPI - Controller Out, Peripheral In
+ CIPO - Controller in, Peripheral Out
+ SCK - Serial Clock
+
+ You can choose any pin for Chip Select, but the others are likely either defined by the library you are using
+ (see here for the standard Arduino one: https://www.arduino.cc/en/reference/SPI) or the microcontroller. The
+ ESP32 has two standard, selectable SPI ports, for example.
+
+ To enable SPI communication, you will need to solder the DSEL/SPI jumper closed on your u-blox board.
+
+ IMPORTANT: there have been reports that some u-blox devices do not respond to the UBX protocol over SPI
+ with the factory settings. You may find you need to connect to the device via USB and u-center and set
+ the incoming protocol to UBX only. Make sure you disable all other protocols as inputs if you can't
+ get things to work! Hopefully this is just a bug in the u-blox firmware that will be fixed soon ;-)
+
+*/
+
+#include //Needed for SPI to GNSS
+
+#include //http://librarymanager/All#SparkFun_u-blox_GNSS
+SFE_UBLOX_GNSS myGNSS;
+
+// #########################################
+
+// Instantiate an instance of the SPI class.
+// Your configuration may be different, depending on the microcontroller you are using!
+
+#define spiPort SPI // This is the SPI port on standard Ardino boards. Comment this line if you want to use a different port.
+
+//SPIClass spiPort (HSPI); // This is the default SPI interface on some ESP32 boards. Uncomment this line if you are using ESP32.
+
+// #########################################
+
+const uint8_t csPin = 10; // On ATmega328 boards, SPI Chip Select is usually pin 10. Change this to match your board.
+
+// #########################################
+
+void setup()
+{
+ Serial.begin(115200);
+ while (!Serial); //Wait for user to open terminal
+ Serial.println("SparkFun u-blox Example");
+
+ spiPort.begin(); // begin the SPI port
+
+ //myGNSS.enableDebugging(); // Uncomment this line to see helpful debug messages on Serial
+
+ // Connect to the u-blox module using SPI port, csPin and speed setting
+ // ublox devices generally work up to 5MHz. We'll use 4MHz for this example:
+ if (myGNSS.begin(spiPort, csPin, 4000000) == false)
+ {
+ Serial.println(F("u-blox GNSS not detected on SPI bus. Please check wiring. Freezing."));
+ while (1);
+ }
+
+ //myGNSS.factoryDefault(); delay(5000); // Uncomment this line to reset the module back to its factory defaults
+
+ myGNSS.setPortOutput(COM_PORT_SPI, COM_TYPE_UBX); //Set the SPI port to output UBX only (turn off NMEA noise)
+ myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR
+
+ myGNSS.setNavigationFrequency(2); //Produce two solutions per second
+ myGNSS.setAutoPVT(true); //Tell the GNSS to "send" each solution
+ //myGNSS.saveConfiguration(); //Optional: Save _all_ the current settings to flash and BBR
+}
+
+void loop()
+{
+ // Calling getPVT returns true if there actually is a fresh navigation solution available.
+ // Start the reading only when valid LLH is available
+ if (myGNSS.getPVT() && (myGNSS.getInvalidLlh() == false))
+ {
+ Serial.println();
+ long latitude = myGNSS.getLatitude();
+ Serial.print(F("Lat: "));
+ Serial.print(latitude);
+
+ long longitude = myGNSS.getLongitude();
+ Serial.print(F(" Long: "));
+ Serial.print(longitude);
+ Serial.print(F(" (degrees * 10^-7)"));
+
+ long altitude = myGNSS.getAltitude();
+ Serial.print(F(" Alt: "));
+ Serial.print(altitude);
+ Serial.print(F(" (mm)"));
+
+ byte SIV = myGNSS.getSIV();
+ Serial.print(F(" SIV: "));
+ Serial.print(SIV);
+
+ int PDOP = myGNSS.getPDOP();
+ Serial.print(F(" PDOP: "));
+ Serial.print(PDOP);
+ Serial.print(F(" (10^-2)"));
+
+ int nedNorthVel = myGNSS.getNedNorthVel();
+ Serial.print(F(" VelN: "));
+ Serial.print(nedNorthVel);
+ Serial.print(F(" (mm/s)"));
+
+ int nedEastVel = myGNSS.getNedEastVel();
+ Serial.print(F(" VelE: "));
+ Serial.print(nedEastVel);
+ Serial.print(F(" (mm/s)"));
+
+ int nedDownVel = myGNSS.getNedDownVel();
+ Serial.print(F(" VelD: "));
+ Serial.print(nedDownVel);
+ Serial.print(F(" (mm/s)"));
+
+ int verticalAccEst = myGNSS.getVerticalAccEst();
+ Serial.print(F(" VAccEst: "));
+ Serial.print(verticalAccEst);
+ Serial.print(F(" (mm)"));
+
+ int horizontalAccEst = myGNSS.getHorizontalAccEst();
+ Serial.print(F(" HAccEst: "));
+ Serial.print(horizontalAccEst);
+ Serial.print(F(" (mm)"));
+
+ int speedAccEst = myGNSS.getSpeedAccEst();
+ Serial.print(F(" SpeedAccEst: "));
+ Serial.print(speedAccEst);
+ Serial.print(F(" (mm/s)"));
+
+ int headAccEst = myGNSS.getHeadingAccEst();
+ Serial.print(F(" HeadAccEst: "));
+ Serial.print(headAccEst);
+ Serial.print(F(" (degrees * 10^-5)"));
+
+ if (myGNSS.getHeadVehValid() == true) {
+ int headVeh = myGNSS.getHeadVeh();
+ Serial.print(F(" HeadVeh: "));
+ Serial.print(headVeh);
+ Serial.print(F(" (degrees * 10^-5)"));
+
+ int magDec = myGNSS.getMagDec();
+ Serial.print(F(" MagDec: "));
+ Serial.print(magDec);
+ Serial.print(F(" (degrees * 10^-2)"));
+
+ int magAcc = myGNSS.getMagAcc();
+ Serial.print(F(" MagAcc: "));
+ Serial.print(magAcc);
+ Serial.print(F(" (degrees * 10^-2)"));
+ }
+
+ Serial.println();
+ } else {
+ Serial.print(".");
+ delay(50);
+ }
+}
diff --git a/keywords.txt b/keywords.txt
index b2b7cab..9862fb0 100644
--- a/keywords.txt
+++ b/keywords.txt
@@ -53,8 +53,13 @@ setPacketCfgPayloadSize KEYWORD2
begin KEYWORD2
end KEYWORD2
setI2CpollingWait KEYWORD2
+setSPIpollingWait KEYWORD2
setI2CTransactionSize KEYWORD2
getI2CTransactionSize KEYWORD2
+setSpiTransactionSize KEYWORD2
+getSpiTransactionSize KEYWORD2
+setMaxNMEAByteCount KEYWORD2
+getMaxNMEAByteCount KEYWORD2
isConnected KEYWORD2
enableDebugging KEYWORD2
disableDebugging KEYWORD2
@@ -66,6 +71,7 @@ disableUBX7Fcheck KEYWORD2
checkUblox KEYWORD2
checkUbloxI2C KEYWORD2
checkUbloxSerial KEYWORD2
+checkUbloxSPI KEYWORD2
process KEYWORD2
processNMEA KEYWORD2
@@ -425,6 +431,7 @@ getNanosecond KEYWORD2
getUnixEpoch KEYWORD2
getDateValid KEYWORD2
getTimeValid KEYWORD2
+getTimeFullyResolved KEYWORD2
getConfirmedDate KEYWORD2
getConfirmedTime KEYWORD2
getFixType KEYWORD2
diff --git a/library.properties b/library.properties
index ac25b7a..8ea9e6c 100644
--- a/library.properties
+++ b/library.properties
@@ -1,9 +1,9 @@
name=SparkFun u-blox GNSS Arduino Library
-version=2.0.7
+version=2.0.8
author=SparkFun Electronics
maintainer=SparkFun Electronics
sentence=Library for I2C and Serial Communication with u-blox GNSS modules
-paragraph=An Arduino Library to enable both I2C and Serial communication for both NMEA reception and binary UBX sending to u-blox modules. Useful for interfacing to the SparkFun GPS-RTK2 ZED-F9P, SparkFun GPS-RTK NEO-M8P-2, the SparkFun SAM-M8Q, and the SparkFun ZOE-M8Q. Library also works with other u-blox based boards.
The ZED-F9P and NEO-M8P-2 modules are top-of-the-line modules for high accuracy GNSS and GPS location solutions including RTK. The ZED-F9P is unique in that it is capable of both rover and base station operations allowing the module to become a base station and produce RTCM 3.x correction data.
+paragraph=An Arduino Library to enable I2C, Serial and SPI communication for both NMEA reception and binary UBX sending to u-blox modules. Useful for interfacing to the SparkFun GPS-RTK2 ZED-F9P, SparkFun GPS-RTK NEO-M8P-2, the SparkFun SAM-M8Q, and the SparkFun ZOE-M8Q. Library also works with other u-blox based boards.
The ZED-F9P and NEO-M8P-2 modules are top-of-the-line modules for high accuracy GNSS and GPS location solutions including RTK. The ZED-F9P is unique in that it is capable of both rover and base station operations allowing the module to become a base station and produce RTCM 3.x correction data.
category=Sensors
url=https://github.com/sparkfun/SparkFun_u-blox_GNSS_Arduino_Library
architectures=*
diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp
index bc45a0c..a3eb754 100644
--- a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp
+++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp
@@ -451,6 +451,60 @@ boolean SFE_UBLOX_GNSS::begin(Stream &serialPort)
return (connected);
}
+// Initialize for SPI
+boolean SFE_UBLOX_GNSS::begin(SPIClass &spiPort, uint8_t csPin, uint32_t spiSpeed)
+{
+ commType = COMM_TYPE_SPI;
+ _spiPort = &spiPort;
+ _csPin = csPin;
+ _spiSpeed = spiSpeed;
+
+ // Initialize the chip select pin
+ pinMode(_csPin, OUTPUT);
+ digitalWrite(_csPin, HIGH);
+
+ //New in v2.0: allocate memory for the packetCfg payload here - if required. (The user may have called setPacketCfgPayloadSize already)
+ if (packetCfgPayloadSize == 0)
+ setPacketCfgPayloadSize(MAX_PAYLOAD_SIZE);
+
+ createFileBuffer();
+
+ //Create the SPI buffer
+ if (spiBuffer == NULL) //Memory has not yet been allocated - so use new
+ {
+ spiBuffer = new uint8_t[getSpiTransactionSize()];
+ }
+
+ if (spiBuffer == NULL)
+ {
+ if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging
+ {
+ _debugSerial->print(F("begin (SPI): memory allocation failed for SPI Buffer!"));
+ return (false);
+ }
+ }
+ else
+ {
+ // Initialize/clear the SPI buffer - fill it with 0xFF as this is what is received from the UBLOX module if there's no data to be processed
+ for (uint8_t i = 0; i < getSpiTransactionSize(); i++)
+ {
+ spiBuffer[i] = 0xFF;
+ }
+ }
+
+ // Call isConnected up to three times
+ boolean connected = isConnected();
+
+ if (!connected)
+ connected = isConnected();
+
+ if (!connected)
+ connected = isConnected();
+
+ return (connected);
+}
+
+
// Allow the user to change I2C polling wait (the minimum interval between I2C data requests - to avoid pounding the bus)
// i2cPollingWait defaults to 100ms and is adjusted automatically when setNavigationFrequency()
// or setHNRNavigationRate() are called. But if the user is using callbacks, it might be advantageous
@@ -460,6 +514,13 @@ void SFE_UBLOX_GNSS::setI2CpollingWait(uint8_t newPollingWait_ms)
i2cPollingWait = newPollingWait_ms;
}
+// Allow the user to change SPI polling wait
+// (the minimum interval between SPI data requests when no data is available - to avoid pounding the bus)
+void SFE_UBLOX_GNSS::setSPIpollingWait(uint8_t newPollingWait_ms)
+{
+ spiPollingWait = newPollingWait_ms;
+}
+
//Sets the global size for I2C transactions
//Most platforms use 32 bytes (the default) but this allows users to increase the transaction
//size if the platform supports it
@@ -473,6 +534,39 @@ uint8_t SFE_UBLOX_GNSS::getI2CTransactionSize(void)
return (i2cTransactionSize);
}
+//Sets the global size for the SPI buffer/transactions.
+//Call this **before** begin()!
+//Note: if the buffer size is too small, incoming characters may be lost if the message sent
+//is larger than this buffer. If too big, you may run out of SRAM on constrained architectures!
+void SFE_UBLOX_GNSS::setSpiTransactionSize(uint8_t transactionSize)
+{
+ if (spiBuffer == NULL)
+ {
+ spiTransactionSize = transactionSize;
+ }
+ else
+ {
+ if (_printDebug == true)
+ {
+ _debugSerial->println(F("setSpiTransactionSize: you need to call setSpiTransactionSize _before_ begin!"));
+ }
+ }
+}
+uint8_t SFE_UBLOX_GNSS::getSpiTransactionSize(void)
+{
+ return (spiTransactionSize);
+}
+
+//Sets the size of maxNMEAByteCount
+void SFE_UBLOX_GNSS::setMaxNMEAByteCount(int8_t newMax)
+{
+ maxNMEAByteCount = newMax;
+}
+int8_t SFE_UBLOX_GNSS::getMaxNMEAByteCount(void)
+{
+ return (maxNMEAByteCount);
+}
+
//Returns true if I2C device ack's
boolean SFE_UBLOX_GNSS::isConnected(uint16_t maxWait)
{
@@ -577,7 +671,7 @@ const char *SFE_UBLOX_GNSS::statusString(sfe_ublox_status_e stat)
return "None";
}
-// Check for the arrival of new I2C/Serial data
+// Check for the arrival of new I2C/Serial/SPI data
//Allow the user to disable the "7F" check (e.g.) when logging RAWX data
void SFE_UBLOX_GNSS::disableUBX7Fcheck(boolean disabled)
@@ -598,6 +692,8 @@ boolean SFE_UBLOX_GNSS::checkUbloxInternal(ubxPacket *incomingUBX, uint8_t reque
return (checkUbloxI2C(incomingUBX, requestedClass, requestedID));
else if (commType == COMM_TYPE_SERIAL)
return (checkUbloxSerial(incomingUBX, requestedClass, requestedID));
+ else if (commType == COMM_TYPE_SPI)
+ return (checkUbloxSpi(incomingUBX, requestedClass, requestedID));
return false;
}
@@ -755,6 +851,45 @@ boolean SFE_UBLOX_GNSS::checkUbloxSerial(ubxPacket *incomingUBX, uint8_t request
} //end checkUbloxSerial()
+
+//Checks SPI for data, passing any new bytes to process()
+boolean SFE_UBLOX_GNSS::checkUbloxSpi(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID)
+{
+ // Process the contents of the SPI buffer if not empty!
+ for (uint8_t i = 0; i < spiBufferIndex; i++) {
+ process(spiBuffer[i], incomingUBX, requestedClass, requestedID);
+ }
+ spiBufferIndex = 0;
+
+ _spiPort->beginTransaction(SPISettings(_spiSpeed, MSBFIRST, SPI_MODE0));
+ digitalWrite(_csPin, LOW);
+ uint8_t byteReturned = _spiPort->transfer(0xFF);
+
+ // Note to future self: I think the 0xFF check might cause problems when attempting to process (e.g.) RAWX data
+ // which could legitimately contain 0xFF within the data stream. But the currentSentence check will certainly help!
+
+ // If we are not receiving a sentence (currentSentence == NONE) and the byteReturned is 0xFF,
+ // i.e. the module has no data for us, then delay for
+ if ((byteReturned == 0xFF) && (currentSentence == NONE))
+ {
+ digitalWrite(_csPin, HIGH);
+ _spiPort->endTransaction();
+ delay(spiPollingWait);
+ return (true);
+ }
+
+ while ((byteReturned != 0xFF) || (currentSentence != NONE))
+ {
+ process(byteReturned, incomingUBX, requestedClass, requestedID);
+ byteReturned = _spiPort->transfer(0xFF);
+ }
+ digitalWrite(_csPin, HIGH);
+ _spiPort->endTransaction();
+ return (true);
+
+} //end checkUbloxSpi()
+
+
//PRIVATE: Check if we have storage allocated for an incoming "automatic" message
boolean SFE_UBLOX_GNSS::checkAutomatic(uint8_t Class, uint8_t ID)
{
@@ -2409,7 +2544,7 @@ void SFE_UBLOX_GNSS::processUBXpacket(ubxPacket *msg)
{
packetUBXESFMEAS->data.data[i].data.all = extractLong(msg, 8 + (i * 4));
}
- if (msg->len > (8 + (packetUBXESFMEAS->data.flags.bits.numMeas * 4)))
+ if (msg->len > (8 + (packetUBXESFMEAS->data.flags.bits.numMeas * 4))) // IGNORE COMPILER WARNING comparison between signed and unsigned integer expressions
packetUBXESFMEAS->data.calibTtag = extractLong(msg, 8 + (packetUBXESFMEAS->data.flags.bits.numMeas * 4));
//Mark all datums as fresh (not read before)
@@ -2675,6 +2810,10 @@ sfe_ublox_status_e SFE_UBLOX_GNSS::sendCommand(ubxPacket *outgoingUBX, uint16_t
{
sendSerialCommand(outgoingUBX);
}
+ else if (commType == COMM_TYPE_SPI)
+ {
+ sendSpiCommand(outgoingUBX);
+ }
if (maxWait > 0)
{
@@ -2781,6 +2920,87 @@ void SFE_UBLOX_GNSS::sendSerialCommand(ubxPacket *outgoingUBX)
_serialPort->write(outgoingUBX->checksumB);
}
+
+// Transfer a byte to SPI. Also capture any bytes received from the UBLOX device during sending and capture them in a small buffer so that
+// they can be processed later with process
+void SFE_UBLOX_GNSS::spiTransfer(uint8_t byteToTransfer)
+{
+ uint8_t returnedByte = _spiPort->transfer(byteToTransfer);
+ if ((spiBufferIndex < getSpiTransactionSize()) && (returnedByte != 0xFF || currentSentence != NONE))
+ {
+ spiBuffer[spiBufferIndex] = returnedByte;
+ spiBufferIndex++;
+ }
+}
+
+// Send a command via SPI
+void SFE_UBLOX_GNSS::sendSpiCommand(ubxPacket *outgoingUBX)
+{
+ if (spiBuffer == NULL)
+ {
+ if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging
+ {
+ _debugSerial->print(F("sendSpiCommand: no memory allocation for SPI Buffer!"));
+ }
+ return;
+ }
+
+ // Start at the beginning of the SPI buffer
+ spiBufferIndex = 0;
+
+ _spiPort->beginTransaction(SPISettings(_spiSpeed, MSBFIRST, SPI_MODE0));
+ digitalWrite(_csPin, LOW);
+ //Write header bytes
+ spiTransfer(UBX_SYNCH_1); //μ - oh ublox, you're funny. I will call you micro-blox from now on.
+ spiTransfer(UBX_SYNCH_2); //b
+
+ spiTransfer(outgoingUBX->cls);
+ spiTransfer(outgoingUBX->id);
+ spiTransfer(outgoingUBX->len & 0xFF); //LSB
+ spiTransfer(outgoingUBX->len >> 8);
+
+ if (_printDebug)
+ {
+ _debugSerial->print(F("sendSpiCommand: "));
+ _debugSerial->print(UBX_SYNCH_1, HEX);
+ _debugSerial->print(F(" "));
+ _debugSerial->print(UBX_SYNCH_2, HEX);
+ _debugSerial->print(F(" "));
+ _debugSerial->print(outgoingUBX->cls, HEX);
+ _debugSerial->print(F(" "));
+ _debugSerial->print(outgoingUBX->id, HEX);
+ _debugSerial->print(F(" "));
+ _debugSerial->print(outgoingUBX->len & 0xFF, HEX);
+ _debugSerial->print(F(" "));
+ _debugSerial->print(outgoingUBX->len >> 8, HEX);
+ }
+
+ //Write payload.
+ for (uint16_t i = 0; i < outgoingUBX->len; i++)
+ {
+ spiTransfer(outgoingUBX->payload[i]);
+ if (_printDebug)
+ {
+ _debugSerial->print(F(" "));
+ _debugSerial->print(outgoingUBX->payload[i], HEX);
+ }
+ }
+
+ //Write checksum
+ spiTransfer(outgoingUBX->checksumA);
+ spiTransfer(outgoingUBX->checksumB);
+ digitalWrite(_csPin, HIGH);
+ _spiPort->endTransaction();
+
+ if (_printDebug)
+ {
+ _debugSerial->print(F(" "));
+ _debugSerial->print(outgoingUBX->checksumA, HEX);
+ _debugSerial->print(F(" "));
+ _debugSerial->println(outgoingUBX->checksumB, HEX);
+ }
+}
+
//Pretty prints the current ubxPacket
void SFE_UBLOX_GNSS::printPacket(ubxPacket *packet, boolean alwaysPrintPayload)
{
@@ -3004,7 +3224,7 @@ sfe_ublox_status_e SFE_UBLOX_GNSS::waitForACKResponse(ubxPacket *outgoingUBX, ui
} //checkUbloxInternal == true
- delayMicroseconds(500);
+ delay(1); // Allow an RTOS to get an elbow in (#11)
} //while (millis() - startTime < maxTime)
// We have timed out...
@@ -3114,7 +3334,7 @@ sfe_ublox_status_e SFE_UBLOX_GNSS::waitForNoACKResponse(ubxPacket *outgoingUBX,
}
}
- delayMicroseconds(500);
+ delay(1); // Allow an RTOS to get an elbow in (#11)
}
if (_printDebug == true)
@@ -3404,7 +3624,7 @@ boolean SFE_UBLOX_GNSS::pushRawData(uint8_t *dataBytes, size_t numDataBytes, boo
size_t bytesWritten = _serialPort->write(dataBytes, numDataBytes);
return (bytesWritten == numDataBytes);
}
- else
+ else if (commType == COMM_TYPE_I2C)
{
// I2C: split the data up into packets of i2cTransactionSize
size_t bytesLeftToWrite = numDataBytes;
@@ -3439,6 +3659,14 @@ boolean SFE_UBLOX_GNSS::pushRawData(uint8_t *dataBytes, size_t numDataBytes, boo
return (bytesWrittenTotal == numDataBytes);
}
+ else // SPI
+ {
+ if (_printDebug == true)
+ {
+ _debugSerial->println(F("pushRawData: SPI not currently supported"));
+ }
+ return (false);
+ }
}
// Support for data logging
@@ -9782,6 +10010,21 @@ bool SFE_UBLOX_GNSS::getTimeValid(uint16_t maxWait)
return ((bool)packetUBXNAVPVT->data.valid.bits.validTime);
}
+//Check to see if the UTC time has been fully resolved
+bool SFE_UBLOX_GNSS::getTimeFullyResolved(uint16_t maxWait)
+{
+ if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data
+ if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed
+ return (false);
+
+ if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.fullyResolved == false)
+ getPVT(maxWait);
+ packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.fullyResolved = false; //Since we are about to give this to user, mark this data as stale
+ packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false;
+ return ((bool)packetUBXNAVPVT->data.valid.bits.fullyResolved);
+}
+
+
//Get the confirmed date validity
bool SFE_UBLOX_GNSS:: getConfirmedDate(uint16_t maxWait)
{
diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.h b/src/SparkFun_u-blox_GNSS_Arduino_Library.h
index 42e72eb..691b6a9 100644
--- a/src/SparkFun_u-blox_GNSS_Arduino_Library.h
+++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.h
@@ -51,6 +51,8 @@
#include
+#include
+
#include "u-blox_config_keys.h"
#include "u-blox_structs.h"
@@ -492,6 +494,15 @@ enum sfe_ublox_ls_src_e
//#define MAX_PAYLOAD_SIZE 768 //Worst case: UBX_CFG_VALSET packet with 64 keyIDs each with 64 bit values
#endif
+// For storing SPI bytes received during sendSpiCommand
+#define SFE_UBLOX_SPI_BUFFER_SIZE 128
+
+// Default maximum NMEA byte count
+// maxNMEAByteCount was set to 82: https://en.wikipedia.org/wiki/NMEA_0183#Message_structure
+// but the u-blox HP (RTK) GGA messages are 88 bytes long
+// The user can adjust maxNMEAByteCount by calling setMaxNMEAByteCount
+#define SFE_UBLOX_MAX_NMEA_BYTE_COUNT 88
+
//-=-=-=-=- UBX binary specific variables
struct ubxPacket
{
@@ -560,17 +571,29 @@ class SFE_UBLOX_GNSS
boolean begin(TwoWire &wirePort = Wire, uint8_t deviceAddress = 0x42); //Returns true if module is detected
//serialPort needs to be perviously initialized to correct baud rate
boolean begin(Stream &serialPort); //Returns true if module is detected
+ //SPI - supply instance of SPIClass, chip select pin and SPI speed (in Hz)
+ boolean begin(SPIClass &spiPort, uint8_t csPin, uint32_t spiSpeed);
void end(void); //Stop all automatic message processing. Free all used RAM
void setI2CpollingWait(uint8_t newPollingWait_ms); // Allow the user to change the I2C polling wait if required
+ void setSPIpollingWait(uint8_t newPollingWait_ms); // Allow the user to change the SPI polling wait if required
+
+ //Set the max number of bytes set in a given I2C transaction
+ uint8_t i2cTransactionSize = 32; //Default to ATmega328 limit
//Control the size of the internal I2C transaction amount
void setI2CTransactionSize(uint8_t bufferSize);
uint8_t getI2CTransactionSize(void);
- //Set the max number of bytes set in a given I2C transaction
- uint8_t i2cTransactionSize = 32; //Default to ATmega328 limit
+ //Control the size of the spi buffer. If the buffer isn't big enough, we'll start to lose bytes
+ //That we receive if the buffer is full!
+ void setSpiTransactionSize(uint8_t bufferSize);
+ uint8_t getSpiTransactionSize(void);
+
+ //Control the size of maxNMEAByteCount
+ void setMaxNMEAByteCount(int8_t newMax);
+ int8_t getMaxNMEAByteCount(void);
//Returns true if device answers on _gpsI2Caddress address or via Serial
boolean isConnected(uint16_t maxWait = 1100);
@@ -612,6 +635,7 @@ class SFE_UBLOX_GNSS
boolean checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for I2C polling of data, passing any new bytes to process()
boolean checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for serial polling of data, passing any new bytes to process()
+ boolean checkUbloxSpi(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for spi polling of data, passing any new bytes to process()
// Process the incoming data
@@ -622,12 +646,13 @@ class SFE_UBLOX_GNSS
void processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Given a character, file it away into the uxb packet structure
void processUBXpacket(ubxPacket *msg); //Once a packet has been received and validated, identify this packet's class/id and update internal flags
- // Send I2C/Serial commands to the module
+ // Send I2C/Serial/SPI commands to the module
void calcChecksum(ubxPacket *msg); //Sets the checksumA and checksumB of a given messages
sfe_ublox_status_e sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait = defaultMaxWait, boolean expectACKonly = false); //Given a packet and payload, send everything including CRC bytes, return true if we got a response
sfe_ublox_status_e sendI2cCommand(ubxPacket *outgoingUBX, uint16_t maxWait = defaultMaxWait);
void sendSerialCommand(ubxPacket *outgoingUBX);
+ void sendSpiCommand(ubxPacket *outgoingUBX);
void printPacket(ubxPacket *packet, boolean alwaysPrintPayload = false); //Useful for debugging
@@ -1061,6 +1086,7 @@ class SFE_UBLOX_GNSS
bool getDateValid(uint16_t maxWait = defaultMaxWait);
bool getTimeValid(uint16_t maxWait = defaultMaxWait);
+ bool getTimeFullyResolved(uint16_t maxWait = defaultMaxWait);
bool getConfirmedDate(uint16_t maxWait = defaultMaxWait);
bool getConfirmedTime(uint16_t maxWait = defaultMaxWait);
@@ -1235,6 +1261,9 @@ class SFE_UBLOX_GNSS
//Calculate how much RAM is needed to store the payload for a given automatic message
uint16_t getMaxPayloadSize(uint8_t Class, uint8_t ID);
+ //Do the actual transfer to SPI
+ void spiTransfer(uint8_t byteToTransfer);
+
boolean initGeofenceParams(); // Allocate RAM for currentGeofenceParams and initialize it
boolean initModuleSWVersion(); // Allocate RAM for moduleSWVersion and initialize it
@@ -1273,6 +1302,10 @@ class SFE_UBLOX_GNSS
Stream *_nmeaOutputPort = NULL; //The user can assign an output port to print NMEA sentences if they wish
Stream *_debugSerial; //The stream to send debug messages to if enabled
+ SPIClass *_spiPort; //The instance of SPIClass
+ uint8_t _csPin; //The chip select pin
+ uint32_t _spiSpeed; //The speed to use for SPI (Hz)
+
uint8_t _gpsI2Caddress = 0x42; //Default 7-bit unshifted address of the ublox 6/7/8/M8/F9 series
//This can be changed using the ublox configuration software
@@ -1292,6 +1325,10 @@ class SFE_UBLOX_GNSS
uint8_t *payloadCfg = NULL;
uint8_t *payloadAuto = NULL;
+ uint8_t *spiBuffer = NULL; // A buffer to store any bytes being recieved back from the device while we are sending via SPI
+ uint8_t spiBufferIndex = 0; // Index into the SPI buffer
+ uint8_t spiTransactionSize = SFE_UBLOX_SPI_BUFFER_SIZE; //Default size of the SPI buffer
+
//Init the packet structures and init them with pointers to the payloadAck, payloadCfg, payloadBuf and payloadAuto arrays
ubxPacket packetAck = {0, 0, 0, 0, 0, payloadAck, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED};
ubxPacket packetBuf = {0, 0, 0, 0, 0, payloadBuf, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED};
@@ -1308,10 +1345,14 @@ class SFE_UBLOX_GNSS
sfe_ublox_packet_buffer_e activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF;
//Limit checking of new data to every X ms
- //If we are expecting an update every X Hz then we should check every half that amount of time
+ //If we are expecting an update every X Hz then we should check every quarter that amount of time
//Otherwise we may block ourselves from seeing new data
uint8_t i2cPollingWait = 100; //Default to 100ms. Adjusted when user calls setNavigationFrequency() or setHNRNavigationRate() or setMeasurementRate()
+ //The SPI polling wait is a little different. checkUbloxSpi will delay for this amount before returning if
+ //there is no data waiting to be read. This prevents waitForACKResponse from pounding the SPI bus too hard.
+ uint8_t spiPollingWait = 9; //Default to 9ms; waitForACKResponse delays for 1ms on top of this. User can adjust with setSPIPollingWait.
+
unsigned long lastCheck = 0;
uint16_t ubxFrameCounter; //Count all UBX frame bytes. [Fixed header(2bytes), CLS(1byte), ID(1byte), length(2bytes), payload(x bytes), checksums(2bytes)]
@@ -1319,7 +1360,9 @@ class SFE_UBLOX_GNSS
uint8_t rollingChecksumB; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes
int8_t nmeaByteCounter; //Count all NMEA message bytes.
- const int8_t maxNMEAByteCount = 82; // Abort NMEA message reception if nmeaByteCounter exceeds this (https://en.wikipedia.org/wiki/NMEA_0183#Message_structure)
+ // Abort NMEA message reception if nmeaByteCounter exceeds maxNMEAByteCount.
+ // The user can adjust maxNMEAByteCount by calling setMaxNMEAByteCount
+ int8_t maxNMEAByteCount = SFE_UBLOX_MAX_NMEA_BYTE_COUNT;
uint8_t nmeaAddressField[6]; // NMEA Address Field - includes the start character (*)
boolean logThisNMEA(); // Return true if we should log this NMEA message
boolean processThisNMEA(); // Return true if we should pass this NMEA message to processNMEA