diff --git a/README.md b/README.md index be11801..3936f47 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Thanks to: * [dotMorten](https://github.com/dotMorten) for the MSGOUT keys, autoHPPOSLLH, autoDOP and upgrades to autoPVT. * [markuckermann](https://github.com/markuckermann) for spotting the config layer gremlins * [vid553](https://github.com/vid553) for the Zephyr port -* [balamuruganky](https://github.com/balamuruganky) for the NAV-PVT velocity parameters +* [balamuruganky](https://github.com/balamuruganky) for the NAV-PVT velocity parameters, getSpeedAccEst, getHeadingAccEst, getInvalidLlh, getHeadVeh, getMagDec and getMagAcc * [nelarsen](https://github.com/nelarsen) for the buffer overrun improvements * [mstranne](https://github.com/mstranne) and [shaneperera](https://github.com/shaneperera) for the pushRawData suggestion * [rubienr](https://github.com/rubienr) for spotting the logical AND issues @@ -113,6 +113,21 @@ packets in its internal buffer (about 500 bytes) and the library will read those called, update its internal copy of the nav data 5 times, and return `true` to the sketch. The sketch calls `getLatitude`, etc. and retrieve the data of the most recent of those 5 packets. +The library also supports: +* `autoHPPOSLLH` +* `autoDOP` +* `autoHNRAtt` +* `autoHNRDyn` +* `autoHNRPVT` + +Memory Usage +--------------------------------- + +Version 1.8.9 introduced support for `autoHNR` on the NEO-M8U, and that tipped the balance in terms of RAM use on the ATmega328. +The library does still run on the ATmega328 but you will see _**Low memory available, stability problems may occur**_ warnings +as the global variables now occupy 1540 bytes of RAM. If you do want to run this library on the ATmega328, you may need to regress +to Version 1.8.8 via the Library Manager. + Products That Use This Library --------------------------------- * [GPS-16481](https://www.sparkfun.com/products/16481) - SparkFun GPS-RTK-SMA Breakout - ZED-F9P (Qwiic) diff --git a/examples/Dead Reckoning/Example5_getHNRData/Example5_getHNRData.ino b/examples/Dead Reckoning/Example5_getHNRData/Example5_getHNRData.ino new file mode 100644 index 0000000..70c1efc --- /dev/null +++ b/examples/Dead Reckoning/Example5_getHNRData/Example5_getHNRData.ino @@ -0,0 +1,90 @@ +/* + By: Paul Clark + SparkFun Electronics + Date: December, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example configures the High Navigation Rate on the NEO-M8U and then + polls and displays the attitude solution, vehicle dynamics information + and high rate position, velocity and time. + + This example polls the high rate data. + (The next example uses "autoHNR" to receive the HNR data automatically.) + + Please make sure your NEO-M8U is running UDR firmware >= 1.31. Please update using u-center if necessary: + https://www.u-blox.com/en/product/neo-m8u-module#tab-documentation-resources + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + 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 GPS + +#include //http://librarymanager/All#SparkFun_Ublox_GPS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages on Serial + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Warning! u-blox GPS did not begin correctly.")); + Serial.println(F("(This may be because the I2C port is busy with HNR messages.)")); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + if (myGPS.setHNRNavigationRate(10) == true) //Set the High Navigation Rate to 10Hz + Serial.println(F("setHNRNavigationRate was successful")); + else + Serial.println(F("setHNRNavigationRate was NOT successful")); +} + +void loop() +{ + // Poll and print selected HNR data + if (myGPS.getHNRAtt(125) == true) // Request HNR Att data using a 125ms timeout + { + Serial.print(F("Roll: ")); + Serial.print(myGPS.hnrAtt.roll); + Serial.print(F(" Pitch: ")); + Serial.print(myGPS.hnrAtt.pitch); + Serial.print(F(" Heading: ")); + Serial.println(myGPS.hnrAtt.heading); + } + if (myGPS.getHNRDyn(125) == true) // Request HNR Dyn data using a 125ms timeout + { + Serial.print(F("xAccel: ")); + Serial.print(myGPS.hnrVehDyn.xAccel); + Serial.print(F(" yAccel: ")); + Serial.print(myGPS.hnrVehDyn.yAccel); + Serial.print(F(" zAccel: ")); + Serial.println(myGPS.hnrVehDyn.zAccel); + } + if (myGPS.getHNRPVT(125) == true) // Request HNR PVT data using a 125ms timeout + { + Serial.print(F("ns: ")); + Serial.print(myGPS.hnrPVT.nano); + Serial.print(F(" Lat: ")); + Serial.print(myGPS.hnrPVT.lat); + Serial.print(F(" Lon: ")); + Serial.println(myGPS.hnrPVT.lon); + } +} diff --git a/examples/Dead Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino b/examples/Dead Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino new file mode 100644 index 0000000..e191dfe --- /dev/null +++ b/examples/Dead Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino @@ -0,0 +1,96 @@ +/* + By: Paul Clark + SparkFun Electronics + Date: December, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example configures the High Navigation Rate on the NEO-M8U and then + reads and displays the attitude solution, vehicle dynamics information + and high rate position, velocity and time. + + This example uses "autoHNR" to receive the HNR data automatically. + + Please make sure your NEO-M8U is running UDR firmware >= 1.31. Please update using u-center if necessary: + https://www.u-blox.com/en/product/neo-m8u-module#tab-documentation-resources + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + 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 GPS + +#include //http://librarymanager/All#SparkFun_Ublox_GPS +SFE_UBLOX_GPS myGPS; + +boolean usingAutoHNRAtt = false; +boolean usingAutoHNRDyn = false; +boolean usingAutoHNRPVT = false; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages on Serial + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Warning! u-blox GPS did not begin correctly.")); + Serial.println(F("(This may be because the I2C port is busy with HNR messages.)")); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + if (myGPS.setHNRNavigationRate(10) == true) //Set the High Navigation Rate to 10Hz + Serial.println(F("setHNRNavigationRate was successful")); + else + Serial.println(F("setHNRNavigationRate was NOT successful")); + + usingAutoHNRAtt = myGPS.setAutoHNRAtt(true); //Attempt to enable auto HNR attitude messages + usingAutoHNRDyn = myGPS.setAutoHNRDyn(true); //Attempt to enable auto HNR vehicle dynamics messages + usingAutoHNRPVT = myGPS.setAutoHNRPVT(true); //Attempt to enable auto HNR PVT messages +} + +void loop() +{ + if (usingAutoHNRAtt && (myGPS.getHNRAtt() == true)) // If setAutoHNRAtt was successful and new data is available + { + Serial.print(F("Roll: ")); // Print selected data + Serial.print(myGPS.hnrAtt.roll); + Serial.print(F(" Pitch: ")); + Serial.print(myGPS.hnrAtt.pitch); + Serial.print(F(" Heading: ")); + Serial.println(myGPS.hnrAtt.heading); + } + if (usingAutoHNRDyn && (myGPS.getHNRDyn() == true)) // If setAutoHNRDyn was successful and new data is available + { + Serial.print(F("xAccel: ")); // Print selected data + Serial.print(myGPS.hnrVehDyn.xAccel); + Serial.print(F(" yAccel: ")); + Serial.print(myGPS.hnrVehDyn.yAccel); + Serial.print(F(" zAccel: ")); + Serial.println(myGPS.hnrVehDyn.zAccel); + } + if (usingAutoHNRPVT && (myGPS.getHNRPVT() == true)) // If setAutoHNRPVT was successful and new data is available + { + Serial.print(F("ns: ")); // Print selected data + Serial.print(myGPS.hnrPVT.nano); + Serial.print(F(" Lat: ")); + Serial.print(myGPS.hnrPVT.lat); + Serial.print(F(" Lon: ")); + Serial.println(myGPS.hnrPVT.lon); + } +} diff --git a/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino b/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino index b5dd97e..0370e90 100644 --- a/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino +++ b/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino @@ -54,7 +54,8 @@ void setup() void loop() { // Calling getPVT returns true if there actually is a fresh navigation solution available. - if (myGPS.getPVT()) + // Start the reading only when valid LLH is available + if (myGPS.getPVT() && (myGPS.getInvalidLlh() == false)) { Serial.println(); long latitude = myGPS.getLatitude(); @@ -105,6 +106,33 @@ void loop() Serial.print(horizontalAccEst); Serial.print(F(" (mm)")); + int speedAccEst = myGPS.getSpeedAccEst(); + Serial.print(F(" SpeedAccEst: ")); + Serial.print(speedAccEst); + Serial.print(F(" (mm/s)")); + + int headAccEst = myGPS.getHeadingAccEst(); + Serial.print(F(" HeadAccEst: ")); + Serial.print(headAccEst); + Serial.print(F(" (degrees * 10^-5)")); + + if (myGPS.getHeadVehValid() == true) { + int headVeh = myGPS.getHeadVeh(); + Serial.print(F(" HeadVeh: ")); + Serial.print(headVeh); + Serial.print(F(" (degrees * 10^-5)")); + + int magDec = myGPS.getMagDec(); + Serial.print(F(" MagDec: ")); + Serial.print(magDec); + Serial.print(F(" (degrees * 10^-2)")); + + int magAcc = myGPS.getMagAcc(); + Serial.print(F(" MagAcc: ")); + Serial.print(magAcc); + Serial.print(F(" (degrees * 10^-2)")); + } + Serial.println(); } else { Serial.print("."); diff --git a/keywords.txt b/keywords.txt index 39981df..99345c1 100644 --- a/keywords.txt +++ b/keywords.txt @@ -58,6 +58,13 @@ getVerticalAccEst KEYWORD2 getNedNorthVel KEYWORD2 getNedEastVel KEYWORD2 getNedDownVel KEYWORD2 +getSpeedAccEst KEYWORD2 +getHeadingAccEst KEYWORD2 +getInvalidLlh KEYWORD2 +getHeadVeh KEYWORD2 +getMagDec KEYWORD2 +getMagAcc KEYWORD2 +getHeadVehValid KEYWORD2 setPortOutput KEYWORD2 setPortInput KEYWORD2 @@ -175,6 +182,18 @@ setStaticPosition KEYWORD2 pushRawData KEYWORD2 +setHNRNavigationRate KEYWORD2 +getHNRNavigationRate KEYWORD2 +assumeAutoHNRAtt KEYWORD2 +setAutoHNRAtt KEYWORD2 +getHNRAtt KEYWORD2 +assumeAutoHNRDyn KEYWORD2 +setAutoHNRDyn KEYWORD2 +getHNRDyn KEYWORD2 +assumeAutoHNRPVT KEYWORD2 +setAutoHNRPVT KEYWORD2 +getHNRPVT KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### diff --git a/library.properties b/library.properties index 4773b05..a3f6858 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SparkFun u-blox Arduino Library -version=1.8.8 +version=1.8.9 author=SparkFun Electronics maintainer=SparkFun Electronics sentence=Library for I2C and Serial Communication with u-blox modules diff --git a/src/SparkFun_Ublox_Arduino_Library.cpp b/src/SparkFun_Ublox_Arduino_Library.cpp index a42bed3..224305d 100644 --- a/src/SparkFun_Ublox_Arduino_Library.cpp +++ b/src/SparkFun_Ublox_Arduino_Library.cpp @@ -554,7 +554,7 @@ void SFE_UBLOX_GPS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t re //So let's check for an HPPOSLLH message arriving when we were expecting PVT and vice versa else if ((packetBuf.cls == requestedClass) && (((packetBuf.id == UBX_NAV_PVT) && (requestedID == UBX_NAV_HPPOSLLH || requestedID == UBX_NAV_DOP)) || - ((packetBuf.id == UBX_NAV_HPPOSLLH) && (requestedID == UBX_NAV_PVT || requestedID == UBX_NAV_DOP)) || + ((packetBuf.id == UBX_NAV_HPPOSLLH) && (requestedID == UBX_NAV_PVT || requestedID == UBX_NAV_DOP)) || ((packetBuf.id == UBX_NAV_DOP) && (requestedID == UBX_NAV_PVT || requestedID == UBX_NAV_HPPOSLLH)))) { //This is not the message we were expecting but we start diverting data into incomingUBX (usually packetCfg) and process it anyway @@ -564,7 +564,25 @@ void SFE_UBLOX_GPS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t re incomingUBX->counter = packetBuf.counter; //Copy over the .counter too if (_printDebug == true) { - _debugSerial->print(F("process: auto PVT/HPPOSLLH/DOP collision: Requested ID: 0x")); + _debugSerial->print(F("process: auto NAV PVT/HPPOSLLH/DOP collision: Requested ID: 0x")); + _debugSerial->print(requestedID, HEX); + _debugSerial->print(F(" Message ID: 0x")); + _debugSerial->println(packetBuf.id, HEX); + } + } + else if ((packetBuf.cls == requestedClass) && + (((packetBuf.id == UBX_HNR_ATT) && (requestedID == UBX_HNR_INS || requestedID == UBX_HNR_PVT)) || + ((packetBuf.id == UBX_HNR_INS) && (requestedID == UBX_HNR_ATT || requestedID == UBX_HNR_PVT)) || + ((packetBuf.id == UBX_HNR_PVT) && (requestedID == UBX_HNR_ATT || requestedID == UBX_HNR_INS)))) + { + //This is not the message we were expecting but we start diverting data into incomingUBX (usually packetCfg) and process it anyway + activePacketBuffer = SFE_UBLOX_PACKET_PACKETCFG; + incomingUBX->cls = packetBuf.cls; //Copy the class and ID into incomingUBX (usually packetCfg) + incomingUBX->id = packetBuf.id; + incomingUBX->counter = packetBuf.counter; //Copy over the .counter too + if (_printDebug == true) + { + _debugSerial->print(F("process: auto HNR ATT/INS/PVT collision: Requested ID: 0x")); _debugSerial->print(requestedID, HEX); _debugSerial->print(F(" Message ID: 0x")); _debugSerial->println(packetBuf.id, HEX); @@ -837,12 +855,28 @@ void SFE_UBLOX_GPS::processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t // Let's say so and leave incomingUBX->classAndIDmatch _unchanged_ if (_printDebug == true) { - _debugSerial->print(F("processUBX: auto PVT/HPPOSLLH/DOP collision: Requested ID: 0x")); + _debugSerial->print(F("processUBX: auto NAV PVT/HPPOSLLH/DOP collision: Requested ID: 0x")); _debugSerial->print(requestedID, HEX); _debugSerial->print(F(" Message ID: 0x")); _debugSerial->println(incomingUBX->id, HEX); } } + // Let's do the same for the HNR messages + else if ((incomingUBX->cls == requestedClass) && + (((incomingUBX->id == UBX_HNR_ATT) && (requestedID == UBX_HNR_INS || requestedID == UBX_HNR_PVT)) || + ((incomingUBX->id == UBX_HNR_INS) && (requestedID == UBX_HNR_ATT || requestedID == UBX_HNR_PVT)) || + ((incomingUBX->id == UBX_HNR_PVT) && (requestedID == UBX_HNR_ATT || requestedID == UBX_HNR_INS)))) + { + // This isn't the message we are looking for... + // Let's say so and leave incomingUBX->classAndIDmatch _unchanged_ + if (_printDebug == true) + { + _debugSerial->print(F("processUBX: auto HNR ATT/INS/PVT collision: Requested ID: 0x")); + _debugSerial->print(requestedID, HEX); + _debugSerial->print(F(" Message ID: 0x")); + _debugSerial->println(incomingUBX->id, HEX); + } + } if (_printDebug == true) { @@ -993,8 +1027,9 @@ void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) fixType = extractByte(20 - startingSpot); gnssFixOk = extractByte(21 - startingSpot) & 0x1; //Get the 1st bit - diffSoln = extractByte(21 - startingSpot) >> 1 & 0x1; //Get the 2nd bit + diffSoln = (extractByte(21 - startingSpot) >> 1) & 0x1; //Get the 2nd bit carrierSolution = extractByte(21 - startingSpot) >> 6; //Get 6th&7th bits of this byte + headVehValid = (extractByte(21 - startingSpot) >> 5) & 0x1; // Get the 5th bit SIV = extractByte(23 - startingSpot); longitude = extractSignedLong(24 - startingSpot); latitude = extractSignedLong(28 - startingSpot); @@ -1005,10 +1040,15 @@ void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) nedNorthVel = extractSignedLong(48 - startingSpot); nedEastVel = extractSignedLong(52 - startingSpot); nedDownVel = extractSignedLong(56 - startingSpot); - groundSpeed = extractSignedLong(60 - startingSpot); headingOfMotion = extractSignedLong(64 - startingSpot); + speedAccEst = extractLong(68 - startingSpot); + headingAccEst = extractLong(72 - startingSpot); pDOP = extractInt(76 - startingSpot); + invalidLlh = extractByte(78 - startingSpot) & 0x1; + headVeh = extractSignedLong(84 - startingSpot); + magDec = extractSignedInt(88 - startingSpot); + magAcc = extractInt(90 - startingSpot); //Mark all datums as fresh (not read before) moduleQueried.gpsiTOW = true; @@ -1025,23 +1065,28 @@ void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) moduleQueried.all = true; moduleQueried.gnssFixOk = true; moduleQueried.diffSoln = true; + moduleQueried.headVehValid = true; moduleQueried.longitude = true; moduleQueried.latitude = true; moduleQueried.altitude = true; moduleQueried.altitudeMSL = true; - moduleQueried.horizontalAccEst = true; moduleQueried.verticalAccEst = true; moduleQueried.nedNorthVel = true; moduleQueried.nedEastVel = true; moduleQueried.nedDownVel = true; - moduleQueried.SIV = true; moduleQueried.fixType = true; moduleQueried.carrierSolution = true; moduleQueried.groundSpeed = true; moduleQueried.headingOfMotion = true; + moduleQueried.speedAccEst = true; + moduleQueried.headingAccEst = true; moduleQueried.pDOP = true; + moduleQueried.invalidLlh = true; + moduleQueried.headVeh = true; + moduleQueried.magDec = true; + moduleQueried.magAcc = true; } else if (msg->id == UBX_NAV_HPPOSLLH && msg->len == 36) { @@ -1128,6 +1173,80 @@ void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) dopModuleQueried.eastingDOP = true; } break; + case UBX_CLASS_HNR: + if (msg->id == UBX_HNR_ATT && msg->len == 32) + { + //Parse various byte fields into global vars + hnrAtt.iTOW = extractLong(0); + hnrAtt.roll = extractSignedLong(8); + hnrAtt.pitch = extractSignedLong(12); + hnrAtt.heading = extractSignedLong(16); + hnrAtt.accRoll = extractLong(20); + hnrAtt.accPitch = extractLong(24); + hnrAtt.accHeading = extractLong(28); + + hnrAttQueried = true; + } + else if (msg->id == UBX_HNR_INS && msg->len == 36) + { + //Parse various byte fields into global vars + hnrVehDyn.iTOW = extractLong(8); + hnrVehDyn.xAngRate = extractSignedLong(12); + hnrVehDyn.yAngRate = extractSignedLong(16); + hnrVehDyn.zAngRate = extractSignedLong(20); + hnrVehDyn.xAccel = extractSignedLong(24); + hnrVehDyn.yAccel = extractSignedLong(28); + hnrVehDyn.zAccel = extractSignedLong(32); + + uint32_t bitfield0 = extractLong(0); + hnrVehDyn.xAngRateValid = (bitfield0 & 0x00000100) > 0; + hnrVehDyn.yAngRateValid = (bitfield0 & 0x00000200) > 0; + hnrVehDyn.zAngRateValid = (bitfield0 & 0x00000400) > 0; + hnrVehDyn.xAccelValid = (bitfield0 & 0x00000800) > 0; + hnrVehDyn.yAccelValid = (bitfield0 & 0x00001000) > 0; + hnrVehDyn.zAccelValid = (bitfield0 & 0x00002000) > 0; + + hnrDynQueried = true; + } + else if (msg->id == UBX_HNR_PVT && msg->len == 72) + { + //Parse various byte fields into global vars + hnrPVT.iTOW = extractLong(0); + hnrPVT.year = extractInt(4); + hnrPVT.month = extractByte(6); + hnrPVT.day = extractByte(7); + hnrPVT.hour = extractByte(8); + hnrPVT.min = extractByte(9); + hnrPVT.sec = extractByte(10); + hnrPVT.nano = extractSignedLong(12); + hnrPVT.gpsFix = extractByte(16); + hnrPVT.lon = extractSignedLong(20); + hnrPVT.lat = extractSignedLong(24); + hnrPVT.height = extractSignedLong(28); + hnrPVT.hMSL = extractSignedLong(32); + hnrPVT.gSpeed = extractSignedLong(36); + hnrPVT.speed = extractSignedLong(40); + hnrPVT.headMot = extractSignedLong(44); + hnrPVT.headVeh = extractSignedLong(48); + hnrPVT.hAcc = extractLong(52); + hnrPVT.vAcc = extractLong(56); + hnrPVT.sAcc = extractLong(60); + hnrPVT.headAcc = extractLong(64); + + uint8_t valid = extractByte(11); + hnrPVT.validDate = (valid & 0x01) > 0; + hnrPVT.validTime = (valid & 0x02) > 0; + hnrPVT.fullyResolved = (valid & 0x04) > 0; + + uint8_t flags = extractByte(17); + hnrPVT.gpsFixOK = (flags & 0x01) > 0; + hnrPVT.diffSoln = (flags & 0x02) > 0; + hnrPVT.WKNSET = (flags & 0x04) > 0; + hnrPVT.TOWSET = (flags & 0x08) > 0; + hnrPVT.headVehValid = (flags & 0x10) > 0; + + hnrPVTQueried = true; + } } } @@ -2323,7 +2442,7 @@ boolean SFE_UBLOX_GPS::setNavigationFrequency(uint8_t navFreq, uint16_t maxWait) //if(updateRate > 40) updateRate = 40; //Not needed: module will correct out of bounds values //Adjust the I2C polling timeout based on update rate - i2cPollingWait = 1000 / (navFreq * 4); //This is the number of ms to wait between checks for new I2C data + i2cPollingWait = 1000 / (((int)navFreq) * 4); //This is the number of ms to wait between checks for new I2C data //Query the module for the latest lat/long packetCfg.cls = UBX_CLASS_CFG; @@ -3003,6 +3122,19 @@ uint16_t SFE_UBLOX_GPS::extractInt(uint8_t spotToStart) return (val); } +//Just so there is no ambiguity about whether a uint16_t will cast to a int16_t correctly... +int16_t SFE_UBLOX_GPS::extractSignedInt(int8_t spotToStart) +{ + union // Use a union to convert from uint16_t to int16_t + { + uint16_t unsignedInt; + int16_t signedInt; + } stSignedInt; + + stSignedInt.unsignedInt = extractInt(spotToStart); + return (stSignedInt.signedInt); +} + //Given a spot, extract a byte from the payload uint8_t SFE_UBLOX_GPS::extractByte(uint8_t spotToStart) { @@ -3087,6 +3219,54 @@ bool SFE_UBLOX_GPS::getTimeValid(uint16_t maxWait) return (gpsTimeValid); } +uint32_t SFE_UBLOX_GPS::getSpeedAccEst(uint16_t maxWait) +{ + if (moduleQueried.speedAccEst == false) + getPVT(maxWait); + moduleQueried.speedAccEst = false; //Since we are about to give this to user, mark this data as stale + return (speedAccEst); +} + +uint32_t SFE_UBLOX_GPS::getHeadingAccEst(uint16_t maxWait) +{ + if (moduleQueried.headingAccEst == false) + getPVT(maxWait); + moduleQueried.headingAccEst = false; //Since we are about to give this to user, mark this data as stale + return (headingAccEst); +} + +bool SFE_UBLOX_GPS::getInvalidLlh(uint16_t maxWait) +{ + if (moduleQueried.invalidLlh == false) + getPVT(maxWait); + moduleQueried.invalidLlh = false; //Since we are about to give this to user, mark this data as stale + return (invalidLlh); +} + +int32_t SFE_UBLOX_GPS::getHeadVeh(uint16_t maxWait) +{ + if (moduleQueried.headVeh == false) + getPVT(maxWait); + moduleQueried.headVeh = false; //Since we are about to give this to user, mark this data as stale + return (headVeh); +} + +int16_t SFE_UBLOX_GPS::getMagDec(uint16_t maxWait) +{ + if (moduleQueried.magDec == false) + getPVT(maxWait); + moduleQueried.magDec = false; //Since we are about to give this to user, mark this data as stale + return (magDec); +} + +uint16_t SFE_UBLOX_GPS::getMagAcc(uint16_t maxWait) +{ + if (moduleQueried.magAcc == false) + getPVT(maxWait); + moduleQueried.magAcc = false; //Since we are about to give this to user, mark this data as stale + return (magAcc); +} + //Get the current millisecond uint16_t SFE_UBLOX_GPS::getMillisecond(uint16_t maxWait) { @@ -3146,22 +3326,22 @@ boolean SFE_UBLOX_GPS::getPVT(uint16_t maxWait) if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) return (true); - if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.id == UBX_NAV_HPPOSLLH)) + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) { if (_printDebug == true) { - _debugSerial->println(F("getPVT: data was OVERWRITTEN by HPPOSLLH (but that's OK)")); + _debugSerial->println(F("getPVT: data was OVERWRITTEN by another NAV message (but that's OK)")); } return (true); } - if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.id == UBX_NAV_DOP)) + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) { if (_printDebug == true) { - _debugSerial->println(F("getPVT: data was OVERWRITTEN by DOP (but that's OK)")); + _debugSerial->println(F("getPVT: data was OVERWRITTEN by a HNR message (and that's not OK)")); } - return (true); + return (false); } if (_printDebug == true) @@ -3331,21 +3511,21 @@ boolean SFE_UBLOX_GPS::getHPPOSLLH(uint16_t maxWait) if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) return (true); - if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.id == UBX_NAV_PVT)) + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) { if (_printDebug == true) { - _debugSerial->println(F("getHPPOSLLH: data was OVERWRITTEN by PVT (but that's OK)")); + _debugSerial->println(F("getHPPOSLLH: data was OVERWRITTEN by another NAV message (but that's OK)")); } return (true); } - if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.id == UBX_NAV_DOP)) + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) { if (_printDebug == true) { - _debugSerial->println(F("getHPPOSLLH: data was OVERWRITTEN by DOP (but that's OK)")); + _debugSerial->println(F("getHPPOSLLH: data was OVERWRITTEN by a HNR message (and that's not OK)")); } - return (true); + return (false); } if (_printDebug == true) @@ -3466,22 +3646,22 @@ boolean SFE_UBLOX_GPS::getDOP(uint16_t maxWait) if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) return (true); - if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.id == UBX_NAV_PVT)) + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) { if (_printDebug == true) { - _debugSerial->println(F("getHPPOSLLH: data was OVERWRITTEN by PVT (but that's OK)")); + _debugSerial->println(F("getDOP: data was OVERWRITTEN by another NAV message (but that's OK)")); } return (true); } - if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.id == UBX_NAV_HPPOSLLH)) + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) { if (_printDebug == true) { - _debugSerial->println(F("getPVT: data was OVERWRITTEN by HPPOSLLH (but that's OK)")); + _debugSerial->println(F("getDOP: data was OVERWRITTEN by a HNR message (and that's not OK)")); } - return (true); + return (false); } if (_printDebug == true) @@ -3492,6 +3672,7 @@ boolean SFE_UBLOX_GPS::getDOP(uint16_t maxWait) return (false); } } + //Get the current 3D high precision positional accuracy - a fun thing to watch //Returns a long representing the 3D accuracy in millimeters uint32_t SFE_UBLOX_GPS::getPositionAccuracy(uint16_t maxWait) @@ -3671,6 +3852,18 @@ uint8_t SFE_UBLOX_GPS::getCarrierSolutionType(uint16_t maxWait) return (carrierSolution); } +//Get whether head vehicle valid or not +bool SFE_UBLOX_GPS::getHeadVehValid(uint16_t maxWait) +{ + if (moduleQueried.headVehValid == false) + getPVT(maxWait); + moduleQueried.headVehValid = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (headVehValid); +} + + //Get the ground speed in mm/s int32_t SFE_UBLOX_GPS::getGroundSpeed(uint16_t maxWait) { @@ -3792,6 +3985,7 @@ void SFE_UBLOX_GPS::flushPVT() moduleQueried.all = false; moduleQueried.gnssFixOk = false; moduleQueried.diffSoln = false; + moduleQueried.headVehValid = false; moduleQueried.longitude = false; moduleQueried.latitude = false; moduleQueried.altitude = false; @@ -3801,7 +3995,13 @@ void SFE_UBLOX_GPS::flushPVT() moduleQueried.carrierSolution = false; moduleQueried.groundSpeed = false; moduleQueried.headingOfMotion = false; + moduleQueried.speedAccEst = false; + moduleQueried.headingAccEst = false; moduleQueried.pDOP = false; + moduleQueried.invalidLlh = false; + moduleQueried.headVeh = false; + moduleQueried.magDec = false; + moduleQueried.magAcc = false; } //Mark all the HPPOSLLH data as read/stale. This is handy to get data alignment after CRC failure @@ -3950,7 +4150,7 @@ boolean SFE_UBLOX_GPS::getEsfInfo(uint16_t maxWait) if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) return (false); //If command send fails then bail - checkUblox(); + //checkUblox(); // payload should be loaded. imuMeas.version = extractByte(4); @@ -3972,7 +4172,7 @@ boolean SFE_UBLOX_GPS::getEsfIns(uint16_t maxWait) if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) return (false); //If command send fails then bail - checkUblox(); + //checkUblox(); // Validity of each sensor value below uint32_t validity = extractLong(0); @@ -4007,7 +4207,7 @@ boolean SFE_UBLOX_GPS::getEsfDataInfo(uint16_t maxWait) if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) return (false); //If command send fails then bail - checkUblox(); + //checkUblox(); uint32_t timeStamp = extractLong(0); uint32_t flags = extractInt(4); @@ -4058,7 +4258,7 @@ boolean SFE_UBLOX_GPS::getEsfRawDataInfo(uint16_t maxWait) if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) return (false); //If command send fails then bail - checkUblox(); + //checkUblox(); uint32_t bitField = extractLong(4); imuMeas.rawDataType = (bitField & 0xFF000000) >> 24; @@ -4086,7 +4286,7 @@ sfe_ublox_status_e SFE_UBLOX_GPS::getSensState(uint8_t sensor, uint16_t maxWait) if (sensor > ubloxSen.numSens) return (SFE_UBLOX_STATUS_OUT_OF_RANGE); - checkUblox(); + //checkUblox(); uint8_t offset = 4; @@ -4126,7 +4326,7 @@ boolean SFE_UBLOX_GPS::getVehAtt(uint16_t maxWait) if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) return (SFE_UBLOX_STATUS_FAIL); //If command send fails then bail - checkUblox(); + //checkUblox(); vehAtt.roll = extractSignedLong(8); // 0.00001 deg vehAtt.pitch = extractSignedLong(12); // 0.00001 deg @@ -4245,3 +4445,406 @@ boolean SFE_UBLOX_GPS::pushRawData(uint8_t *dataBytes, size_t numDataBytes) return (bytesWrittenTotal == numDataBytes); } } + +// Set the High Navigation Rate +// Returns true if the setHNRNavigationRate is successful +boolean SFE_UBLOX_GPS::setHNRNavigationRate(uint8_t rate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_HNR; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current HNR settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); + + //Load the new navigation rate into payloadCfg + payloadCfg[0] = rate; + + //Update the navigation rate + sfe_ublox_status_e result = sendCommand(&packetCfg, maxWait); // We are only expecting an ACK + + //Adjust the I2C polling timeout based on update rate + if (result == SFE_UBLOX_STATUS_DATA_SENT) + i2cPollingWait = 1000 / (((int)rate) * 4); //This is the number of ms to wait between checks for new I2C data + + return (result == SFE_UBLOX_STATUS_DATA_SENT); +} + +// Get the High Navigation Rate +// Returns 0 if the getHNRNavigationRate fails +uint8_t SFE_UBLOX_GPS::getHNRNavigationRate(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_HNR; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current HNR settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (0); + + //Return the navigation rate + return (payloadCfg[0]); +} + +//In case no config access to the GPS is possible and HNR attitude is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoHNRAtt(boolean enabled, boolean implicitUpdate) +{ + boolean changes = autoHNRAtt != enabled || autoHNRAttImplicitUpdate != implicitUpdate; + if (changes) + { + autoHNRAtt = enabled; + autoHNRAttImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic HNR attitude message generation by the GPS. This changes the way getHNRAtt +//works. +boolean SFE_UBLOX_GPS::setAutoHNRAtt(boolean enable, uint16_t maxWait) +{ + return setAutoHNRAtt(enable, true, maxWait); +} + +//Enable or disable automatic HNR attitude message generation by the GPS. This changes the way getHNRAtt +//works. +boolean SFE_UBLOX_GPS::setAutoHNRAtt(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_HNR; + payloadCfg[1] = UBX_HNR_ATT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoHNRAtt = enable; + autoHNRAttImplicitUpdate = implicitUpdate; + } + hnrAttQueried = false; // Mark data as stale + return ok; +} + +//Get the HNR Attitude data +// Returns true if the get HNR attitude is successful. Data is returned in hnrAtt +// Note: if hnrAttQueried is true, it gets set to false by this function since we assume +// that the user will read hnrAtt immediately after this. I.e. this function will +// only return true _once_ after each auto HNR Att is processed +boolean SFE_UBLOX_GPS::getHNRAtt(uint16_t maxWait) +{ + if (autoHNRAtt && autoHNRAttImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + _debugSerial->println(F("getHNRAtt: Autoreporting")); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_HNR, UBX_HNR_ATT); + if (hnrAttQueried) + { + hnrAttQueried = false; // Mark data as stale as we assume the user will read it after this + return true; + } + return false; + } + else if (autoHNRAtt && !autoHNRAttImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + _debugSerial->println(F("getHNRAtt: Exit immediately")); + } + return (false); + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRAtt: Polling")); + } + + //The GPS is not automatically reporting HNR attitude so we have to poll explicitly + packetCfg.cls = UBX_CLASS_HNR; + packetCfg.id = UBX_HNR_ATT; + packetCfg.len = 0; + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRAtt: data was OVERWRITTEN by a NAV message (and that's not OK)")); + } + return (false); + } + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRAtt: data was OVERWRITTEN by another HNR message (but that's OK)")); + } + return (true); + } + + if (_printDebug == true) + { + _debugSerial->print(F("getHNRAtt retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//In case no config access to the GPS is possible and HNR vehicle dynamics is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoHNRDyn(boolean enabled, boolean implicitUpdate) +{ + boolean changes = autoHNRDyn != enabled || autoHNRDynImplicitUpdate != implicitUpdate; + if (changes) + { + autoHNRDyn = enabled; + autoHNRDynImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic HNR vehicle dynamics message generation by the GPS. This changes the way getHNRDyn +//works. +boolean SFE_UBLOX_GPS::setAutoHNRDyn(boolean enable, uint16_t maxWait) +{ + return setAutoHNRDyn(enable, true, maxWait); +} + +//Enable or disable automatic HNR vehicle dynamics message generation by the GPS. This changes the way getHNRDyn +//works. +boolean SFE_UBLOX_GPS::setAutoHNRDyn(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_HNR; + payloadCfg[1] = UBX_HNR_INS; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoHNRDyn = enable; + autoHNRDynImplicitUpdate = implicitUpdate; + } + hnrDynQueried = false; // Mark data as stale + return ok; +} + +//Get the HNR vehicle dynamics data +// Returns true if the get HNR vehicle dynamics is successful. Data is returned in hnrVehDyn +// Note: if hnrDynQueried is true, it gets set to false by this function since we assume +// that the user will read hnrVehDyn immediately after this. I.e. this function will +// only return true _once_ after each auto HNR Dyn is processed +boolean SFE_UBLOX_GPS::getHNRDyn(uint16_t maxWait) +{ + if (autoHNRDyn && autoHNRDynImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + _debugSerial->println(F("getHNRDyn: Autoreporting")); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_HNR, UBX_HNR_INS); + if (hnrDynQueried) + { + hnrDynQueried = false; // Mark data as stale as we assume the user will read it after this + return true; + } + return false; + } + else if (autoHNRDyn && !autoHNRDynImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + _debugSerial->println(F("getHNRDyn: Exit immediately")); + } + return (false); + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRDyn: Polling")); + } + + //The GPS is not automatically reporting HNR vehicle dynamics so we have to poll explicitly + packetCfg.cls = UBX_CLASS_HNR; + packetCfg.id = UBX_HNR_INS; + packetCfg.len = 0; + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRDyn: data was OVERWRITTEN by a NAV message (and that's not OK)")); + } + return (false); + } + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRDyn: data was OVERWRITTEN by another HNR message (but that's OK)")); + } + return (true); + } + + if (_printDebug == true) + { + _debugSerial->print(F("getHNRDyn retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//In case no config access to the GPS is possible and HNR PVT is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoHNRPVT(boolean enabled, boolean implicitUpdate) +{ + boolean changes = autoHNRPVT != enabled || autoHNRPVTImplicitUpdate != implicitUpdate; + if (changes) + { + autoHNRPVT = enabled; + autoHNRPVTImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic HNR PVT message generation by the GPS. This changes the way getHNRPVT +//works. +boolean SFE_UBLOX_GPS::setAutoHNRPVT(boolean enable, uint16_t maxWait) +{ + return setAutoHNRPVT(enable, true, maxWait); +} + +//Enable or disable automatic HNR PVT message generation by the GPS. This changes the way getHNRPVT +//works. +boolean SFE_UBLOX_GPS::setAutoHNRPVT(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_HNR; + payloadCfg[1] = UBX_HNR_PVT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoHNRPVT = enable; + autoHNRPVTImplicitUpdate = implicitUpdate; + } + hnrPVTQueried = false; // Mark data as stale + return ok; +} + +//Get the HNR PVT data +// Returns true if the get HNR PVT is successful. Data is returned in hnrPVT +// Note: if hnrPVTQueried is true, it gets set to false by this function since we assume +// that the user will read hnrPVT immediately after this. I.e. this function will +// only return true _once_ after each auto HNR PVT is processed +boolean SFE_UBLOX_GPS::getHNRPVT(uint16_t maxWait) +{ + if (autoHNRPVT && autoHNRPVTImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + _debugSerial->println(F("getHNRPVT: Autoreporting")); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_HNR, UBX_HNR_PVT); + if (hnrPVTQueried) + { + hnrPVTQueried = false; // Mark data as stale as we assume the user will read it after this + return true; + } + return false; + } + else if (autoHNRPVT && !autoHNRPVTImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + _debugSerial->println(F("getHNRPVT: Exit immediately")); + } + return (false); + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRPVT: Polling")); + } + + //The GPS is not automatically reporting HNR PVT so we have to poll explicitly + packetCfg.cls = UBX_CLASS_HNR; + packetCfg.id = UBX_HNR_PVT; + packetCfg.len = 0; + + //The data is parsed as part of processing the response + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_NAV)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRPVT: data was OVERWRITTEN by a NAV message (and that's not OK)")); + } + return (false); + } + + if ((retVal == SFE_UBLOX_STATUS_DATA_OVERWRITTEN) && (packetCfg.cls == UBX_CLASS_HNR)) + { + if (_printDebug == true) + { + _debugSerial->println(F("getHNRPVT: data was OVERWRITTEN by another HNR message (but that's OK)")); + } + return (true); + } + + if (_printDebug == true) + { + _debugSerial->print(F("getHNRPVT retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); + } + + return (false); // Trap. We should never get here... +} diff --git a/src/SparkFun_Ublox_Arduino_Library.h b/src/SparkFun_Ublox_Arduino_Library.h index 75ce1dd..9a3e250 100644 --- a/src/SparkFun_Ublox_Arduino_Library.h +++ b/src/SparkFun_Ublox_Arduino_Library.h @@ -122,8 +122,12 @@ const uint8_t UBX_CFG_BATCH = 0x93; //Get/set data batching configuration. const uint8_t UBX_CFG_CFG = 0x09; //Clear, Save, and Load Configurations. Used to save current configuration const uint8_t UBX_CFG_DAT = 0x06; //Set User-defined Datum or The currently defined Datum const uint8_t UBX_CFG_DGNSS = 0x70; //DGNSS configuration +const uint8_t UBX_CFG_ESFALG = 0x56; //ESF alignment +const uint8_t UBX_CFG_ESFA = 0x4C; //ESF accelerometer +const uint8_t UBX_CFG_ESFG = 0x4D; //ESF gyro const uint8_t UBX_CFG_GEOFENCE = 0x69; //Geofencing configuration. Used to configure a geofence const uint8_t UBX_CFG_GNSS = 0x3E; //GNSS system configuration +const uint8_t UBX_CFG_HNR = 0x5C; //High Navigation Rate const uint8_t UBX_CFG_INF = 0x02; //Depending on packet length, either: poll configuration for one protocol, or information message configuration const uint8_t UBX_CFG_ITFM = 0x39; //Jamming/Interference Monitor configuration const uint8_t UBX_CFG_LOGFILTER = 0x47; //Data Logger Configuration @@ -180,6 +184,11 @@ const uint8_t UBX_NMEA_MAINTALKERID_GB = 0x05; //main talker ID is BeiDou const uint8_t UBX_NMEA_GSVTALKERID_GNSS = 0x00; //GNSS specific Talker ID (as defined by NMEA) const uint8_t UBX_NMEA_GSVTALKERID_MAIN = 0x01; //use the main Talker ID +//The following are used to configure the HNR message rates +const uint8_t UBX_HNR_ATT = 0x01; //HNR Attitude +const uint8_t UBX_HNR_INS = 0x02; //HNR Vehicle Dynamics +const uint8_t UBX_HNR_PVT = 0x00; //HNR PVT + //The following are used to configure INF UBX messages (information messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) const uint8_t UBX_INF_CLASS = 0x04; //All INF messages have 0x04 as the class const uint8_t UBX_INF_DEBUG = 0x04; //ASCII output with debug contents @@ -482,8 +491,8 @@ class SFE_UBLOX_GPS boolean assumeAutoPVT(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and PVT is send cyclically already boolean setAutoPVT(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency - boolean getPVT(uint16_t maxWait = getPVTmaxWait); //Query module for latest group of datums and load global vars: lat, long, alt, speed, SIV, accuracies, etc. If autoPVT is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new PVT is available. boolean setAutoPVT(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean getPVT(uint16_t maxWait = getPVTmaxWait); //Query module for latest group of datums and load global vars: lat, long, alt, speed, SIV, accuracies, etc. If autoPVT is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new PVT is available. boolean assumeAutoHPPOSLLH(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HPPOSLLH is send cyclically already boolean setAutoHPPOSLLH(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HPPOSLLH reports at the navigation frequency boolean setAutoHPPOSLLH(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HPPOSLLH reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update @@ -498,17 +507,16 @@ class SFE_UBLOX_GPS bool getGnssFixOk(uint16_t maxWait = getPVTmaxWait); //Get whether we have a valid fix (i.e within DOP & accuracy masks) bool getDiffSoln(uint16_t maxWait = getPVTmaxWait); //Get whether differential corrections were applied + bool getHeadVehValid(uint16_t maxWait = getPVTmaxWait); int32_t getLatitude(uint16_t maxWait = getPVTmaxWait); //Returns the current latitude in degrees * 10^-7. Auto selects between HighPrecision and Regular depending on ability of module. int32_t getLongitude(uint16_t maxWait = getPVTmaxWait); //Returns the current longitude in degrees * 10-7. Auto selects between HighPrecision and Regular depending on ability of module. int32_t getAltitude(uint16_t maxWait = getPVTmaxWait); //Returns the current altitude in mm above ellipsoid int32_t getAltitudeMSL(uint16_t maxWait = getPVTmaxWait); //Returns the current altitude in mm above mean sea level - int32_t getHorizontalAccEst(uint16_t maxWait = getPVTmaxWait); int32_t getVerticalAccEst(uint16_t maxWait = getPVTmaxWait); int32_t getNedNorthVel(uint16_t maxWait = getPVTmaxWait); int32_t getNedEastVel(uint16_t maxWait = getPVTmaxWait); int32_t getNedDownVel(uint16_t maxWait = getPVTmaxWait); - uint8_t getSIV(uint16_t maxWait = getPVTmaxWait); //Returns number of sats used in fix uint8_t getFixType(uint16_t maxWait = getPVTmaxWait); //Returns the type of fix: 0=no, 3=3D, 4=GNSS+Deadreckoning uint8_t getCarrierSolutionType(uint16_t maxWait = getPVTmaxWait); //Returns RTK solution: 0=no, 1=float solution, 2=fixed solution @@ -526,6 +534,12 @@ class SFE_UBLOX_GPS uint32_t getTimeOfWeek(uint16_t maxWait = getPVTmaxWait); bool getDateValid(uint16_t maxWait = getPVTmaxWait); bool getTimeValid(uint16_t maxWait = getPVTmaxWait); + uint32_t getSpeedAccEst(uint16_t maxWait = getPVTmaxWait); + uint32_t getHeadingAccEst(uint16_t maxWait = getPVTmaxWait); + bool getInvalidLlh(uint16_t maxWait = getPVTmaxWait); + int32_t getHeadVeh(uint16_t maxWait = getPVTmaxWait); + int16_t getMagDec(uint16_t maxWait = getPVTmaxWait); + uint16_t getMagAcc(uint16_t maxWait = getPVTmaxWait); int32_t getHighResLatitude(uint16_t maxWait = getHPPOSLLHmaxWait); int8_t getHighResLatitudeHp(uint16_t maxWait = getHPPOSLLHmaxWait); @@ -721,9 +735,9 @@ class SFE_UBLOX_GPS bool gpsDateValid; bool gpsTimeValid; - bool gnssFixOk; //valid fix (i.e within DOP & accuracy masks) bool diffSoln; //Differential corrections were applied + bool headVehValid; int32_t latitude; //Degrees * 10^-7 (more accurate than floats) int32_t longitude; //Degrees * 10^-7 (more accurate than floats) int32_t altitude; //Number of mm above ellipsoid @@ -738,7 +752,13 @@ class SFE_UBLOX_GPS uint8_t carrierSolution; //Tells us when we have an RTK float/fixed solution int32_t groundSpeed; //mm/s int32_t headingOfMotion; //degrees * 10^-5 + uint32_t speedAccEst; + uint32_t headingAccEst; uint16_t pDOP; //Positional dilution of precision * 10^-2 (dimensionless) + bool invalidLlh; + int32_t headVeh; + int16_t magDec; + uint16_t magAcc; uint8_t versionLow; //Loaded from getProtocolVersion(). uint8_t versionHigh; @@ -757,13 +777,13 @@ class SFE_UBLOX_GPS uint16_t rtcmFrameCounter = 0; //Tracks the type of incoming byte inside RTCM frame -uint16_t geometricDOP; // Geometric dilution of precision * 10^-2 -uint16_t positionDOP; // Posoition dilution of precision * 10^-2 -uint16_t timeDOP; // Time dilution of precision * 10^-2 -uint16_t verticalDOP; // Vertical dilution of precision * 10^-2 -uint16_t horizontalDOP; // Horizontal dilution of precision * 10^-2 -uint16_t northingDOP; // Northing dilution of precision * 10^-2 -uint16_t eastingDOP; // Easting dilution of precision * 10^-2 + uint16_t geometricDOP; // Geometric dilution of precision * 10^-2 + uint16_t positionDOP; // Posoition dilution of precision * 10^-2 + uint16_t timeDOP; // Time dilution of precision * 10^-2 + uint16_t verticalDOP; // Vertical dilution of precision * 10^-2 + uint16_t horizontalDOP; // Horizontal dilution of precision * 10^-2 + uint16_t northingDOP; // Northing dilution of precision * 10^-2 + uint16_t eastingDOP; // Easting dilution of precision * 10^-2 #define DEF_NUM_SENS 7 struct deadReckData @@ -826,6 +846,84 @@ uint16_t eastingDOP; // Easting dilution of precision * 10^-2 uint32_t accHeading; } vehAtt; + //HNR-specific structs + struct hnrAttitudeSolution + { + uint32_t iTOW; + int32_t roll; // Degrees * 1e-5 + int32_t pitch; // Degrees * 1e-5 + int32_t heading; // Degrees * 1e-5 + uint32_t accRoll; // Degrees * 1e-5 + uint32_t accPitch; // Degrees * 1e-5 + uint32_t accHeading; // Degrees * 1e-5 + } hnrAtt; + + struct hnrVehicleDynamics + { + boolean xAngRateValid; + boolean yAngRateValid; + boolean zAngRateValid; + boolean xAccelValid; + boolean yAccelValid; + boolean zAccelValid; + uint32_t iTOW; + int32_t xAngRate; // Degrees/s * 1e-3 + int32_t yAngRate; // Degrees/s * 1e-3 + int32_t zAngRate; // Degrees/s * 1e-3 + int32_t xAccel; // m/s^2 * 1e-2 + int32_t yAccel; // m/s^2 * 1e-2 + int32_t zAccel; // m/s^2 * 1e-2 + } hnrVehDyn; + + struct hnrPosVelTime + { + uint32_t iTOW; + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint8_t sec; + boolean validDate; + boolean validTime; + boolean fullyResolved; + int32_t nano; + uint8_t gpsFix; + boolean gpsFixOK; + boolean diffSoln; + boolean WKNSET; + boolean TOWSET; + boolean headVehValid; + int32_t lon; // Degrees * 1e-7 + int32_t lat; // Degrees * 1e-7 + int32_t height; // mm above ellipsoid + int32_t hMSL; // mm above MSL + int32_t gSpeed; // mm/s 2D + int32_t speed; // mm/s 3D + int32_t headMot; // Degrees * 1e-5 + int32_t headVeh; // Degrees * 1e-5 + uint32_t hAcc; // mm + uint32_t vAcc; // mm + uint32_t sAcc; // mm + uint32_t headAcc; // Degrees * 1e-5 + } hnrPVT; + + //HNR functions + boolean setHNRNavigationRate(uint8_t rate, uint16_t maxWait = 1100); // Returns true if the setHNRNavigationRate is successful + uint8_t getHNRNavigationRate(uint16_t maxWait = 1100); // Returns 0 if the getHNRNavigationRate fails + boolean assumeAutoHNRAtt(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HNR Attitude is send cyclically already + boolean setAutoHNRAtt(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR Attitude reports at the HNR rate + boolean setAutoHNRAtt(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR Attitude reports at the HNR rate, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean getHNRAtt(uint16_t maxWait = 1100); // Returns true if the get HNR attitude is successful. Data is returned in hnrAtt + boolean assumeAutoHNRDyn(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HNR dynamics is send cyclically already + boolean setAutoHNRDyn(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR dynamics reports at the HNR rate + boolean setAutoHNRDyn(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR dynamics reports at the HNR rate, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean getHNRDyn(uint16_t maxWait = 1100); // Returns true if the get HNR dynamics is successful. Data is returned in hnrVehDyn + boolean assumeAutoHNRPVT(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HNR PVT is send cyclically already + boolean setAutoHNRPVT(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR PVT reports at the HNR rate + boolean setAutoHNRPVT(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR PVT reports at the HNR rate, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean getHNRPVT(uint16_t maxWait = 1100); // Returns true if the get HNR PVT is successful. Data is returned in hnrPVT + private: //Depending on the sentence type the processor will load characters into different arrays enum SentenceTypes @@ -856,6 +954,7 @@ uint16_t eastingDOP; // Easting dilution of precision * 10^-2 uint32_t extractLong(uint8_t spotToStart); //Combine four bytes from payload into long int32_t extractSignedLong(uint8_t spotToStart); //Combine four bytes from payload into signed long (avoiding any ambiguity caused by casting) uint16_t extractInt(uint8_t spotToStart); //Combine two bytes from payload into int + int16_t extractSignedInt(int8_t spotToStart); uint8_t extractByte(uint8_t spotToStart); //Get byte from payload int8_t extractSignedChar(uint8_t spotToStart); //Get signed 8-bit value from payload void addToChecksum(uint8_t incoming); //Given an incoming byte, adjust rollingChecksumA/B @@ -903,6 +1002,12 @@ uint16_t eastingDOP; // Easting dilution of precision * 10^-2 boolean autoHPPOSLLHImplicitUpdate = true; // Whether autoHPPOSLLH is triggered by accessing stale data (=true) or by a call to checkUblox (=false) boolean autoDOP = false; //Whether autoDOP is enabled or not boolean autoDOPImplicitUpdate = true; // Whether autoDOP is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + boolean autoHNRAtt = false; //Whether auto HNR attitude is enabled or not + boolean autoHNRAttImplicitUpdate = true; // Whether auto HNR attitude is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + boolean autoHNRDyn = false; //Whether auto HNR dynamics is enabled or not + boolean autoHNRDynImplicitUpdate = true; // Whether auto HNR dynamics is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + boolean autoHNRPVT = false; //Whether auto HNR PVT is enabled or not + boolean autoHNRPVTImplicitUpdate = true; // Whether auto HNR PVT is triggered by accessing stale data (=true) or by a call to checkUblox (=false) uint16_t ubxFrameCounter; //It counts all UBX frame. [Fixed header(2bytes), CLS(1byte), ID(1byte), length(2bytes), payload(x bytes), checksums(2bytes)] @@ -929,23 +1034,28 @@ uint16_t eastingDOP; // Easting dilution of precision * 10^-2 uint32_t all : 1; uint32_t gnssFixOk : 1; uint32_t diffSoln : 1; + uint32_t headVehValid : 1; uint32_t longitude : 1; uint32_t latitude : 1; uint32_t altitude : 1; uint32_t altitudeMSL : 1; - uint32_t horizontalAccEst : 1; uint32_t verticalAccEst : 1; uint32_t nedNorthVel : 1; uint32_t nedEastVel : 1; uint32_t nedDownVel : 1; - uint32_t SIV : 1; uint32_t fixType : 1; uint32_t carrierSolution : 1; uint32_t groundSpeed : 1; uint32_t headingOfMotion : 1; + uint32_t speedAccEst : 1; + uint32_t headingAccEst : 1; uint32_t pDOP : 1; + uint32_t invalidLlh : 1; + uint32_t headVeh : 1; + uint32_t magDec : 1; + uint32_t magAcc : 1; uint32_t versionNumber : 1; } moduleQueried; @@ -978,6 +1088,10 @@ uint16_t eastingDOP; // Easting dilution of precision * 10^-2 uint16_t eastingDOP : 1; } dopModuleQueried; + boolean hnrAttQueried; + boolean hnrDynQueried; + boolean hnrPVTQueried; + uint16_t rtcmLen = 0; };