diff --git a/examples/Example33_TimeFreqSyncManager/Example33_TimeFreqSyncManager.ino b/examples/Example33_TimeFreqSyncManager/Example33_TimeFreqSyncManager.ino new file mode 100644 index 0000000..5098b41 --- /dev/null +++ b/examples/Example33_TimeFreqSyncManager/Example33_TimeFreqSyncManager.ino @@ -0,0 +1,207 @@ +/* + Configure Time & Frequency Sync manager (UBX-CFG-SMGR) + By: Danylo Ulianych + SparkFun Electronics + Date: March 6th, 2024 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example reads / sets UBX-CFG-SMGR configuration and prints UBX-TIM-SMEAS messages. + Works only with Time & Frequency Sync products like LEA-M8F, etc. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GNSS and a BlackBoard + 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 GNSS + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + + +// Callback: printTIMSMEASdata will be called when new TIM SMEA data arrives +// See u-blox_structs.h for the full definition of UBX_TIM_SMEAS_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoTIMTM2callback +// / _____ This _must_ be UBX_TIM_SMEAS_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printTIMSMEASdata(UBX_TIM_SMEAS_data_t smea) +{ + Serial.print("UBX-TIM-SMEAS:"); + Serial.printf("\n version: %u", smea.version); + Serial.printf("\n numMeas: %u", smea.numMeas); + Serial.printf("\n iTOW: %lu", smea.iTOW); + for (int i = 0; i < smea.numMeas; i++) { + Serial.printf("\n sourceId %u:", smea.data[i].sourceId); + Serial.printf("\n flags:"); + Serial.printf("\n freqValid %u", smea.data[i].flags.bits.freqValid); + Serial.printf("\n phaseValid %u", smea.data[i].flags.bits.phaseValid); + Serial.printf("\n phaseOffsetFrac %d", smea.data[i].phaseOffsetFrac); + Serial.printf("\n phaseUncFrac %u", smea.data[i].phaseUncFrac); + Serial.printf("\n phaseOffset %ld", smea.data[i].phaseOffset); + Serial.printf("\n phaseUnc %lu", smea.data[i].phaseUnc); + Serial.printf("\n freqOffset %ld", smea.data[i].freqOffset); + Serial.printf("\n freqUnc %lu", smea.data[i].freqUnc); + } +} + + +UBX_CFG_SMGR_data_t convertRawBufToCfgSmgr(const ubxPacket* msg) { + UBX_CFG_SMGR_data_t smgr; + if (msg->len < sizeof(UBX_CFG_SMGR_data_t)) { + Serial.printf("Payload message size (%zu) is too small to be converted to UBX_CFG_SMGR_data_t\n", msg->len); + return smgr; + } + + smgr.version = SFE_UBLOX_GNSS::extractByte(msg, 0); + smgr.minGNSSFix = SFE_UBLOX_GNSS::extractByte(msg, 1); + smgr.maxFreqChangeRate = SFE_UBLOX_GNSS::extractInt(msg, 2); + smgr.maxPhaseCorrRate = SFE_UBLOX_GNSS::extractInt(msg, 4); + smgr.freqTolerance = SFE_UBLOX_GNSS::extractInt(msg, 8); + smgr.timeTolerance = SFE_UBLOX_GNSS::extractInt(msg, 10); + smgr.messageCfg.all = SFE_UBLOX_GNSS::extractInt(msg, 12); + smgr.maxSlewRate = SFE_UBLOX_GNSS::extractInt(msg, 14); + smgr.flags.all = SFE_UBLOX_GNSS::extractLong(msg, 16); + + return smgr; +} + + +void printUbxCfgSmgr(const UBX_CFG_SMGR_data_t& smgr) { + Serial.printf("\nUBX-CFG-SMGR:"); + Serial.printf("\n version %u (0x%02x)", smgr.version, smgr.version); + Serial.printf("\n minGNSSFix %u (0x%02x)", smgr.minGNSSFix, smgr.minGNSSFix); + Serial.printf("\n maxFreqChangeRate %u (0x%02x)", smgr.maxFreqChangeRate, smgr.maxFreqChangeRate); + Serial.printf("\n maxPhaseCorrRate %u (0x%02x)", smgr.maxPhaseCorrRate, smgr.maxPhaseCorrRate); + Serial.printf("\n freqTolerance %u (0x%02x)", smgr.freqTolerance, smgr.freqTolerance); + Serial.printf("\n timeTolerance %u (0x%02x)", smgr.timeTolerance, smgr.timeTolerance); + Serial.printf("\n messageCfg:"); + Serial.printf("\n measInternal: %u", smgr.messageCfg.bits.measInternal); + Serial.printf("\n measGNSS: %u", smgr.messageCfg.bits.measGNSS); + Serial.printf("\n measEXTINT0: %u", smgr.messageCfg.bits.measEXTINT0); + Serial.printf("\n measEXTINT1: %u", smgr.messageCfg.bits.measEXTINT1); + Serial.printf("\n maxSlewRate %u (0x%02x)", smgr.maxSlewRate, smgr.maxSlewRate); + Serial.printf("\n flags:"); + Serial.printf("\n disableInternal: %u", smgr.flags.bits.disableInternal); + Serial.printf("\n disableExternal: %u", smgr.flags.bits.disableExternal); + Serial.printf("\n preferenceMode: %u", smgr.flags.bits.preferenceMode); + Serial.printf("\n enableGNSS: %u", smgr.flags.bits.enableGNSS); + Serial.printf("\n enableEXTINT0: %u", smgr.flags.bits.enableEXTINT0); + Serial.printf("\n enableEXTINT1: %u", smgr.flags.bits.enableEXTINT1); + Serial.printf("\n enableHostMeasInt: %u", smgr.flags.bits.enableHostMeasInt); + Serial.printf("\n enableHostMeasExt: %u", smgr.flags.bits.enableHostMeasExt); + Serial.printf("\n useAnyFix: %u", smgr.flags.bits.useAnyFix); + Serial.printf("\n disableMaxSlewRate: %u", smgr.flags.bits.disableMaxSlewRate); + Serial.printf("\n issueFreqWarn: %u", smgr.flags.bits.issueFreqWarn); + Serial.printf("\n issueTimeWarn: %u", smgr.flags.bits.issueTimeWarn); + Serial.printf("\n TPCoherent: %u", smgr.flags.bits.TPCoherent); + Serial.printf("\n disableOffset: %u", smgr.flags.bits.disableOffset); + Serial.println("\n"); +} + + +void setup() +{ + Serial.begin(115200); + while (!Serial); // wait for Serial ready + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGNSS.begin() == false) + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGNSS.setI2COutput(COM_TYPE_UBX); // ignore NMEA messages + + // setPacketCfgPayloadSize tells the library how many bytes our customPayload can hold. + // It is more memory-efficient to call setPacketCfgPayloadSize before .begin (to avoid creating a new buffer, copying across + // the contents of the old buffer and then deleting the old buffer). But let's call it here just to prove that we can. + myGNSS.setPacketCfgPayloadSize(MAX_PAYLOAD_SIZE); + + uint8_t customPayload[MAX_PAYLOAD_SIZE]; // This array holds the payload data bytes. MAX_PAYLOAD_SIZE defaults to 256. The CFG_RATE payload is only 6 bytes! + + // The next line creates and initialises the packet information which wraps around the payload + ubxPacket customCfg = {0, 0, 0, 0, 0, customPayload, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + + // The structure of ubxPacket is: + // uint8_t cls : The message Class + // uint8_t id : The message ID + // uint16_t len : Length of the payload. Does not include cls, id, or checksum bytes + // uint16_t counter : Keeps track of number of overall bytes received. Some responses are larger than 255 bytes. + // uint16_t startingSpot : The counter value needed to go past before we begin recording into payload array + // uint8_t *payload : The payload + // uint8_t checksumA : Given to us by the module. Checked against the rolling calculated A/B checksums. + // uint8_t checksumB + // sfe_ublox_packet_validity_e valid : Goes from NOT_DEFINED to VALID or NOT_VALID when checksum is checked + // sfe_ublox_packet_validity_e classAndIDmatch : Goes from NOT_DEFINED to VALID or NOT_VALID when the Class and ID match the requestedClass and requestedID + + // sendCommand will return: + // SFE_UBLOX_STATUS_DATA_RECEIVED if the data we requested was read / polled successfully + // SFE_UBLOX_STATUS_DATA_SENT if the data we sent was writted successfully (ACK'd) + // Other values indicate errors. Please see the sfe_ublox_status_e enum for further details. + + // Referring to the u-blox M8 Receiver Description and Protocol Specification we see that + // the navigation rate is configured using the UBX-CFG-RATE message. So let's load our + // custom packet with the correct information so we can read (poll / get) the current settings. + + customCfg.cls = UBX_CLASS_CFG; // This is the message Class + customCfg.id = UBX_CFG_SMGR; // This is the message ID + customCfg.len = 0; // Setting the len (length) to zero let's us poll the current settings + customCfg.startingSpot = 0; // Always set the startingSpot to zero (unless you really know what you are doing) + + // We also need to tell sendCommand how long it should wait for a reply + uint16_t maxWait = 250; // Wait for up to 250ms (Serial may need a lot longer e.g. 1100) + + // Now let's read the current UBX-CFG-SMGR settings. The results will be loaded into customCfg. + if (myGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + { + Serial.println(F("sendCommand (poll / get) failed! Freezing...")); + while (1) + ; + } + + UBX_CFG_SMGR_data_t cfgSmgrPayload = convertRawBufToCfgSmgr(&customCfg); + + printUbxCfgSmgr(cfgSmgrPayload); + + cfgSmgrPayload.minGNSSFix = 5; // update the min no. of GNSS fixes to start freq/phase sync + cfgSmgrPayload.flags.bits.useAnyFix = 1; // use any fix + + // update the raw payload buffer + memmove(customPayload, &cfgSmgrPayload, sizeof(UBX_CFG_SMGR_data_t)); + + // Now let's set the updated settings. + if (myGNSS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_SENT) // We are expecting data and an ACK + { + Serial.println(F("sendCommand set failed! Freezing...")); + while (1) + ; + } + + Serial.println("UBX-CFG-SMGR successfully updated"); + + myGNSS.setAutoTIMSMEAcallback(&printTIMSMEASdata); + + // Enable info/warns messages + // myGNSS.setVal8(UBLOX_CFG_INFMSG_UBX_I2C, 1); +} + +void loop() +{ + myGNSS.checkUblox(); //See if new UBX data is available. Process bytes as they come in. + myGNSS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + delay(250); //Don't pound too hard on the I2C bus +} diff --git a/keywords.txt b/keywords.txt index 6ff142f..5765da9 100644 --- a/keywords.txt +++ b/keywords.txt @@ -21,6 +21,7 @@ UBX_CFG_RATE_data_t KEYWORD1 UBX_CFG_TP5_data_t KEYWORD1 UBX_CFG_ITFM_data_t KEYWORD1 UBX_CFG_TMODE3_data_t KEYWORD1 +UBX_CFG_SMGR_data_t KEYWORD1 UBX_MON_RF_data_t KEYWORD1 UBX_MON_HW_data_t KEYWORD1 @@ -55,6 +56,7 @@ UBX_RXM_RAWX_data_t KEYWORD1 UBX_RXM_QZSSL6_message_data_t KEYWORD1 UBX_TIM_TM2_data_t KEYWORD1 +UBX_TIM_SMEAS_data_t KEYWORD1 UBX_ESF_ALG_data_t KEYWORD1 UBX_ESF_INS_data_t KEYWORD1 @@ -808,7 +810,8 @@ UBX_RXM_SFRBX LITERAL1 UBX_RXM_SPARTN LITERAL1 UBX_RXM_QZSSL6 LITERAL1 -UBX_TIM_TM2 LITERAL1 +UBX_TIM_TM2 LITERAL1 +UBX_TIM_SMEAS LITERAL1 UBX_RTCM_MSB LITERAL1 UBX_RTCM_1005 LITERAL1 diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp index 6d68956..85d1389 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.cpp @@ -397,6 +397,16 @@ void SFE_UBLOX_GNSS::end(void) packetUBXTIMTM2 = NULL; // Redundant? } + if (packetUBXTIMSMEAS != NULL) + { + if (packetUBXTIMSMEAS->callbackData != NULL) + { + delete packetUBXTIMSMEAS->callbackData; + } + delete packetUBXTIMSMEAS; + packetUBXTIMSMEAS = NULL; // Redundant + } + if (packetUBXESFALG != NULL) { if (packetUBXESFALG->callbackData != NULL) @@ -1448,6 +1458,10 @@ bool SFE_UBLOX_GNSS::checkAutomatic(uint8_t Class, uint8_t ID) if (packetUBXTIMTM2 != NULL) result = true; break; + case UBX_TIM_SMEAS: + if (packetUBXTIMSMEAS != NULL) + result = true; + break; } } break; @@ -1628,6 +1642,9 @@ uint16_t SFE_UBLOX_GNSS::getMaxPayloadSize(uint8_t Class, uint8_t ID) case UBX_TIM_TM2: maxSize = UBX_TIM_TM2_LEN; break; + case UBX_TIM_SMEAS: + maxSize = UBX_TIM_SMEAS_MAX_LEN; + break; } } break; @@ -4153,7 +4170,7 @@ void SFE_UBLOX_GNSS::processUBXpacket(ubxPacket *msg) if ((packetUBXTIMTM2->callbackData != NULL) // If RAM has been allocated for the copy of the data && (packetUBXTIMTM2->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale { - memcpy(&packetUBXTIMTM2->callbackData->ch, &packetUBXTIMTM2->data.ch, sizeof(UBX_TIM_TM2_data_t)); + memcpy(packetUBXTIMTM2->callbackData, &packetUBXTIMTM2->data, sizeof(UBX_TIM_TM2_data_t)); packetUBXTIMTM2->automaticFlags.flags.bits.callbackCopyValid = true; } @@ -4164,6 +4181,45 @@ void SFE_UBLOX_GNSS::processUBXpacket(ubxPacket *msg) } } } + else if (msg->id == UBX_TIM_SMEAS && msg->len <= UBX_TIM_SMEAS_MAX_LEN) + { + // Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXTIMSMEAS != NULL) + { + + packetUBXTIMSMEAS->data.version = extractByte(msg, 0); + packetUBXTIMSMEAS->data.numMeas = extractByte(msg, 1); + packetUBXTIMSMEAS->data.iTOW = extractLong(msg, 4); + + for (int i = 0; i < packetUBXTIMSMEAS->data.numMeas; i++) { + packetUBXTIMSMEAS->data.data[i].sourceId = extractByte(msg, 12 + 24 * i); + packetUBXTIMSMEAS->data.data[i].flags.all = extractByte(msg, 13 + 24 * i); + packetUBXTIMSMEAS->data.data[i].phaseOffsetFrac = extractSignedChar(msg, 14 + 24 * i); + packetUBXTIMSMEAS->data.data[i].phaseUncFrac = extractByte(msg, 15 + 24 * i); + packetUBXTIMSMEAS->data.data[i].phaseOffset = extractSignedLong(msg, 16 + 24 * i); + packetUBXTIMSMEAS->data.data[i].phaseUnc = extractLong(msg, 20 + 24 * i); + packetUBXTIMSMEAS->data.data[i].freqOffset = extractSignedLong(msg, 28 + 24 * i); + packetUBXTIMSMEAS->data.data[i].freqUnc = extractLong(msg, 32 + 24 * i); + } + + // Mark all datums as fresh (not read before) + packetUBXTIMSMEAS->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + // Check if we need to copy the data for the callback + if ((packetUBXTIMSMEAS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXTIMSMEAS->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(packetUBXTIMSMEAS->callbackData, &packetUBXTIMSMEAS->data, sizeof(UBX_TIM_SMEAS_data_t)); + packetUBXTIMSMEAS->automaticFlags.flags.bits.callbackCopyValid = true; + } + + // Check if we need to copy the data into the file buffer + if (packetUBXTIMSMEAS->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } break; case UBX_CLASS_ESF: if (msg->id == UBX_ESF_ALG && msg->len == UBX_ESF_ALG_LEN) @@ -5703,6 +5759,19 @@ void SFE_UBLOX_GNSS::checkCallbacks(void) packetUBXTIMTM2->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale } + if ((packetUBXTIMSMEAS != NULL) // If RAM has been allocated for message storage + && (packetUBXTIMSMEAS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXTIMSMEAS->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + if (packetUBXTIMSMEAS->callbackPointer != NULL) // If the pointer to the callback has been defined + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for TIM SMEA")); + packetUBXTIMSMEAS->callbackPointer(*packetUBXTIMSMEAS->callbackData); // Call the callback + } + packetUBXTIMSMEAS->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + if ((packetUBXESFALG != NULL) // If RAM has been allocated for message storage && (packetUBXESFALG->callbackData != NULL) // If RAM has been allocated for the copy of the data && (packetUBXESFALG->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid @@ -14131,6 +14200,60 @@ bool SFE_UBLOX_GNSS::setAutoTIMTM2rate(uint8_t rate, bool implicitUpdate, uint16 return ok; } + +bool SFE_UBLOX_GNSS::setAutoTIMSMEA(bool enabled, bool implicitUpdate, uint16_t maxWait) +{ + if (packetUBXTIMSMEAS == NULL) + initPacketUBXTIMSMEA(); // Check that RAM has been allocated for the data + if (packetUBXTIMSMEAS == NULL) // Only attempt this if RAM allocation was successful + return false; + + uint8_t rate = enabled ? 1 : 0; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_TIM; + payloadCfg[1] = UBX_TIM_SMEAS; + payloadCfg[2] = rate; // rate relative to navigation freq. + + bool ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXTIMSMEAS->automaticFlags.flags.bits.automatic = (rate > 0); + packetUBXTIMSMEAS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXTIMSMEAS->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + + +bool SFE_UBLOX_GNSS::setAutoTIMSMEAcallback(void (*callbackPointer)(UBX_TIM_SMEAS_data_t), uint16_t maxWait) { + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + bool result = setAutoTIMSMEA(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXTIMSMEAS->callbackData == NULL) // Check if RAM has been allocated for the callback copy + { + packetUBXTIMSMEAS->callbackData = new UBX_TIM_SMEAS_data_t; // Allocate RAM for the main struct + } + + if (packetUBXTIMSMEAS->callbackData == NULL) + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoTIMSMEAcallback: RAM alloc failed!")); +#endif + return (false); + } + + packetUBXTIMSMEAS->callbackPointer = callbackPointer; + return (true); +} + + // Enable automatic navigation message generation by the GNSS. bool SFE_UBLOX_GNSS::setAutoTIMTM2callback(void (*callbackPointer)(UBX_TIM_TM2_data_t), uint16_t maxWait) { @@ -14200,6 +14323,25 @@ bool SFE_UBLOX_GNSS::assumeAutoTIMTM2(bool enabled, bool implicitUpdate) return changes; } +// PRIVATE: Allocate RAM for packetUBXTIMSMEA and initialize it +bool SFE_UBLOX_GNSS::initPacketUBXTIMSMEA() +{ + packetUBXTIMSMEAS = new UBX_TIM_SMEAS_t; // Allocate RAM for the main struct + if (packetUBXTIMSMEAS == NULL) + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXTIMSMEA: RAM alloc failed!")); +#endif + return (false); + } + packetUBXTIMSMEAS->automaticFlags.flags.all = 0; + packetUBXTIMSMEAS->callbackPointer = NULL; + packetUBXTIMSMEAS->callbackData = NULL; + packetUBXTIMSMEAS->moduleQueried.moduleQueried.all = 0; + return (true); +} + // PRIVATE: Allocate RAM for packetUBXTIMTM2 and initialize it bool SFE_UBLOX_GNSS::initPacketUBXTIMTM2() { @@ -14228,6 +14370,14 @@ void SFE_UBLOX_GNSS::flushTIMTM2() packetUBXTIMTM2->moduleQueried.moduleQueried.all = 0; // Mark all datums as stale (read before) } +// Mark all the data as read/stale +void SFE_UBLOX_GNSS::flushTIMSMEA() +{ + if (packetUBXTIMSMEAS == NULL) + return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXTIMSMEAS->moduleQueried.moduleQueried.all = 0; // Mark all datums as stale (read before) +} + // Log this data in file buffer void SFE_UBLOX_GNSS::logTIMTM2(bool enabled) { @@ -14236,6 +14386,14 @@ void SFE_UBLOX_GNSS::logTIMTM2(bool enabled) packetUBXTIMTM2->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; } +// Log this data in file buffer +void SFE_UBLOX_GNSS::logTIMSMEA(bool enabled) +{ + if (packetUBXTIMSMEAS == NULL) + return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXTIMSMEAS->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + // ***** ESF ALG automatic support bool SFE_UBLOX_GNSS::getEsfAlignment(uint16_t maxWait) @@ -18498,7 +18656,7 @@ float SFE_UBLOX_GNSS::getHNRheading(uint16_t maxWait) // Returned as degrees // From v2.0: These are public. The user can call these to extract data from custom packets // Given a spot in the payload array, extract eight bytes and build a uint64_t -uint64_t SFE_UBLOX_GNSS::extractLongLong(ubxPacket *msg, uint16_t spotToStart) +uint64_t SFE_UBLOX_GNSS::extractLongLong(const ubxPacket *msg, uint16_t spotToStart) { uint64_t val = 0; val |= (uint64_t)msg->payload[spotToStart + 0] << 8 * 0; @@ -18513,7 +18671,7 @@ uint64_t SFE_UBLOX_GNSS::extractLongLong(ubxPacket *msg, uint16_t spotToStart) } // Given a spot in the payload array, extract four bytes and build a long -uint32_t SFE_UBLOX_GNSS::extractLong(ubxPacket *msg, uint16_t spotToStart) +uint32_t SFE_UBLOX_GNSS::extractLong(const ubxPacket *msg, uint16_t spotToStart) { uint32_t val = 0; val |= (uint32_t)msg->payload[spotToStart + 0] << 8 * 0; @@ -18524,7 +18682,7 @@ uint32_t SFE_UBLOX_GNSS::extractLong(ubxPacket *msg, uint16_t spotToStart) } // Just so there is no ambiguity about whether a uint32_t will cast to a int32_t correctly... -int32_t SFE_UBLOX_GNSS::extractSignedLong(ubxPacket *msg, uint16_t spotToStart) +int32_t SFE_UBLOX_GNSS::extractSignedLong(const ubxPacket *msg, uint16_t spotToStart) { union // Use a union to convert from uint32_t to int32_t { @@ -18537,7 +18695,7 @@ int32_t SFE_UBLOX_GNSS::extractSignedLong(ubxPacket *msg, uint16_t spotToStart) } // Given a spot in the payload array, extract two bytes and build an int -uint16_t SFE_UBLOX_GNSS::extractInt(ubxPacket *msg, uint16_t spotToStart) +uint16_t SFE_UBLOX_GNSS::extractInt(const ubxPacket *msg, uint16_t spotToStart) { uint16_t val = 0; val |= (uint16_t)msg->payload[spotToStart + 0] << 8 * 0; @@ -18546,7 +18704,7 @@ uint16_t SFE_UBLOX_GNSS::extractInt(ubxPacket *msg, uint16_t spotToStart) } // Just so there is no ambiguity about whether a uint16_t will cast to a int16_t correctly... -int16_t SFE_UBLOX_GNSS::extractSignedInt(ubxPacket *msg, uint16_t spotToStart) +int16_t SFE_UBLOX_GNSS::extractSignedInt(const ubxPacket *msg, uint16_t spotToStart) { union // Use a union to convert from uint16_t to int16_t { @@ -18559,13 +18717,13 @@ int16_t SFE_UBLOX_GNSS::extractSignedInt(ubxPacket *msg, uint16_t spotToStart) } // Given a spot, extract a byte from the payload -uint8_t SFE_UBLOX_GNSS::extractByte(ubxPacket *msg, uint16_t spotToStart) +uint8_t SFE_UBLOX_GNSS::extractByte(const ubxPacket *msg, uint16_t spotToStart) { return (msg->payload[spotToStart]); } // Given a spot, extract a signed 8-bit value from the payload -int8_t SFE_UBLOX_GNSS::extractSignedChar(ubxPacket *msg, uint16_t spotToStart) +int8_t SFE_UBLOX_GNSS::extractSignedChar(const ubxPacket *msg, uint16_t spotToStart) { union // Use a union to convert from uint8_t to int8_t { diff --git a/src/SparkFun_u-blox_GNSS_Arduino_Library.h b/src/SparkFun_u-blox_GNSS_Arduino_Library.h index fedd63a..19194c5 100644 --- a/src/SparkFun_u-blox_GNSS_Arduino_Library.h +++ b/src/SparkFun_u-blox_GNSS_Arduino_Library.h @@ -231,6 +231,7 @@ const uint8_t UBX_CFG_RINV = 0x34; // Contents of Remote Inventory const uint8_t UBX_CFG_RST = 0x04; // Reset Receiver / Clear Backup Data Structures. Used to reset device. const uint8_t UBX_CFG_RXM = 0x11; // RXM configuration const uint8_t UBX_CFG_SBAS = 0x16; // SBAS configuration +const uint8_t UBX_CFG_SMGR = 0x62; // Synchronization manager configuration const uint8_t UBX_CFG_TMODE3 = 0x71; // Time Mode Settings 3. Used to enable Survey In Mode const uint8_t UBX_CFG_TP5 = 0x31; // Time Pulse Parameters const uint8_t UBX_CFG_USB = 0x1B; // USB Configuration @@ -411,10 +412,17 @@ const uint8_t UBX_RXM_SPARTNKEY = 0x36; // Poll/transfer dynamic SPARTN keys const uint8_t UBX_SEC_UNIQID = 0x03; // Unique chip ID // Class: TIM -// The following are used to configure the TIM UBX messages (timing messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) -const uint8_t UBX_TIM_TM2 = 0x03; // Time mark data -const uint8_t UBX_TIM_TP = 0x01; // Time Pulse Timedata -const uint8_t UBX_TIM_VRFY = 0x06; // Sourced Time Verification +// The following are used to configure the TIM UBX messages (timing messages). Descriptions from UBX messages overview (u-blox M8 Protocol Specification Document page 178) +const uint8_t UBX_TIM_DOSC = 0x11; // Disciplined oscillator control +const uint8_t UBX_TIM_FCHG = 0x16; // Oscillator freq changed notification +const uint8_t UBX_TIM_HOC = 0x17; // Host oscillator control +const uint8_t UBX_TIM_SMEAS = 0x13; // Source measurement +const uint8_t UBX_TIM_SVIN = 0x04; // Survey-in data +const uint8_t UBX_TIM_TM2 = 0x03; // Time mark data +const uint8_t UBX_TIM_TOS = 0x12; // Time Pulse time and freq data +const uint8_t UBX_TIM_TP = 0x01; // Time Pulse time data +const uint8_t UBX_TIM_VCOCAL = 0x15; // Calibration +const uint8_t UBX_TIM_VRFY = 0x06; // Sourced Time Verification // Class: UPD // The following are used to configure the UPD UBX messages (firmware update messages). Descriptions from UBX messages overview (ZED-F9P Interface Description Document page 36) @@ -1287,6 +1295,11 @@ class SFE_UBLOX_GNSS void flushTIMTM2(); // Mark all the data as read/stale void logTIMTM2(bool enabled = true); // Log data to file buffer + bool setAutoTIMSMEA(bool enabled, bool implicitUpdate, uint16_t maxWait = defaultMaxWait); // Enable/disable automatic TIM SMEA 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 + bool setAutoTIMSMEAcallback(void (*callbackPointer)(UBX_TIM_SMEAS_data_t), uint16_t maxWait = defaultMaxWait); // Enable automatic SMEA reports at the navigation frequency. Data is accessed from the callback. + void flushTIMSMEA(); // Mark all the data as read/stale + void logTIMSMEA(bool enabled = true); // Log data to file buffer + // Sensor fusion (dead reckoning) (ESF) bool getEsfAlignment(uint16_t maxWait = defaultMaxWait); // ESF ALG Helper @@ -1570,13 +1583,13 @@ class SFE_UBLOX_GNSS // Functions to extract signed and unsigned 8/16/32-bit data from a ubxPacket // From v2.0: These are public. The user can call these to extract data from custom packets - uint64_t extractLongLong(ubxPacket *msg, uint16_t spotToStart); // Combine eight bytes from payload into uint64_t - uint32_t extractLong(ubxPacket *msg, uint16_t spotToStart); // Combine four bytes from payload into long - int32_t extractSignedLong(ubxPacket *msg, uint16_t spotToStart); // Combine four bytes from payload into signed long (avoiding any ambiguity caused by casting) - uint16_t extractInt(ubxPacket *msg, uint16_t spotToStart); // Combine two bytes from payload into int - int16_t extractSignedInt(ubxPacket *msg, uint16_t spotToStart); - uint8_t extractByte(ubxPacket *msg, uint16_t spotToStart); // Get byte from payload - int8_t extractSignedChar(ubxPacket *msg, uint16_t spotToStart); // Get signed 8-bit value from payload + static uint64_t extractLongLong(const ubxPacket *msg, uint16_t spotToStart); // Combine eight bytes from payload into uint64_t + static uint32_t extractLong(const ubxPacket *msg, uint16_t spotToStart); // Combine four bytes from payload into long + static int32_t extractSignedLong(const ubxPacket *msg, uint16_t spotToStart); // Combine four bytes from payload into signed long (avoiding any ambiguity caused by casting) + static uint16_t extractInt(const ubxPacket *msg, uint16_t spotToStart); // Combine two bytes from payload into int + static int16_t extractSignedInt(const ubxPacket *msg, uint16_t spotToStart); + static uint8_t extractByte(const ubxPacket *msg, uint16_t spotToStart); // Get byte from payload + static int8_t extractSignedChar(const ubxPacket *msg, uint16_t spotToStart); // Get signed 8-bit value from payload // Pointers to storage for the "automatic" messages // RAM is allocated for these if/when required. @@ -1611,7 +1624,8 @@ class SFE_UBLOX_GNSS UBX_CFG_PRT_t *packetUBXCFGPRT = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary UBX_CFG_RATE_t *packetUBXCFGRATE = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary - UBX_TIM_TM2_t *packetUBXTIMTM2 = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_TIM_SMEAS_t *packetUBXTIMSMEAS = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_TIM_TM2_t *packetUBXTIMTM2 = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary UBX_ESF_ALG_t *packetUBXESFALG = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary UBX_ESF_INS_t *packetUBXESFINS = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary @@ -1703,6 +1717,7 @@ class SFE_UBLOX_GNSS bool initPacketUBXCFGPRT(); // Allocate RAM for packetUBXCFGPRT and initialize it bool initPacketUBXCFGRATE(); // Allocate RAM for packetUBXCFGRATE and initialize it bool initPacketUBXTIMTM2(); // Allocate RAM for packetUBXTIMTM2 and initialize it + bool initPacketUBXTIMSMEA(); // Allocate RAM for packetUBXTIMSMEA and initialize it bool initPacketUBXESFALG(); // Allocate RAM for packetUBXESFALG and initialize it bool initPacketUBXESFSTATUS(); // Allocate RAM for packetUBXESFSTATUS and initialize it bool initPacketUBXESFINS(); // Allocate RAM for packetUBXESFINS and initialize it diff --git a/src/u-blox_structs.h b/src/u-blox_structs.h index 06160ce..33893c5 100644 --- a/src/u-blox_structs.h +++ b/src/u-blox_structs.h @@ -57,6 +57,10 @@ #define DEF_MAX_NUM_ESF_MEAS 31 // numMeas is 5 bits, indicating up to 31 groups could be received #endif +#ifndef DEF_MAX_NUM_TIM_SMEAS_BLOCKS +#define DEF_MAX_NUM_TIM_SMEAS_BLOCKS 6 +#endif + // Additional flags and pointers that need to be stored with each message type struct ubxAutomaticFlags { @@ -1877,6 +1881,51 @@ typedef struct uint8_t reserved3[8]; } UBX_CFG_TMODE3_data_t; + +// UBX-CFG-SMGR (0x06 0x62): Synchronization manager configuration +const uint16_t UBX_CFG_SMGR_LEN = 20; + +typedef struct +{ + uint8_t version; /* Message version (0x00 for this version) */ + uint8_t minGNSSFix; /* Minimum number of GNSS fixes before we commit to use it as a source */ + uint16_t maxFreqChangeRate; /* Maximum frequency change rate during disciplining. Must not exceed 30ppb/s*/ + uint16_t maxPhaseCorrRate; /* Maximum phase correction rate in coherent time pulse mode. */ + uint8_t reserved1[2]; /* Reserved. Do not use */ + uint16_t freqTolerance; /* Freq limit of possible deviation from nominal */ + uint16_t timeTolerance; /* Time pulse limit of possible deviation from nominal */ + union { + uint16_t all; + struct { + uint16_t measInternal : 1; /* 1 = report the estimated offset of the internal oscillator based on the oscillator model */ + uint16_t measGNSS : 1; /* 1 = report the internal oscillator's offset relative to GNSS */ + uint16_t measEXTINT0 : 1; /* 1 = report the internal oscillator's offset relative to the source on EXTINT0 */ + uint16_t measEXTINT1 : 1; /* 1 = report the internal oscillator's offset relative to the source on EXTINT1 */ + } bits; + } messageCfg; + uint16_t maxSlewRate; + union { + uint32_t all; + struct { + uint32_t disableInternal : 1; /* 1 = disable disciplining of the internal oscillator */ + uint32_t disableExternal : 1; /* 1 = disable disciplining of the external oscillator */ + uint32_t preferenceMode : 1; /* Reference selection preference: 0 - best frequency accuracy; 1 - best phase accuracy */ + uint32_t enableGNSS : 1; /* 1 = enable use of GNSS as synchronization source */ + uint32_t enableEXTINT0 : 1; /* 1 = enable use of EXTINT0 as synchronization source */ + uint32_t enableEXTINT1 : 1; /* 1 = enable use of EXTINT1 as synchronization source */ + uint32_t enableHostMeasInt : 1; /* 1 = enable use of host measurements on the internal oscillator as synchronization source */ + uint32_t enableHostMeasExt : 1; /* 1 = enable use of host measurements on the external oscillator as synchronization source */ + uint32_t reserved : 2; /* Reserved. Do not use */ + uint32_t useAnyFix : 1; /* 0 - use over-determined navigation solutions only; 1 - use any fix */ + uint32_t disableMaxSlewRate : 1; /* 1 - don't use the value in the field maxSlewRate */ + uint32_t issueFreqWarn : 1; /* 1 - issue a warning (via UBX-TIM-TOS flag) when frequency uncertainty exceeds freqTolerance */ + uint32_t issueTimeWarn : 1; /* 1 = issue a warning (via UBX-TIM-TOS flag) when time uncertainty exceeds timeTolerance */ + uint32_t TPCoherent : 2; /* Control time pulse coherency: 0 - Coherent pulses; 1 - Non-coherent pulses; 2 - Post-initialization coherent pulses*/ + uint32_t disableOffset : 1; /* 1 = disable automatic storage of oscillator offset */ + } bits; + } flags; +} UBX_CFG_SMGR_data_t; + // MON-specific structs // UBX-MON-HW (0x0A 0x09): Hardware status @@ -1980,6 +2029,76 @@ typedef struct // TIM-specific structs +// UBX-TIM-SMEAS (0x0D 0x13): Source measurement +// Note: length is variable +const uint16_t UBX_TIM_SMEAS_MAX_LEN = 12 + 24 * DEF_MAX_NUM_TIM_SMEAS_BLOCKS; + +typedef struct +{ + uint8_t sourceId; /* Index of source */ + union { + uint8_t all; + struct { + uint8_t freqValid : 1; /* 1 = frequency measurement is valid */ + uint8_t phaseValid : 1; /* 1 = phase measurement is valid */ + } bits; + } flags; + int8_t phaseOffsetFrac; /* Sub-nanosecond phase offset (scaled by 2^-8) */ + uint8_t phaseUncFrac; /* Sub-nanosecond phase uncertainty (scaled by 2^-8) */ + int32_t phaseOffset; /* Phase offset [ns] */ + uint32_t phaseUnc; /* Phase uncertainty (one standard deviation, ns) */ + uint8_t reserved3[4]; /* Reserved. Do not use. Set as zero */ + int32_t freqOffset; /* Frequency offset [ppb] */ + uint32_t freqUnc; /* Frequency uncertainty (one standard deviation, ppb) */ +} UBX_TIM_SMEAS_blockData_t; + + +typedef struct +{ + uint8_t version; /* Message version (0x00 for this version) */ + uint8_t numMeas; /* Number of measurements in repeated block */ + uint8_t reserved1[2]; /* Reserved. Do not use. Set as zero */ + uint32_t iTOW; /* Time of the week [ms] */ + uint8_t reserved2[2]; /* Reserved. Do not use. Set as zero */ + UBX_TIM_SMEAS_blockData_t data[DEF_MAX_NUM_TIM_SMEAS_BLOCKS]; +} UBX_TIM_SMEAS_data_t; + + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t sourceId : 1; + + uint32_t freqValid : 1; + uint32_t phaseValid : 1; + + uint32_t phaseOffsetFrac : 1; + uint32_t phaseUncFrac : 1; + uint32_t phaseOffset : 1; + uint32_t phaseUnc : 1; + uint32_t freqOffset : 1; + uint32_t freqUnc : 1; + } bits; + } moduleQueried; +} UBX_TIM_SMEAS_moduleQueried_t; + + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_TIM_SMEAS_data_t data; // Internal buffer + UBX_TIM_SMEAS_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_TIM_SMEAS_data_t); + UBX_TIM_SMEAS_data_t *callbackData; // Shown to the user +} UBX_TIM_SMEAS_t; + + // UBX-TIM-TM2 (0x0D 0x03): Time mark data const uint16_t UBX_TIM_TM2_LEN = 28;