diff --git a/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino b/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino index 3919959..5b5fb32 100644 --- a/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino +++ b/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino @@ -35,7 +35,8 @@ SFE_UBLOX_GPS myGPS; void setup() { Serial.begin(115200); - while (!Serial); //Wait for user to open terminal + while (!Serial) + ; //Wait for user to open terminal Serial.println("SparkFun Ublox Example"); Wire.begin(); @@ -43,17 +44,18 @@ void setup() if (myGPS.begin() == false) //Connect to the Ublox module using Wire port { Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); - while (1); + while (1) + ; } myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) - myGPS.setNavigationFrequency(2); //Produce two solutions per second - myGPS.setAutoPVT(true, false); //Tell the GPS to "send" each solution and the lib not to update stale data implicitly - myGPS.saveConfiguration(); //Save the current settings to flash and BBR + myGPS.setNavigationFrequency(2); //Produce two solutions per second + myGPS.setAutoPVT(true, false); //Tell the GPS to "send" each solution and the lib not to update stale data implicitly + myGPS.saveConfiguration(); //Save the current settings to flash and BBR } /* - Calling getPVT would return false now (compare to Example 13 where it would return true), so we just use the data provided + Calling getPVT would return false now (compare to previous example where it would return true), so we just use the data provided If you are using a threaded OS eg. FreeRTOS on an ESP32, the explicit mode of autoPVT allows you to use the data provided on both cores and inside multiple threads The data update in background creates an inconsistent state, but that should not cause issues for most applications as they usually won't change the GPS location significantly within a 2Hz - 5Hz update rate. Also you could oversample (10Hz - 20Hz) the data to smooth out such issues... @@ -62,12 +64,14 @@ void loop() { static uint16_t counter = 0; - if (counter % 10 == 0) { + if (counter % 10 == 0) + { // update your AHRS filter here for a ~100Hz update rate // GPS data will be quasi static but data from your IMU will be changing } // debug output each half second - if (counter % 500 == 0) { + if (counter % 500 == 0) + { Serial.println(); long latitude = myGPS.getLatitude(); Serial.print(F("Lat: ")); @@ -90,7 +94,8 @@ void loop() Serial.println(); } // call checkUblox all 50ms to capture the gps data - if (counter % 50 == 0) { + if (counter % 50 == 0) + { myGPS.checkUblox(); } delay(1); diff --git a/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino b/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino index 9ec5eaa..3862c6d 100644 --- a/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino +++ b/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino @@ -33,14 +33,14 @@ long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox m void setup() { - Serial.begin(500000); //Increase serial speed to maximize + Serial.begin(500000); //Increase serial speed to maximize while (!Serial) ; //Wait for user to open terminal Serial.println("SparkFun Ublox Example"); Wire.begin(); Wire.setClock(400000); - + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port { Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); @@ -53,18 +53,16 @@ void setup() //myGPS.enableDebugging(); //Enable debug messages over Serial (default) - myGPS.setNavigationFrequency(10); //Set output to 10 times a second + myGPS.setNavigationFrequency(10); //Set output to 10 times a second byte rate = myGPS.getNavigationFrequency(); //Get the update rate of this module Serial.print("Current update rate:"); Serial.println(rate); - } void loop() { - //Query module only every second. Doing it more often will just cause I2C traffic. - //The module only responds when a new position is available - if (millis() - lastTime > 10) + // Calling getPVT returns true if there actually is a fresh navigation solution available. + if (myGPS.getPVT()) { lastTime = millis(); //Update the timer @@ -86,6 +84,7 @@ void loop() Serial.print(F(" SIV: ")); Serial.print(SIV); + Serial.print(" "); Serial.print(myGPS.getYear()); Serial.print("-"); Serial.print(myGPS.getMonth()); @@ -100,6 +99,8 @@ void loop() Serial.print("."); Serial.print(myGPS.getNanosecond()); + myGPS.flushPVT(); + Serial.println(); } } diff --git a/examples/Example16_PartialSecond_MaxOutput/Example16_PartialSecond_MaxOutput.ino b/examples/Example16_PartialSecond_MaxOutput/Example16_PartialSecond_MaxOutput.ino index 49a2031..45c226f 100644 --- a/examples/Example16_PartialSecond_MaxOutput/Example16_PartialSecond_MaxOutput.ino +++ b/examples/Example16_PartialSecond_MaxOutput/Example16_PartialSecond_MaxOutput.ino @@ -32,14 +32,14 @@ long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to Ublox m void setup() { - Serial.begin(500000); //Increase serial speed to maximize + Serial.begin(500000); //Increase serial speed to maximize while (!Serial) ; //Wait for user to open terminal Serial.println("SparkFun Ublox Example"); Wire.begin(); Wire.setClock(400000); - + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port { Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); @@ -51,12 +51,12 @@ void setup() //myGPS.enableDebugging(); //Enable debug messages over Serial (default) - myGPS.setNavigationFrequency(10); //Set output to 10 times a second + myGPS.setNavigationFrequency(10); //Set output to 10 times a second byte rate = myGPS.getNavigationFrequency(); //Get the update rate of this module Serial.print("Current update rate:"); Serial.println(rate); - - myGPS.saveConfiguration(); //Save the current settings to flash and BBR + + myGPS.saveConfiguration(); //Save the current settings to flash and BBR pinMode(2, OUTPUT); //For debug capture digitalWrite(2, HIGH); @@ -64,8 +64,8 @@ void setup() void loop() { - //Query module very often to get max update rate - if (millis() - lastTime > 10) + // Calling getPVT returns true if there actually is a fresh navigation solution available. + if (myGPS.getPVT()) { lastTime = millis(); //Update the timer @@ -102,11 +102,13 @@ void loop() Serial.print("."); //Pretty print leading zeros int mseconds = myGPS.getMillisecond(); - if(mseconds < 100) Serial.print("0"); - if(mseconds < 10) Serial.print("0"); + if (mseconds < 100) + Serial.print("0"); + if (mseconds < 10) + Serial.print("0"); Serial.print(mseconds); - Serial.print(" nanoSeconds: "); + Serial.print(" nanoSeconds: "); Serial.print(myGPS.getNanosecond()); Serial.println(); diff --git a/keywords.txt b/keywords.txt index 3d8a1f5..ddd571b 100644 --- a/keywords.txt +++ b/keywords.txt @@ -102,6 +102,7 @@ debugPrintln KEYWORD2 factoryReset KEYWORD2 setAutoPVT KEYWORD2 assumeAutoPVT KEYWORD2 +flushPVT KEYWORD2 getYear KEYWORD2 getMonth KEYWORD2 diff --git a/src/SparkFun_Ublox_Arduino_Library.cpp b/src/SparkFun_Ublox_Arduino_Library.cpp index 3290236..121f7d8 100644 --- a/src/SparkFun_Ublox_Arduino_Library.cpp +++ b/src/SparkFun_Ublox_Arduino_Library.cpp @@ -103,6 +103,56 @@ void SFE_UBLOX_GPS::debugPrintln(char *message) } } +const char *SFE_UBLOX_GPS::statusString(sfe_ublox_status_e stat) +{ + switch (stat) + { + case SFE_UBLOX_STATUS_SUCCESS: + return "Success"; + break; + case SFE_UBLOX_STATUS_FAIL: + return "General Failure"; + break; + case SFE_UBLOX_STATUS_CRC_FAIL: + return "CRC Fail"; + break; + case SFE_UBLOX_STATUS_TIMEOUT: + return "Timeout"; + break; + case SFE_UBLOX_STATUS_COMMAND_UNKNOWN: + return "Command Unknown"; + break; + case SFE_UBLOX_STATUS_OUT_OF_RANGE: + return "Out of range"; + break; + case SFE_UBLOX_STATUS_INVALID_ARG: + return "Invalid Arg"; + break; + case SFE_UBLOX_STATUS_INVALID_OPERATION: + return "Invalid operation"; + break; + case SFE_UBLOX_STATUS_MEM_ERR: + return "Memory Error"; + break; + case SFE_UBLOX_STATUS_HW_ERR: + return "Hardware Error"; + break; + case SFE_UBLOX_STATUS_DATA_SENT: + return "Data Sent"; + break; + case SFE_UBLOX_STATUS_DATA_RECEIVED: + return "Data Received"; + break; + case SFE_UBLOX_STATUS_I2C_COMM_FAILURE: + return "I2C Comm Failure"; + break; + default: + return "Unknown Status"; + break; + } + return "None"; +} + void SFE_UBLOX_GPS::factoryReset() { // Copy default settings to permanent @@ -228,7 +278,8 @@ boolean SFE_UBLOX_GPS::checkUbloxI2C() uint8_t lsb = _i2cPort->read(); if (lsb == 0xFF) { - debugPrintln((char *)"No bytes available"); + //I believe this is a Ublox bug. Device should never present an 0xFF. + debugPrintln((char *)F("checkUbloxI2C: Ublox bug, no bytes available")); lastCheck = millis(); //Put off checking to avoid I2C bus traffic return (false); } @@ -237,15 +288,14 @@ boolean SFE_UBLOX_GPS::checkUbloxI2C() if (bytesAvailable == 0) { - debugPrintln((char *)"Zero bytes available"); + debugPrintln((char *)F("checkUbloxI2C: OK, zero bytes available")); lastCheck = millis(); //Put off checking to avoid I2C bus traffic return (false); } - //Check for bit error + //Check for undocumented bit error. We found this doing logic scans. //This error is rare but if we incorrectly interpret the first bit of the two 'data available' bytes as 1 - //then we have far too many bytes to check - //Correct back down to + //then we have far too many bytes to check. May be related to I2C setup time violations: https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues/40 if (bytesAvailable & ((uint16_t)1 << 15)) { //Clear the MSbit @@ -253,7 +303,7 @@ boolean SFE_UBLOX_GPS::checkUbloxI2C() if (_printDebug == true) { - _debugSerial->print(F("Bytes available error:")); + _debugSerial->print(F("checkUbloxI2C: Bytes available error:")); _debugSerial->println(bytesAvailable); } } @@ -262,8 +312,9 @@ boolean SFE_UBLOX_GPS::checkUbloxI2C() { if (_printDebug == true) { - _debugSerial->print(F("Bytes available:")); - _debugSerial->println(bytesAvailable); + _debugSerial->print(F("checkUbloxI2C: Large packet of ")); + _debugSerial->print(bytesAvailable); + _debugSerial->println(F(" bytes received")); } } @@ -294,7 +345,7 @@ boolean SFE_UBLOX_GPS::checkUbloxI2C() { if (incoming == 0x7F) { - debugPrintln((char *)"Module not ready with data"); + debugPrintln((char *)F("Module not ready with data")); delay(5); //In logic analyzation, the module starting responding after 1.48ms goto TRY_AGAIN; } @@ -367,16 +418,19 @@ void SFE_UBLOX_GPS::process(uint8_t incoming) currentSentence = NONE; //Something went wrong. Reset. else if (ubxFrameCounter == 2) //Class { - packetAck.counter = 0; - packetAck.valid = false; - packetCfg.counter = 0; - packetCfg.valid = false; - //We can now identify the type of response if (incoming == UBX_CLASS_ACK) + { + packetAck.counter = 0; + packetAck.valid = false; ubxFrameClass = CLASS_ACK; + } else + { + packetCfg.counter = 0; + packetCfg.valid = false; ubxFrameClass = CLASS_NOT_AN_ACK; + } } ubxFrameCounter++; @@ -509,22 +563,27 @@ void SFE_UBLOX_GPS::processUBX(uint8_t incoming, ubxPacket *incomingUBX) //Validate this sentence if (incomingUBX->checksumA == rollingChecksumA && incomingUBX->checksumB == rollingChecksumB) { + incomingUBX->valid = true; + if (_printDebug == true) { - _debugSerial->print(F("Size: ")); + _debugSerial->print(F("Incoming: Size: ")); _debugSerial->print(incomingUBX->len); _debugSerial->print(F(" Received: ")); printPacket(incomingUBX); + + if (packetCfg.valid == true) + debugPrintln((char *)F("packetCfg now valid")); + if (packetAck.valid == true) + debugPrintln((char *)F("packetAck now valid")); } - incomingUBX->valid = true; + processUBXpacket(incomingUBX); //We've got a valid packet, now do something with it } else { if (_printDebug == true) { - debugPrintln((char *)"Checksum failed. Response too big?"); - //Drive an external pin to allow for easier logic analyzation if (checksumFailurePin >= 0) { @@ -533,11 +592,7 @@ void SFE_UBLOX_GPS::processUBX(uint8_t incoming, ubxPacket *incomingUBX) digitalWrite((uint8_t)checksumFailurePin, HIGH); } - _debugSerial->print(F("Size: ")); - _debugSerial->print(incomingUBX->len); - _debugSerial->print(F(" Received: ")); - printPacket(incomingUBX); - + debugPrint((char *)F("Checksum failed:")); _debugSerial->print(F(" checksumA: ")); _debugSerial->print(incomingUBX->checksumA); _debugSerial->print(F(" checksumB: ")); @@ -548,6 +603,12 @@ void SFE_UBLOX_GPS::processUBX(uint8_t incoming, ubxPacket *incomingUBX) _debugSerial->print(F(" rollingChecksumB: ")); _debugSerial->print(rollingChecksumB); _debugSerial->println(); + + _debugSerial->print(F("Failed : ")); + _debugSerial->print(F("Size: ")); + _debugSerial->print(incomingUBX->len); + _debugSerial->print(F(" Received: ")); + printPacket(incomingUBX); } } } @@ -579,13 +640,13 @@ void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) if (msg->id == UBX_ACK_ACK && msg->payload[0] == packetCfg.cls && msg->payload[1] == packetCfg.id) { //The ack we just received matched the CLS/ID of last packetCfg sent (or received) - debugPrintln((char *)"UBX ACK: Command sent/ack'd successfully"); + debugPrintln((char *)F("UBX ACK: Command sent/ack'd successfully")); commandAck = UBX_ACK_ACK; } else if (msg->id == UBX_ACK_NACK && msg->payload[0] == packetCfg.cls && msg->payload[1] == packetCfg.id) { - //The ack we just received matched the CLS/ID of last packetCfg sent (or received) - debugPrintln((char *)"UBX ACK: Not-Acknowledged"); + //The ack we just received matched the CLS/ID of last packetCfg sent + debugPrintln((char *)F("UBX ACK: Not-Acknowledged")); commandAck = UBX_ACK_NACK; } break; @@ -693,20 +754,26 @@ void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) } //Given a packet and payload, send everything including CRC bytes via I2C port -boolean SFE_UBLOX_GPS::sendCommand(ubxPacket outgoingUBX, uint16_t maxWait) +sfe_ublox_status_e SFE_UBLOX_GPS::sendCommand(ubxPacket outgoingUBX, uint16_t maxWait) { - commandAck = UBX_ACK_NONE; //We're about to send a command. Begin waiting for ack. + sfe_ublox_status_e retVal = SFE_UBLOX_STATUS_SUCCESS; + calcChecksum(&outgoingUBX); //Sets checksum A and B bytes of the packet if (_printDebug == true) { - _debugSerial->print(F("Sending: ")); + _debugSerial->print(F("\nSending: ")); printPacket(&outgoingUBX); } + if (commType == COMM_TYPE_I2C) { - if (!sendI2cCommand(outgoingUBX, maxWait)) - return false; + retVal = sendI2cCommand(outgoingUBX, maxWait); + if (retVal != SFE_UBLOX_STATUS_SUCCESS) + { + debugPrintln((char *)F("Send I2C Command failed")); + return retVal; + } } else if (commType == COMM_TYPE_SERIAL) { @@ -715,19 +782,29 @@ boolean SFE_UBLOX_GPS::sendCommand(ubxPacket outgoingUBX, uint16_t maxWait) if (maxWait > 0) { - //Give waitForResponse the cls/id to check for - return waitForResponse(outgoingUBX.cls, outgoingUBX.id, maxWait); //Wait for Ack response + //Depending on what we just sent, either we need to look for an ACK or not + if (outgoingUBX.cls == UBX_CLASS_CFG) + { + debugPrintln((char *)F("sendCommand: Waiting for ACK response")); + retVal = waitForACKResponse(outgoingUBX.cls, outgoingUBX.id, maxWait); //Wait for Ack response + } + else + { + debugPrintln((char *)F("sendCommand: Waiting for No ACK response")); + retVal = waitForNoACKResponse(outgoingUBX.cls, outgoingUBX.id, maxWait); //Wait for Ack response + } } - return true; + return retVal; } -boolean SFE_UBLOX_GPS::sendI2cCommand(ubxPacket outgoingUBX, uint16_t maxWait) +//Returns false if sensor fails to respond to I2C traffic +sfe_ublox_status_e SFE_UBLOX_GPS::sendI2cCommand(ubxPacket outgoingUBX, uint16_t maxWait) { //Point at 0xFF data register _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); //There is no register to write to, we just begin writing data bytes _i2cPort->write(0xFF); - if (_i2cPort->endTransmission() != 0) //Don't release bus - return (false); //Sensor did not ACK + if (_i2cPort->endTransmission() != 0) //Don't release bus + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK //Write header bytes _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); //There is no register to write to, we just begin writing data bytes @@ -735,10 +812,10 @@ boolean SFE_UBLOX_GPS::sendI2cCommand(ubxPacket outgoingUBX, uint16_t maxWait) _i2cPort->write(UBX_SYNCH_2); //b _i2cPort->write(outgoingUBX.cls); _i2cPort->write(outgoingUBX.id); - _i2cPort->write(outgoingUBX.len & 0xFF); //LSB - _i2cPort->write(outgoingUBX.len >> 8); //MSB - if (_i2cPort->endTransmission(false) != 0) //Do not release bus - return (false); //Sensor did not ACK + _i2cPort->write(outgoingUBX.len & 0xFF); //LSB + _i2cPort->write(outgoingUBX.len >> 8); //MSB + if (_i2cPort->endTransmission(false) != 0) //Do not release bus + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK //Write payload. Limit the sends into 32 byte chunks //This code based on ublox: https://forum.u-blox.com/index.php/20528/how-to-use-i2c-to-get-the-nmea-frames @@ -759,8 +836,8 @@ boolean SFE_UBLOX_GPS::sendI2cCommand(ubxPacket outgoingUBX, uint16_t maxWait) for (uint16_t x = 0; x < len; x++) _i2cPort->write(outgoingUBX.payload[startSpot + x]); //Write a portion of the payload to the bus - if (_i2cPort->endTransmission(false) != 0) //Don't release bus - return (false); //Sensor did not ACK + if (_i2cPort->endTransmission(false) != 0) //Don't release bus + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK //*outgoingUBX.payload += len; //Move the pointer forward startSpot += len; //Move the pointer forward @@ -776,8 +853,8 @@ boolean SFE_UBLOX_GPS::sendI2cCommand(ubxPacket outgoingUBX, uint16_t maxWait) //All done transmitting bytes. Release bus. if (_i2cPort->endTransmission() != 0) - return (false); //Sensor did not ACK - return (true); + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + return (SFE_UBLOX_STATUS_SUCCESS); } //Given a packet and payload, send everything including CRC bytesA via Serial port @@ -863,10 +940,22 @@ void SFE_UBLOX_GPS::printPacket(ubxPacket *packet) if (_printDebug == true) { _debugSerial->print(F("CLS:")); - _debugSerial->print(packet->cls, HEX); + if (packet->cls == UBX_CLASS_NAV) //1 + _debugSerial->print("NAV"); + else if (packet->cls == UBX_CLASS_ACK) //5 + _debugSerial->print("ACK"); + else if (packet->cls == UBX_CLASS_CFG) //6 + _debugSerial->print("CFG"); + else if (packet->cls == UBX_CLASS_MON) //0x0A + _debugSerial->print("MON"); + else + _debugSerial->print(packet->cls, HEX); _debugSerial->print(F(" ID:")); - _debugSerial->print(packet->id, HEX); + if (packet->cls == UBX_CLASS_NAV && packet->id == UBX_NAV_PVT) + _debugSerial->print("PVT"); + else + _debugSerial->print(packet->id, HEX); _debugSerial->print(F(" Len: 0x")); _debugSerial->print(packet->len, HEX); @@ -885,8 +974,18 @@ void SFE_UBLOX_GPS::printPacket(ubxPacket *packet) //=-=-=-=-=-=-=-= Specific commands =-=-=-=-=-=-=-==-=-=-=-=-=-=-= //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -//Poll the module until and ack is received -boolean SFE_UBLOX_GPS::waitForResponse(uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +//When messages from the class CFG are sent to the receiver, the receiver will send an "acknowledge"(UBX - ACK - ACK) or a +//"not acknowledge"(UBX-ACK-NAK) message back to the sender, depending on whether or not the message was processed correctly. +//Some messages from other classes also use the same acknowledgement mechanism. + +//If the packetCfg len is 1, then we are querying the device for data +//If the packetCfg len is >1, then we are sending a new setting + +//Returns true if we got the following: +//* If packetCfg len is 1 and we got and ACK and a valid packetCfg (module is responding with register content) +//* If packetCfg len is >1 and we got an ACK (no valid packetCfg needed, module absorbs new register data) +//Returns false if we timed out, got a NACK (command unknown), or had a CLS/ID mismatch +sfe_ublox_status_e SFE_UBLOX_GPS::waitForACKResponse(uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) { commandAck = UBX_ACK_NONE; //Reset flag packetCfg.valid = false; //This will go true when we receive a response to the packet we sent @@ -897,61 +996,150 @@ boolean SFE_UBLOX_GPS::waitForResponse(uint8_t requestedClass, uint8_t requested { if (checkUblox() == true) //See if new data is available. Process bytes as they come in. { - if (packetAck.valid == true) + //First we verify the ACK. commandAck will only go UBX_ACK_ACK if CLS/ID matches + if (commandAck == UBX_ACK_ACK) { - //If the packet we just sent was a CFG packet then we'll get an ACK - if (commandAck == UBX_ACK_ACK) + if (_printDebug == true) { - if (_printDebug == true) - { - _debugSerial->print(F("ACK received after ")); - _debugSerial->print(millis() - startTime); - _debugSerial->println(F(" msec")); - } - return (true); // Received an ACK + _debugSerial->print(F("waitForACKResponse: ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); } - else if (commandAck == UBX_ACK_NACK) + + //Are we expecting data back or just an ACK? + if (packetCfg.len == 1) { - if (_printDebug == true) + //We are expecting a data response so now we verify the response packet was valid + if (packetCfg.valid == true) { - _debugSerial->print(F("NACK received after ")); - _debugSerial->print(millis() - startTime); - _debugSerial->println(F(" msec")); + if (packetCfg.cls == requestedClass && packetCfg.id == requestedID) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: CLS/ID match after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //Received a data and a correct ACK! + } + else + { + //Reset packet and continue checking incoming data for matching cls/id + debugPrintln((char *)F("waitForACKResponse: CLS/ID mismatch, continue to wait...")); + packetCfg.valid = false; //This will go true when we receive a response to the packet we sent + } + } + else + { + //We were expecting data but didn't get a valid config packet + debugPrintln((char *)F("waitForACKResponse: Invalid config packet")); + return (SFE_UBLOX_STATUS_FAIL); //We got an ACK, we're never going to get valid config data } - return (false); // Received a NACK + } + else + { + //We have sent new data. We expect an ACK but no return config packet. + debugPrintln((char *)F("waitForACKResponse: New data successfully sent")); + return (SFE_UBLOX_STATUS_DATA_SENT); //New data successfully sent } } + else if (commandAck == UBX_ACK_NACK) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: NACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_COMMAND_UNKNOWN); //Received a NACK + } + } //checkUblox == true + + delayMicroseconds(500); + } //while (millis() - startTime < maxTime) + + //TODO add check here if config went valid but we never got the following ack + //Through debug warning, This command might not get an ACK + if (packetCfg.valid == true) + { + debugPrintln((char *)F("waitForACKResponse: Config was valid but ACK not received")); + } + + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: timeout after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. No ack packet received.")); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} - if (packetCfg.valid == true) +//For non-CFG queries no ACK is sent so we use this function +//Returns true if we got a config packet full of response data that has CLS/ID match to our query packet +//Returns false if we timed out +sfe_ublox_status_e SFE_UBLOX_GPS::waitForNoACKResponse(uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + packetCfg.valid = false; //This will go true when we receive a response to the packet we sent + packetAck.valid = false; + packetCfg.cls = 255; + packetCfg.id = 255; + + unsigned long startTime = millis(); + while (millis() - startTime < maxTime) + { + if (checkUblox() == true) //See if new data is available. Process bytes as they come in. + { + //Did we receive a config packet that matches the cls/id we requested? + if (packetCfg.cls == requestedClass && packetCfg.id == requestedID) { - //Did we receive a config packet that matches the cls/id we requested? - if (packetCfg.cls == requestedClass && packetCfg.id == requestedID) + //This packet might be good or it might be CRC corrupt + if (packetCfg.valid == true) { if (_printDebug == true) { - _debugSerial->print(F("CLS/ID match after ")); + _debugSerial->print(F("waitForNoACKResponse: CLS/ID match after ")); _debugSerial->print(millis() - startTime); _debugSerial->println(F(" msec")); } - return (true); //If the packet we just sent was a NAV packet then we'll just get data back + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We have new data to act upon } else { - if (_printDebug == true) - { - _debugSerial->print(F("Packet didn't match CLS/ID")); - printPacket(&packetCfg); - } + debugPrintln((char *)F("waitForNoACKResponse: CLS/ID match but failed CRC")); + return (SFE_UBLOX_STATUS_CRC_FAIL); //We got the right packet but it was corrupt } } + else if (packetCfg.cls < 255 && packetCfg.id < 255) + { + //Reset packet and continue checking incoming data for matching cls/id + if (_printDebug == true) + { + debugPrint((char *)F("waitForNoACKResponse: CLS/ID mismatch: ")); + _debugSerial->print(F("CLS: ")); + _debugSerial->print(packetCfg.cls, HEX); + _debugSerial->print(F(" ID: ")); + _debugSerial->print(packetCfg.id, HEX); + _debugSerial->println(); + } + + packetCfg.valid = false; //This will go true when we receive a response to the packet we sent + packetCfg.cls = 255; + packetCfg.id = 255; + } } delayMicroseconds(500); } - debugPrintln((char *)"waitForResponse timeout"); + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: timeout after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. No packet received.")); + } - return (false); + return (SFE_UBLOX_STATUS_TIMEOUT); } //Save current configuration to flash and BBR (battery backed RAM) @@ -1491,12 +1679,11 @@ boolean SFE_UBLOX_GPS::setPortOutput(uint8_t portID, uint8_t outStreamSettings, if (getPortSettings(portID, maxWait) == false) return (false); //Something went wrong. Bail. - // Let's make sure we wait for the ACK too (sendCommand will have returned as soon as the module sent its response) - // This is only required because we are doing two sendCommands in quick succession using the same class and ID - waitForResponse(UBX_CLASS_CFG, UBX_CFG_PRT, 100); // But we'll only wait for 100msec max - - //Yes, this is the depreciated way to do it but it's still supported on v27 so it - //covers both ZED-F9P (v27) and SAM-M8Q (v18) + if (commandAck != UBX_ACK_ACK) + { + debugPrintln((char *)F("setPortOutput failed to ACK")); + return (false); + } packetCfg.cls = UBX_CLASS_CFG; packetCfg.id = UBX_CFG_PRT; @@ -1519,10 +1706,6 @@ boolean SFE_UBLOX_GPS::setPortInput(uint8_t portID, uint8_t inStreamSettings, ui if (getPortSettings(portID, maxWait) == false) return (false); //Something went wrong. Bail. - // Let's make sure we wait for the ACK too (sendCommand will have returned as soon as the module sent its response) - // This is only required because we are doing two sendCommands in quick succession using the same class and ID - waitForResponse(UBX_CLASS_CFG, UBX_CFG_PRT, 100); // But we'll only wait for 100msec max - packetCfg.cls = UBX_CLASS_CFG; packetCfg.id = UBX_CFG_PRT; packetCfg.len = 20; @@ -1885,7 +2068,7 @@ boolean SFE_UBLOX_GPS::powerSaveMode(bool power_save, uint16_t maxWait) */ if (protVer >= 27) { - debugPrintln((char *)"powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version"); + debugPrintln((char *)F("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version")); return (false); } @@ -1898,10 +2081,6 @@ boolean SFE_UBLOX_GPS::powerSaveMode(bool power_save, uint16_t maxWait) if (sendCommand(packetCfg, maxWait) == false) //Ask module for the current power management settings. Loads into payloadCfg. return (false); - // Let's make sure we wait for the ACK too (sendCommand will have returned as soon as the module sent its response) - // This is only required because we are doing two sendCommands in quick succession using the same class and ID - waitForResponse(UBX_CLASS_CFG, UBX_CFG_RXM, 100); // But we'll only wait for 100msec max - if (power_save) { payloadCfg[1] = 1; // Power Save Mode @@ -1933,10 +2112,6 @@ boolean SFE_UBLOX_GPS::setDynamicModel(dynModel newDynamicModel, uint16_t maxWai if (sendCommand(packetCfg, maxWait) == false) //Ask module for the current navigation model settings. Loads into payloadCfg. return (false); - // Let's make sure we wait for the ACK too (sendCommand will have returned as soon as the module sent its response) - // This is only required because we are doing two sendCommands in quick succession using the same class and ID - waitForResponse(UBX_CLASS_CFG, UBX_CFG_NAV5, 100); // But we'll only wait for 100msec max - payloadCfg[0] = 0x01; // mask: set only the dyn bit (0) payloadCfg[1] = 0x00; // mask payloadCfg[2] = newDynamicModel; // dynModel @@ -2051,16 +2226,20 @@ boolean SFE_UBLOX_GPS::getPVT(uint16_t maxWait) if (autoPVT && autoPVTImplicitUpdate) { //The GPS is automatically reporting, we just check whether we got unread data + debugPrintln((char *)F("getPVT: Autoreporting")); checkUblox(); return moduleQueried.all; } else if (autoPVT && !autoPVTImplicitUpdate) { //Someone else has to call checkUblox for us... + debugPrintln((char *)F("getPVT: Exit immediately")); return (false); } else { + debugPrintln((char *)F("getPVT: Polling")); + //The GPS is not automatically reporting navigation position so we have to poll explicitly packetCfg.cls = UBX_CLASS_NAV; packetCfg.id = UBX_NAV_PVT; @@ -2068,8 +2247,17 @@ boolean SFE_UBLOX_GPS::getPVT(uint16_t maxWait) //packetCfg.startingSpot = 20; //Begin listening at spot 20 so we can record up to 20+MAX_PAYLOAD_SIZE = 84 bytes Note:now hard-coded in processUBX //The data is parsed as part of processing the response - return sendCommand(packetCfg, maxWait); - return (false); //If command send fails then bail + sfe_ublox_status_e retVal = sendCommand(packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if (_printDebug == true) + { + _debugSerial->print(F("getPVT retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); } } @@ -2233,7 +2421,9 @@ uint8_t SFE_UBLOX_GPS::getSIV(uint16_t maxWait) uint8_t SFE_UBLOX_GPS::getFixType(uint16_t maxWait) { if (moduleQueried.fixType == false) + { getPVT(maxWait); + } moduleQueried.fixType = false; //Since we are about to give this to user, mark this data as stale moduleQueried.all = false; @@ -2318,23 +2508,19 @@ boolean SFE_UBLOX_GPS::getProtocolVersion(uint16_t maxWait) if (sendCommand(packetCfg, maxWait) == false) return (false); //If command send fails then bail - // Let's make sure we wait for the ACK too (sendCommand will have returned as soon as the module sent its response) - // This is only required because we are doing multiple sendCommands in quick succession using the same class and ID - waitForResponse(UBX_CLASS_MON, UBX_MON_VER, 100); // But we'll only wait for 100msec max - //Payload should now contain ~220 characters (depends on module type) - if (_printDebug == true) - { - _debugSerial->print(F("MON VER Payload:")); - for (int location = 0; location < packetCfg.len; location++) - { - if (location % 30 == 0) - _debugSerial->println(); - _debugSerial->write(payloadCfg[location]); - } - _debugSerial->println(); - } + // if (_printDebug == true) + // { + // _debugSerial->print(F("MON VER Payload:")); + // for (int location = 0; location < packetCfg.len; location++) + // { + // if (location % 30 == 0) + // _debugSerial->println(); + // _debugSerial->write(payloadCfg[location]); + // } + // _debugSerial->println(); + // } //We will step through the payload looking at each extension field of 30 bytes for (uint8_t extensionNumber = 0; extensionNumber < 10; extensionNumber++) @@ -2345,13 +2531,47 @@ boolean SFE_UBLOX_GPS::getProtocolVersion(uint16_t maxWait) versionHigh = (payloadCfg[(30 * extensionNumber) + 8] - '0') * 10 + (payloadCfg[(30 * extensionNumber) + 9] - '0'); //Convert '18' to 18 versionLow = (payloadCfg[(30 * extensionNumber) + 11] - '0') * 10 + (payloadCfg[(30 * extensionNumber) + 12] - '0'); //Convert '00' to 00 moduleQueried.versionNumber = true; //Mark this data as new - return (true); //Success! + + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version: ")); + _debugSerial->print(versionHigh); + _debugSerial->print(F(".")); + _debugSerial->println(versionLow); + } + return (true); //Success! } } return (false); //We failed } +//Mark all the PVT data as read/stale. This is handy to get data alignment after CRC failure +void SFE_UBLOX_GPS::flushPVT() +{ + //Mark all datums as stale (read before) + moduleQueried.gpsiTOW = false; + moduleQueried.gpsYear = false; + moduleQueried.gpsMonth = false; + moduleQueried.gpsDay = false; + moduleQueried.gpsHour = false; + moduleQueried.gpsMinute = false; + moduleQueried.gpsSecond = false; + moduleQueried.gpsNanosecond = false; + + moduleQueried.all = false; + moduleQueried.longitude = false; + moduleQueried.latitude = false; + moduleQueried.altitude = false; + moduleQueried.altitudeMSL = false; + moduleQueried.SIV = false; + moduleQueried.fixType = false; + moduleQueried.carrierSolution = false; + moduleQueried.groundSpeed = false; + moduleQueried.headingOfMotion = false; + moduleQueried.pDOP = false; +} + //Relative Positioning Information in NED frame //Returns true if commands was successful boolean SFE_UBLOX_GPS::getRELPOSNED(uint16_t maxWait) diff --git a/src/SparkFun_Ublox_Arduino_Library.h b/src/SparkFun_Ublox_Arduino_Library.h index 23adb05..b574cab 100644 --- a/src/SparkFun_Ublox_Arduino_Library.h +++ b/src/SparkFun_Ublox_Arduino_Library.h @@ -80,6 +80,24 @@ //Leave set to -1 if not needed const int checksumFailurePin = -1; +// Global Status Returns +typedef enum +{ + SFE_UBLOX_STATUS_SUCCESS, + SFE_UBLOX_STATUS_FAIL, + SFE_UBLOX_STATUS_CRC_FAIL, + SFE_UBLOX_STATUS_TIMEOUT, + SFE_UBLOX_STATUS_COMMAND_UNKNOWN, + SFE_UBLOX_STATUS_OUT_OF_RANGE, + SFE_UBLOX_STATUS_INVALID_ARG, + SFE_UBLOX_STATUS_INVALID_OPERATION, + SFE_UBLOX_STATUS_MEM_ERR, + SFE_UBLOX_STATUS_HW_ERR, + SFE_UBLOX_STATUS_DATA_SENT, + SFE_UBLOX_STATUS_DATA_RECEIVED, + SFE_UBLOX_STATUS_I2C_COMM_FAILURE, +} sfe_ublox_status_e; + //Registers const uint8_t UBX_SYNCH_1 = 0xB5; const uint8_t UBX_SYNCH_2 = 0x62; @@ -270,9 +288,9 @@ class SFE_UBLOX_GPS void processUBXpacket(ubxPacket *msg); //Once a packet has been received and validated, identify this packet's class/id and update internal flags void processNMEA(char incoming) __attribute__((weak)); //Given a NMEA character, do something with it. User can overwrite if desired to use something like tinyGPS or MicroNMEA libraries - void calcChecksum(ubxPacket *msg); //Sets the checksumA and checksumB of a given messages - boolean sendCommand(ubxPacket outgoingUBX, uint16_t maxWait = 250); //Given a packet and payload, send everything including CRC bytes, return true if we got a response - boolean sendI2cCommand(ubxPacket outgoingUBX, uint16_t maxWait = 250); + void calcChecksum(ubxPacket *msg); //Sets the checksumA and checksumB of a given messages + sfe_ublox_status_e sendCommand(ubxPacket outgoingUBX, uint16_t maxWait = 250); //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 = 250); void sendSerialCommand(ubxPacket outgoingUBX); void printPacket(ubxPacket *packet); //Useful for debugging @@ -289,32 +307,34 @@ class SFE_UBLOX_GPS boolean saveConfiguration(uint16_t maxWait = 250); //Save current configuration to flash and BBR (battery backed RAM) boolean factoryDefault(uint16_t maxWait = 250); //Reset module to factory defaults - boolean waitForResponse(uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = 250); //Poll the module until and ack is received + sfe_ublox_status_e waitForACKResponse(uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = 250); //Poll the module until a config packet and an ACK is received + sfe_ublox_status_e waitForNoACKResponse(uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = 250); //Poll the module until a config packet is received - // getPVT will only return data once in each navigation cycle. By default, that is once per second. - // Therefore we should set getPVTmaxWait to slightly longer than that. - // If you change the navigation frequency to (e.g.) 4Hz using setNavigationFrequency(4) - // then you should use a shorter maxWait for getPVT. 300msec would be about right: getPVT(300) - // The same is true for getHPPOSLLH. - #define getPVTmaxWait 1100 // Default maxWait for getPVT and all functions which call it - #define getHPPOSLLHmaxWait 1100 // Default maxWait for getHPPOSLLH and all functions which call it +// getPVT will only return data once in each navigation cycle. By default, that is once per second. +// Therefore we should set getPVTmaxWait to slightly longer than that. +// If you change the navigation frequency to (e.g.) 4Hz using setNavigationFrequency(4) +// then you should use a shorter maxWait for getPVT. 300msec would be about right: getPVT(300) +// The same is true for getHPPOSLLH. +#define getPVTmaxWait 1100 // Default maxWait for getPVT and all functions which call it +#define getHPPOSLLHmaxWait 1100 // Default maxWait for getHPPOSLLH and all functions which call it - 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 = 250); //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. Retruns true if new PVT is available. + 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 = 250); //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. Retruns true if new PVT is available. boolean setAutoPVT(boolean enabled, boolean implicitUpdate, uint16_t maxWait = 250); //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 getHPPOSLLH(uint16_t maxWait = getHPPOSLLHmaxWait); //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. Retruns true if new PVT is available. - - 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 - 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 + boolean getHPPOSLLH(uint16_t maxWait = getHPPOSLLHmaxWait); //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. Retruns true if new PVT is available. + void flushPVT(); //Mark all the PVT data as read/stale. This is handy to get data alignment after CRC failure + + 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 + 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 - int32_t getGroundSpeed(uint16_t maxWait = getPVTmaxWait); //Returns speed in mm/s - int32_t getHeading(uint16_t maxWait = getPVTmaxWait); //Returns heading in degrees * 10^-7 - uint16_t getPDOP(uint16_t maxWait = getPVTmaxWait); //Returns positional dillution of precision * 10^-2 + int32_t getGroundSpeed(uint16_t maxWait = getPVTmaxWait); //Returns speed in mm/s + int32_t getHeading(uint16_t maxWait = getPVTmaxWait); //Returns heading in degrees * 10^-7 + uint16_t getPDOP(uint16_t maxWait = getPVTmaxWait); //Returns positional dillution of precision * 10^-2 uint16_t getYear(uint16_t maxWait = getPVTmaxWait); uint8_t getMonth(uint16_t maxWait = getPVTmaxWait); uint8_t getDay(uint16_t maxWait = getPVTmaxWait); @@ -386,17 +406,18 @@ class SFE_UBLOX_GPS boolean getRELPOSNED(uint16_t maxWait = 1100); //Get Relative Positioning Information of the NED frame - void enableDebugging(Stream &debugPort = Serial); //Given a port to print to, enable debug messages - void disableDebugging(void); //Turn off debug statements - void debugPrint(char *message); //Safely print debug statements - void debugPrintln(char *message); //Safely print debug statements + void enableDebugging(Stream &debugPort = Serial); //Given a port to print to, enable debug messages + void disableDebugging(void); //Turn off debug statements + void debugPrint(char *message); //Safely print debug statements + void debugPrintln(char *message); //Safely print debug statements + const char *statusString(sfe_ublox_status_e stat); //Pretty print the return value //Support for geofences boolean addGeofence(int32_t latitude, int32_t longitude, uint32_t radius, byte confidence = 0, byte pinPolarity = 0, byte pin = 0, uint16_t maxWait = 1100); // Add a new geofence - boolean clearGeofences(uint16_t maxWait = 1100); //Clears all geofences - boolean getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait = 1100); //Returns the combined geofence state - boolean clearAntPIO(uint16_t maxWait = 1100); //Clears the antenna control pin settings to release the PIOs - geofenceParams currentGeofenceParams; // Global to store the geofence parameters + boolean clearGeofences(uint16_t maxWait = 1100); //Clears all geofences + boolean getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait = 1100); //Returns the combined geofence state + boolean clearAntPIO(uint16_t maxWait = 1100); //Clears the antenna control pin settings to release the PIOs + geofenceParams currentGeofenceParams; // Global to store the geofence parameters boolean powerSaveMode(bool power_save = true, uint16_t maxWait = 1100);