From 9a3dec086a92715b0bde6caed3ca7e2c6ea03632 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 23 Mar 2023 17:27:58 +0000 Subject: [PATCH 1/5] Move createLock and deleteLock --- src/u-blox_GNSS.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/u-blox_GNSS.cpp b/src/u-blox_GNSS.cpp index eaba1f6..3dde965 100644 --- a/src/u-blox_GNSS.cpp +++ b/src/u-blox_GNSS.cpp @@ -63,8 +63,6 @@ DevUBLOXGNSS::DevUBLOXGNSS(void) _logNMEA.all = 0; // Default to passing no NMEA messages to the file buffer _processNMEA.all = SFE_UBLOX_FILTER_NMEA_ALL; // Default to passing all NMEA messages to processNMEA _logRTCM.all = 0; // Default to passing no RTCM messages to the file buffer - - createLock(); // Create the lock semaphore - if needed } DevUBLOXGNSS::~DevUBLOXGNSS(void) @@ -90,8 +88,6 @@ DevUBLOXGNSS::~DevUBLOXGNSS(void) delete[] spiBuffer; // Created with new[] spiBuffer = nullptr; } - - deleteLock(); // Delete the lock semaphore - if required } // Stop all automatic message processing. Free all used RAM @@ -619,6 +615,8 @@ void DevUBLOXGNSS::end(void) delete sfe_ublox_ubx_logging_list_head; sfe_ublox_ubx_logging_list_head = nullptr; } + + deleteLock(); // Delete the lock semaphore - if required } // Allow the user to change packetCfgPayloadSize. Handy if you want to process big messages like RAWX @@ -754,6 +752,8 @@ uint8_t DevUBLOXGNSS::readBytes(uint8_t *data, uint8_t length) bool DevUBLOXGNSS::init(uint16_t maxWait, bool assumeSuccess) { + createLock(); // Create the lock semaphore - if needed + _signsOfLife = false; // Clear the _signsOfLife flag. It will be set true if valid traffic is seen. // New in v2.0: allocate memory for the packetCfg payload here - if required. (The user may have called setPacketCfgPayloadSize already) From 466348ccb1645110d38a2815164147c38fc4e145 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 24 Mar 2023 07:11:09 +0000 Subject: [PATCH 2/5] Add missing return in extractConfigValueByKey - resolve #12 --- src/u-blox_GNSS.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/u-blox_GNSS.h b/src/u-blox_GNSS.h index cd2271f..4b3042b 100644 --- a/src/u-blox_GNSS.h +++ b/src/u-blox_GNSS.h @@ -500,6 +500,7 @@ class DevUBLOXGNSS if (maxWidth < sizeof(int8_t)) return false; *value = (int8_t)extractSignedChar(pkt, ptr); + return (true); break; case UBX_CFG_U2: case UBX_CFG_E2: From 9e5f77b08f233c47c598d41a1c362461cbc4fbd1 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 24 Mar 2023 10:47:46 +0000 Subject: [PATCH 3/5] Add Auto support for TIM TP --- keywords.txt | 16 ++ src/u-blox_GNSS.cpp | 301 +++++++++++++++++++++++++++++++++ src/u-blox_GNSS.h | 49 ++++-- src/u-blox_external_typedefs.h | 4 + src/u-blox_structs.h | 68 ++++++++ 5 files changed, 423 insertions(+), 15 deletions(-) diff --git a/keywords.txt b/keywords.txt index 87fa220..c02f2c2 100644 --- a/keywords.txt +++ b/keywords.txt @@ -60,6 +60,7 @@ UBX_RXM_MEASX_data_t KEYWORD1 UBX_RXM_QZSSL6_message_data_t KEYWORD1 UBX_TIM_TM2_data_t KEYWORD1 +UBX_TIM_TP_data_t KEYWORD1 UBX_ESF_ALG_data_t KEYWORD1 UBX_ESF_INS_data_t KEYWORD1 @@ -491,6 +492,15 @@ assumeAutoTIMTM2 KEYWORD2 flushTIMTM2 KEYWORD2 logTIMTM2 KEYWORD2 +getTIMTP KEYWORD2 +setAutoTIMTP KEYWORD2 +setAutoTIMTPrate KEYWORD2 +setAutoTIMTPcallback KEYWORD2 +setAutoTIMTPcallbackPtr KEYWORD2 +assumeAutoTIMTP KEYWORD2 +flushTIMTP KEYWORD2 +logTIMTP KEYWORD2 + getEsfAlignment KEYWORD2 getESFALG KEYWORD2 setAutoESFALG KEYWORD2 @@ -666,6 +676,11 @@ getRelPosAccD KEYWORD2 getAOPSTATUSuseAOP KEYWORD2 getAOPSTATUSstatus KEYWORD2 +getTIMTPtowMS KEYWORD2 +getTIMTPtowSubMS KEYWORD2 +getTIMTPweek KEYWORD2 +getTIMTPAsEpoch KEYWORD2 + getESFroll KEYWORD2 getESFpitch KEYWORD2 getESFyaw KEYWORD2 @@ -904,6 +919,7 @@ UBX_RXM_SPARTN LITERAL1 UBX_RXM_QZSSL6 LITERAL1 UBX_TIM_TM2 LITERAL1 +UBX_TIM_TP LITERAL1 UBX_RTCM_MSB LITERAL1 UBX_RTCM_1005 LITERAL1 diff --git a/src/u-blox_GNSS.cpp b/src/u-blox_GNSS.cpp index 3dde965..40d2a9e 100644 --- a/src/u-blox_GNSS.cpp +++ b/src/u-blox_GNSS.cpp @@ -399,6 +399,16 @@ void DevUBLOXGNSS::end(void) packetUBXTIMTM2 = nullptr; } + if (packetUBXTIMTP != nullptr) + { + if (packetUBXTIMTP->callbackData != nullptr) + { + delete packetUBXTIMTP->callbackData; + } + delete packetUBXTIMTP; + packetUBXTIMTP = nullptr; + } + #ifndef SFE_UBLOX_DISABLE_ESF if (packetUBXESFALG != nullptr) { @@ -1397,6 +1407,12 @@ bool DevUBLOXGNSS::autoLookup(uint8_t Class, uint8_t ID, uint16_t *maxSize) *maxSize = UBX_TIM_TM2_LEN; return (packetUBXTIMTM2 != nullptr); } + else if (ID == UBX_TIM_TP) + { + if (maxSize != nullptr) + *maxSize = UBX_TIM_TP_LEN; + return (packetUBXTIMTP != nullptr); + } break; case UBX_CLASS_ESF: #ifndef SFE_UBLOX_DISABLE_ESF @@ -4158,6 +4174,36 @@ void DevUBLOXGNSS::processUBXpacket(ubxPacket *msg) } } } + else if (msg->id == UBX_TIM_TP && msg->len == UBX_TIM_TP_LEN) + { + // Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXTIMTP != nullptr) + { + packetUBXTIMTP->data.towMS = extractLong(msg, 0); + packetUBXTIMTP->data.towSubMS = extractLong(msg, 4); + packetUBXTIMTP->data.qErr = extractSignedLong(msg, 8); + packetUBXTIMTP->data.week = extractInt(msg, 12); + packetUBXTIMTP->data.flags.all = extractByte(msg, 14); + packetUBXTIMTP->data.refInfo.all = extractByte(msg, 15); + + // Mark all datums as fresh (not read before) + packetUBXTIMTP->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + // Check if we need to copy the data for the callback + if ((packetUBXTIMTP->callbackData != nullptr) // If RAM has been allocated for the copy of the data + && (packetUBXTIMTP->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXTIMTP->callbackData->towMS, &packetUBXTIMTP->data.towMS, sizeof(UBX_TIM_TP_data_t)); + packetUBXTIMTP->automaticFlags.flags.bits.callbackCopyValid = true; + } + + // Check if we need to copy the data into the file buffer + if (packetUBXTIMTP->automaticFlags.flags.bits.addToFileBuffer) + { + addedToFileBuffer = storePacket(msg); + } + } + } break; #ifndef SFE_UBLOX_DISABLE_ESF case UBX_CLASS_ESF: @@ -5642,6 +5688,17 @@ void DevUBLOXGNSS::checkCallbacks(void) packetUBXTIMTM2->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale } + if (packetUBXTIMTP != nullptr) // If RAM has been allocated for message storage + if (packetUBXTIMTP->callbackData != nullptr) // If RAM has been allocated for the copy of the data + if (packetUBXTIMTP->automaticFlags.flags.bits.callbackCopyValid == true) // If the copy of the data is valid + { + if (packetUBXTIMTP->callbackPointerPtr != nullptr) // If the pointer to the callback has been defined + { + packetUBXTIMTP->callbackPointerPtr(packetUBXTIMTP->callbackData); // Call the callback + } + packetUBXTIMTP->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + #ifndef SFE_UBLOX_DISABLE_ESF if (packetUBXESFALG != nullptr) // If RAM has been allocated for message storage if (packetUBXESFALG->callbackData != nullptr) // If RAM has been allocated for the copy of the data @@ -13010,6 +13067,172 @@ void DevUBLOXGNSS::logTIMTM2(bool enabled) packetUBXTIMTM2->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; } +// ***** TIM TP automatic support + +bool DevUBLOXGNSS::getTIMTP(uint16_t maxWait) +{ + if (packetUBXTIMTP == nullptr) + initPacketUBXTIMTP(); // Check that RAM has been allocated for the TP data + if (packetUBXTIMTP == nullptr) // Bail if the RAM allocation failed + return (false); + + if (packetUBXTIMTP->automaticFlags.flags.bits.automatic && packetUBXTIMTP->automaticFlags.flags.bits.implicitUpdate) + { + // The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, 0, 0); // Call checkUbloxInternal to parse any incoming data. Don't overwrite the requested Class and ID + return packetUBXTIMTP->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXTIMTP->automaticFlags.flags.bits.automatic && !packetUBXTIMTP->automaticFlags.flags.bits.implicitUpdate) + { + // Someone else has to call checkUblox for us... + return (false); + } + else + { + // The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_TIM; + packetCfg.id = UBX_TIM_TP; + packetCfg.len = 0; + packetCfg.startingSpot = 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) + { + return (true); + } + + return (false); + } +} + +// Enable or disable automatic message generation by the GNSS. This changes the way getTIMTP works. +bool DevUBLOXGNSS::setAutoTIMTP(bool enable, uint8_t layer, uint16_t maxWait) +{ + return setAutoTIMTPrate(enable ? 1 : 0, true, layer, maxWait); +} + +// Enable or disable automatic message generation by the GNSS. This changes the way getTIMTP works. +bool DevUBLOXGNSS::setAutoTIMTP(bool enable, bool implicitUpdate, uint8_t layer, uint16_t maxWait) +{ + return setAutoTIMTPrate(enable ? 1 : 0, implicitUpdate, layer, maxWait); +} + +// Enable or disable automatic message generation by the GNSS. This changes the way getTIMTP works. +bool DevUBLOXGNSS::setAutoTIMTPrate(uint8_t rate, bool implicitUpdate, uint8_t layer, uint16_t maxWait) +{ + if (packetUBXTIMTP == nullptr) + initPacketUBXTIMTP(); // Check that RAM has been allocated for the data + if (packetUBXTIMTP == nullptr) // Only attempt this if RAM allocation was successful + return false; + + if (rate > 127) + rate = 127; + + uint32_t key = UBLOX_CFG_MSGOUT_UBX_TIM_TP_I2C; + if (_commType == COMM_TYPE_SPI) + key = UBLOX_CFG_MSGOUT_UBX_TIM_TP_SPI; + else if (_commType == COMM_TYPE_SERIAL) + { + if (!_UART2) + key = UBLOX_CFG_MSGOUT_UBX_TIM_TP_UART1; + else + key = UBLOX_CFG_MSGOUT_UBX_TIM_TP_UART2; + } + + bool ok = setVal8(key, rate, layer, maxWait); + if (ok) + { + packetUBXTIMTP->automaticFlags.flags.bits.automatic = (rate > 0); + packetUBXTIMTP->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXTIMTP->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + +// Enable automatic message generation by the GNSS. +bool DevUBLOXGNSS::setAutoTIMTPcallbackPtr(void (*callbackPointerPtr)(UBX_TIM_TP_data_t *), uint8_t layer, uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + bool result = setAutoTIMTP(true, false, layer, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXTIMTP->callbackData == nullptr) // Check if RAM has been allocated for the callback copy + { + packetUBXTIMTP->callbackData = new UBX_TIM_TP_data_t; // Allocate RAM for the main struct + } + + if (packetUBXTIMTP->callbackData == nullptr) + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial.println(F("setAutoTIMTPcallbackPtr: RAM alloc failed!")); +#endif + return (false); + } + + packetUBXTIMTP->callbackPointerPtr = callbackPointerPtr; + return (true); +} + +// In case no config access to the GNSS is possible and TIM TP is send cyclically already +// set config to suitable parameters +bool DevUBLOXGNSS::assumeAutoTIMTP(bool enabled, bool implicitUpdate) +{ + if (packetUBXTIMTP == nullptr) + initPacketUBXTIMTP(); // Check that RAM has been allocated for the data + if (packetUBXTIMTP == nullptr) // Only attempt this if RAM allocation was successful + return false; + + bool changes = packetUBXTIMTP->automaticFlags.flags.bits.automatic != enabled || packetUBXTIMTP->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXTIMTP->automaticFlags.flags.bits.automatic = enabled; + packetUBXTIMTP->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXTIMTP and initialize it +bool DevUBLOXGNSS::initPacketUBXTIMTP() +{ + packetUBXTIMTP = new UBX_TIM_TP_t; // Allocate RAM for the main struct + if (packetUBXTIMTP == nullptr) + { +#ifndef SFE_UBLOX_REDUCED_PROG_MEM + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial.println(F("initPacketUBXTIMTP: RAM alloc failed!")); +#endif + return (false); + } + packetUBXTIMTP->automaticFlags.flags.all = 0; + packetUBXTIMTP->callbackPointerPtr = nullptr; + packetUBXTIMTP->callbackData = nullptr; + packetUBXTIMTP->moduleQueried.moduleQueried.all = 0; + return (true); +} + +// Mark all the data as read/stale +void DevUBLOXGNSS::flushTIMTP() +{ + if (packetUBXTIMTP == nullptr) + return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXTIMTP->moduleQueried.moduleQueried.all = 0; // Mark all datums as stale (read before) +} + +// Log this data in file buffer +void DevUBLOXGNSS::logTIMTP(bool enabled) +{ + if (packetUBXTIMTP == nullptr) + return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXTIMTP->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + #ifndef SFE_UBLOX_DISABLE_ESF // ***** ESF ALG automatic support @@ -16545,6 +16768,84 @@ uint8_t DevUBLOXGNSS::getAOPSTATUSstatus(uint16_t maxWait) return (packetUBXNAVAOPSTATUS->data.status); } +// ***** TIM TP Helper Functions + +uint32_t DevUBLOXGNSS::getTIMTPtowMS(uint16_t maxWait) +{ + if (packetUBXTIMTP == nullptr) + initPacketUBXTIMTP(); // Check that RAM has been allocated for the TP data + if (packetUBXTIMTP == nullptr) // Bail if the RAM allocation failed + return 0; + + if (packetUBXTIMTP->moduleQueried.moduleQueried.bits.towMS == false) + getTIMTP(maxWait); + packetUBXTIMTP->moduleQueried.moduleQueried.bits.towMS = false; // Since we are about to give this to user, mark this data as stale + packetUBXTIMTP->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXTIMTP->data.towMS); +} + +uint32_t DevUBLOXGNSS::getTIMTPtowSubMS(uint16_t maxWait) +{ + if (packetUBXTIMTP == nullptr) + initPacketUBXTIMTP(); // Check that RAM has been allocated for the TP data + if (packetUBXTIMTP == nullptr) // Bail if the RAM allocation failed + return 0; + + if (packetUBXTIMTP->moduleQueried.moduleQueried.bits.towSubMS == false) + getTIMTP(maxWait); + packetUBXTIMTP->moduleQueried.moduleQueried.bits.towSubMS = false; // Since we are about to give this to user, mark this data as stale + packetUBXTIMTP->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXTIMTP->data.towSubMS); +} + +uint16_t DevUBLOXGNSS::getTIMTPweek(uint16_t maxWait) +{ + if (packetUBXTIMTP == nullptr) + initPacketUBXTIMTP(); // Check that RAM has been allocated for the TP data + if (packetUBXTIMTP == nullptr) // Bail if the RAM allocation failed + return 0; + + if (packetUBXTIMTP->moduleQueried.moduleQueried.bits.week == false) + getTIMTP(maxWait); + packetUBXTIMTP->moduleQueried.moduleQueried.bits.week = false; // Since we are about to give this to user, mark this data as stale + packetUBXTIMTP->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXTIMTP->data.week); +} + +// Convert TIM TP to Unix epoch including microseconds +// CAUTION! Assumes the time base is UTC and the week number is GPS +uint32_t DevUBLOXGNSS::getTIMTPAsEpoch(uint32_t µsecond, uint16_t maxWait) +{ + if (packetUBXNAVPVT == nullptr) + initPacketUBXNAVPVT(); // Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == nullptr) // Bail if the RAM allocation failed + return 0; + + if (packetUBXTIMTP->moduleQueried.moduleQueried.bits.week == false) + getTIMTP(maxWait); + packetUBXTIMTP->moduleQueried.moduleQueried.bits.week = false; // Since we are about to give this to user, mark this data as stale + packetUBXTIMTP->moduleQueried.moduleQueried.bits.towMS = false; + packetUBXTIMTP->moduleQueried.moduleQueried.bits.towSubMS = false; + packetUBXTIMTP->moduleQueried.moduleQueried.bits.all = false; + + uint32_t tow = packetUBXTIMTP->data.week - SFE_UBLOX_JAN_1ST_2020_WEEK; // Calculate the number of weeks since Jan 1st 2020 + tow *= SFE_UBLOX_SECS_PER_WEEK; // Convert weeks to seconds + tow += SFE_UBLOX_EPOCH_WEEK_2086; // Add the TOW for Jan 1st 2020 + tow += packetUBXTIMTP->data.towMS / 1000; // Add the TOW for the next TP + + uint32_t us = packetUBXTIMTP->data.towMS % 1000; // Extract the milliseconds + us *= 1000; // Convert to microseconds + + double subMS = packetUBXTIMTP->data.towSubMS; // Get towSubMS (ms * 2^-32) + subMS *= pow(2.0, -32.0); // Convert to milliseconds + subMS *= 1000; // Convert to microseconds + + us += (int32_t)subMS; // Add subMS + + microsecond = us; + return tow; +} + #ifndef SFE_UBLOX_DISABLE_ESF // ***** ESF Helper Functions diff --git a/src/u-blox_GNSS.h b/src/u-blox_GNSS.h index 4b3042b..065d72c 100644 --- a/src/u-blox_GNSS.h +++ b/src/u-blox_GNSS.h @@ -137,12 +137,12 @@ class DevUBLOXGNSS // Needed to select the correct config items when enabling a periodic message bool _UART2 = false; // Default to UART1 - // These lock / unlock functions can be used if you have multiple tasks writing to the bus. + // These lock / unlock functions can be used if you have multiple tasks writing to the bus. // The idea is that in a RTOS you override this class and the functions in which you take and give a mutex. virtual bool createLock(void) { return true; } virtual bool lock(void) { return true; } - virtual void unlock(void) { } - virtual void deleteLock(void) { } + virtual void unlock(void) {} + virtual void deleteLock(void) {} public: void connectedToUART2(bool connected = true) { _UART2 = connected; } @@ -189,10 +189,10 @@ class DevUBLOXGNSS // Boards like the RedBoard Turbo use SerialUSB (not Serial). // But other boards like the SAMD51 Thing Plus use Serial (not SerialUSB). // These lines let the code compile cleanly on as many SAMD boards as possible. -#if defined(ARDUINO_ARCH_SAMD) // Is this a SAMD board? -#if defined(USB_VID) // Is the USB Vendor ID defined? -#if (USB_VID == 0x1B4F) // Is this a SparkFun board? -#if !defined(ARDUINO_SAMD51_THING_PLUS) & !defined(ARDUINO_SAMD51_MICROMOD) // If it is not a SAMD51 Thing Plus or SAMD51 MicroMod +#if defined(ARDUINO_ARCH_SAMD) // Is this a SAMD board? +#if defined(USB_VID) // Is the USB Vendor ID defined? +#if (USB_VID == 0x1B4F) // Is this a SparkFun board? +#if !defined(ARDUINO_SAMD51_THING_PLUS) & !defined(ARDUINO_SAMD51_MICROMOD) // If it is not a SAMD51 Thing Plus or SAMD51 MicroMod void enableDebugging(Print &debugPort = SerialUSB, bool printLimitedDebug = false); // Given a port to print to, enable debug messages. Default to all, not limited. #else void enableDebugging(Print &debugPort = Serial, bool printLimitedDebug = false); // Given a port to print to, enable debug messages. Default to all, not limited. @@ -375,6 +375,7 @@ class DevUBLOXGNSS bool getModuleInfo(uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Queries module, extracts info protected: bool prepareModuleInfo(uint16_t maxWait); + public: moduleSWVersion_t *moduleSWVersion = nullptr; // Pointer to struct. RAM will be allocated for this if/when necessary @@ -394,10 +395,10 @@ class DevUBLOXGNSS uint8_t getDynamicModel(uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Get the dynamic model - returns 255 if the sendCommand fails // Reset / enable / configure the odometer - bool resetOdometer(uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Reset the odometer - bool enableOdometer(bool enable = true, uint8_t layer = VAL_LAYER_RAM_BBR, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Enable / disable the odometer + bool resetOdometer(uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Reset the odometer + bool enableOdometer(bool enable = true, uint8_t layer = VAL_LAYER_RAM_BBR, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Enable / disable the odometer bool getOdometerConfig(uint8_t *flags, uint8_t *odoCfg, uint8_t *cogMaxSpeed, uint8_t *cogMaxPosAcc, uint8_t *velLpGain, uint8_t *cogLpGain, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Read the odometer configuration - bool setOdometerConfig(uint8_t flags, uint8_t odoCfg, uint8_t cogMaxSpeed, uint8_t cogMaxPosAcc, uint8_t velLpGain, uint8_t cogLpGain, uint8_t layer = VAL_LAYER_RAM_BBR, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Configure the odometer + bool setOdometerConfig(uint8_t flags, uint8_t odoCfg, uint8_t cogMaxSpeed, uint8_t cogMaxPosAcc, uint8_t velLpGain, uint8_t cogLpGain, uint8_t layer = VAL_LAYER_RAM_BBR, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Configure the odometer // Enable/Disable individual GNSS systems using UBX-CFG-GNSS // Note: you must leave at least one major GNSS enabled! If in doubt, enable GPS before disabling the others @@ -943,6 +944,15 @@ class DevUBLOXGNSS void flushTIMTM2(); // Mark all the data as read/stale void logTIMTM2(bool enabled = true); // Log data to file buffer + bool getTIMTP(uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // TIM TP + bool setAutoTIMTP(bool enabled, uint8_t layer = VAL_LAYER_RAM_BBR, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Enable/disable automatic TIM TP reports at the navigation frequency + bool setAutoTIMTP(bool enabled, bool implicitUpdate, uint8_t layer = VAL_LAYER_RAM_BBR, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Enable/disable automatic TIM TP 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 setAutoTIMTPrate(uint8_t rate, bool implicitUpdate = true, uint8_t layer = VAL_LAYER_RAM_BBR, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Set the rate for automatic TIM TP reports + bool setAutoTIMTPcallbackPtr(void (*callbackPointerPtr)(UBX_TIM_TP_data_t *), uint8_t layer = VAL_LAYER_RAM_BBR, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Enable automatic TP reports at the navigation frequency. Data is accessed from the callback. + bool assumeAutoTIMTP(bool enabled, bool implicitUpdate = true); // In case no config access to the GPS is possible and TIM TP is send cyclically already + void flushTIMTP(); // Mark all the data as read/stale + void logTIMTP(bool enabled = true); // Log data to file buffer + #ifndef SFE_UBLOX_DISABLE_ESF // Sensor fusion (dead reckoning) (ESF) @@ -1161,6 +1171,13 @@ class DevUBLOXGNSS uint8_t getAOPSTATUSuseAOP(uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Returns the UBX-NAV-AOPSTATUS useAOP flag. Don't confuse this with getAopCfg - which returns the aopCfg byte from UBX-CFG-NAVX5 uint8_t getAOPSTATUSstatus(uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Returns the UBX-NAV-AOPSTATUS status field. A host application can determine the optimal time to shut down the receiver by monitoring the status field for a steady 0. + // Helper functions for TIM TP + + uint32_t getTIMTPtowMS(uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Returns the UBX-TIM-TP towMS time pulse of week (ms) + uint32_t getTIMTPtowSubMS(uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Returns the UBX-TIM-TP submillisecond part of towMS (ms * 2^-32) + uint16_t getTIMTPweek(uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Returns the UBX-TIM-TP time pulse week according to time base + uint32_t getTIMTPAsEpoch(uint32_t µsecond, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Convert TIM TP to Unix Epoch - CAUTION! Assumes the time base is UTC and the week number is GPS + #ifndef SFE_UBLOX_DISABLE_ESF // Helper functions for ESF @@ -1273,6 +1290,7 @@ class DevUBLOXGNSS #endif UBX_TIM_TM2_t *packetUBXTIMTM2 = nullptr; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_TIM_TP_t *packetUBXTIMTP = nullptr; // Pointer to struct. RAM will be allocated for this if/when necessary #ifndef SFE_UBLOX_DISABLE_ESF UBX_ESF_ALG_t *packetUBXESFALG = nullptr; // Pointer to struct. RAM will be allocated for this if/when necessary @@ -1354,6 +1372,7 @@ class DevUBLOXGNSS bool initPacketUBXRXMRAWX(); // Allocate RAM for packetUBXRXMRAWX and initialize it bool initPacketUBXRXMMEASX(); // Allocate RAM for packetUBXRXMMEASX and initialize it bool initPacketUBXTIMTM2(); // Allocate RAM for packetUBXTIMTM2 and initialize it + bool initPacketUBXTIMTP(); // Allocate RAM for packetUBXTIMTP 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 @@ -1462,20 +1481,20 @@ class DevUBLOXGNSS #endif // RTCM logging - sfe_ublox_rtcm_filtering_t _logRTCM; // Flags to indicate which NMEA messages should be added to the file buffer for logging + sfe_ublox_rtcm_filtering_t _logRTCM; // Flags to indicate which NMEA messages should be added to the file buffer for logging #ifndef SFE_UBLOX_DISABLE_RTCM_LOGGING - RTCM_FRAME_t *_storageRTCM = nullptr; // Pointer to struct. RAM will be allocated for this if/when necessary + RTCM_FRAME_t *_storageRTCM = nullptr; // Pointer to struct. RAM will be allocated for this if/when necessary void crc24q(uint8_t incoming, uint32_t *checksum); // Add incoming to checksum as per CRC-24Q #endif - //Define the maximum possible message length for packetAuto and enableUBXlogging - //UBX_NAV_SAT_MAX_LEN is just > UBX_RXM_RAWX_MAX_LEN + // Define the maximum possible message length for packetAuto and enableUBXlogging + // UBX_NAV_SAT_MAX_LEN is just > UBX_RXM_RAWX_MAX_LEN const uint16_t SFE_UBX_MAX_LENGTH = UBX_NAV_SAT_MAX_LEN; // UBX logging sfe_ublox_ubx_logging_list_t *sfe_ublox_ubx_logging_list_head = nullptr; // Linked list of which messages to log - bool logThisUBX(uint8_t UBX_CLASS, uint8_t UBX_ID); // Returns true if this UBX should be added to the logging buffer + bool logThisUBX(uint8_t UBX_CLASS, uint8_t UBX_ID); // Returns true if this UBX should be added to the logging buffer // Flag to prevent reentry into checkCallbacks // Prevent badness if the user accidentally calls checkCallbacks from inside a callback diff --git a/src/u-blox_external_typedefs.h b/src/u-blox_external_typedefs.h index 6a0fe22..ee3b36b 100644 --- a/src/u-blox_external_typedefs.h +++ b/src/u-blox_external_typedefs.h @@ -332,3 +332,7 @@ const uint16_t SFE_UBLOX_DAYS_SINCE_MONTH[2][12] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}, // Leap Year (Year % 4 == 0) {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334} // Normal Year }; + +const uint32_t SFE_UBLOX_JAN_1ST_2020_WEEK = 2086; // GPS Week Number for Jan 1st 2020 +const uint32_t SFE_UBLOX_EPOCH_WEEK_2086 = 1577836800 - 259200; // Epoch for the start of GPS week 2086 +const uint32_t SFE_UBLOX_SECS_PER_WEEK = 60 * 60 * 24 * 7; // Seconds per week diff --git a/src/u-blox_structs.h b/src/u-blox_structs.h index 843cce6..dea0205 100644 --- a/src/u-blox_structs.h +++ b/src/u-blox_structs.h @@ -1920,6 +1920,74 @@ typedef struct UBX_TIM_TM2_data_t *callbackData; } UBX_TIM_TM2_t; +// UBX-TIM-TP (0x0D 0x01): Time pulse time data +// Contains the Time-Pulse-Of-Week for the _next_ time pulse +// (PVT etc. provide the time of the _previous_ time pulse) +const uint16_t UBX_TIM_TP_LEN = 16; + +typedef struct +{ + uint32_t towMS; // Time pulse time of week according to time base : ms + uint32_t towSubMS; // Submillisecond part of towMS : ms * 2^-32 + int32_t qErr; // Quantization error of time pulse : ps + uint16_t week; // Time pulse week number according to time base : weeks + union + { + uint8_t all; + struct + { + uint8_t timeBase : 1; // 0=GNSS; 1=UTC + uint8_t utc : 1; // 0=UTC not available; 1=UTC available + uint8_t raim : 2; // 0=RAIM information not available; 1=RAIM not active; 2=RAIM Active + uint8_t qErrInvalid : 1; // 0=Quantization error invalid; 1=valid + } bits; + } flags; + union + { + uint8_t all; + struct + { + uint8_t timeRefGnss : 4; // GNSS Reference: 0=GPS; 1=GLONASS; 2=BeiDou; 3=Galileo; 4=NavIC; 15=Unknown + uint8_t utcStandard : 4; // UTC Standard ID - if timeBase = 1: 0=not available; 1=CRL; 2=NIST; 3=USNO; + // 4=BIPM; 5=Eu; 6=SU; 7=NTSC; 8=NPLI; 15=Unknown + } bits; + } refInfo; +} UBX_TIM_TP_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t towMS : 1; + uint32_t towSubMS : 1; + uint32_t qErr : 1; + uint32_t week : 1; + + uint32_t timeBase : 1; + uint32_t utc : 1; + uint32_t raim : 1; + uint32_t qErrInvalid : 1; + + uint32_t timeRefGnss : 1; + uint32_t utcStandard : 1; + } bits; + } moduleQueried; +} UBX_TIM_TP_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_TIM_TP_data_t data; + UBX_TIM_TP_moduleQueried_t moduleQueried; + void (*callbackPointerPtr)(UBX_TIM_TP_data_t *); + UBX_TIM_TP_data_t *callbackData; +} UBX_TIM_TP_t; + // ESF-specific structs // UBX-ESF-ALG (0x10 0x14): IMU alignment information From b28bbd6a87fb232a41145ca8b73b146b719c8104 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 24 Mar 2023 10:58:23 +0000 Subject: [PATCH 4/5] v3.0.6 --- library.properties | 2 +- src/u-blox_GNSS.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.properties b/library.properties index bf757ba..10fe3ea 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SparkFun u-blox GNSS v3 -version=3.0.5 +version=3.0.6 author=SparkFun Electronics maintainer=SparkFun Electronics sentence=Library for I2C, Serial and SPI Communication with u-blox GNSS modules

diff --git a/src/u-blox_GNSS.cpp b/src/u-blox_GNSS.cpp index 40d2a9e..03901f6 100644 --- a/src/u-blox_GNSS.cpp +++ b/src/u-blox_GNSS.cpp @@ -16840,7 +16840,7 @@ uint32_t DevUBLOXGNSS::getTIMTPAsEpoch(uint32_t µsecond, uint16_t maxWait) subMS *= pow(2.0, -32.0); // Convert to milliseconds subMS *= 1000; // Convert to microseconds - us += (int32_t)subMS; // Add subMS + us += (uint32_t)subMS; // Add subMS microsecond = us; return tow; From 1b14c47f2075ed32e5628d001a5d4ba2906f7149 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 24 Mar 2023 11:06:09 +0000 Subject: [PATCH 5/5] Add Basics Example32 - TIM TP --- .../Example32_TIMTP/Example32_TIMTP.ino | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 examples/Basics/Example32_TIMTP/Example32_TIMTP.ino diff --git a/examples/Basics/Example32_TIMTP/Example32_TIMTP.ino b/examples/Basics/Example32_TIMTP/Example32_TIMTP.ino new file mode 100644 index 0000000..b9815fe --- /dev/null +++ b/examples/Basics/Example32_TIMTP/Example32_TIMTP.ino @@ -0,0 +1,115 @@ +/* + Displaying the TIM TP timing information for the _next_ TP time pulse + (PVT provides the time of the _previous_ pulse) + By: Paul Clark + SparkFun Electronics + Date: March 24th, 2023 + License: MIT. See license file for more information. + + This example shows how to query a u-blox module for the TIM TP time-pulse-of-week. + This contains the timing information for the _next_ time pulse. I.e. it is output + _ahead_ of time. PVT contains the time of the _previous_ pulse. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + SparkFun GPS-RTK2 - ZED-F9P (GPS-15136) https://www.sparkfun.com/products/15136 + SparkFun GPS-RTK-SMA - ZED-F9P (GPS-16481) https://www.sparkfun.com/products/16481 + SparkFun MAX-M10S Breakout (GPS-18037) https://www.sparkfun.com/products/18037 + SparkFun ZED-F9K Breakout (GPS-18719) https://www.sparkfun.com/products/18719 + SparkFun ZED-F9R Breakout (GPS-16344) https://www.sparkfun.com/products/16344 + + 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 //http://librarymanager/All#SparkFun_u-blox_GNSS_v3 +SFE_UBLOX_GNSS myGNSS; + +void setup() +{ + delay(1000); + + Serial.begin(115200); + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + while (myGNSS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring.")); + } + + myGNSS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) +} + +void loop() +{ +/* + // Read the time pulse of week and construct the microseconds + if (myGNSS.getTIMTP()) //getTIMTP will return true if new fresh data is received + { + uint16_t week = myGNSS.getTIMTPweek(); //Get the time base week number + uint32_t towMS = myGNSS.getTIMTPtowMS(); //Get the time base pulse-of-week (ms) + uint32_t towSubMS = myGNSS.getTIMTPtowSubMS(); //Read the sub-millisecond data (ms * 2^-32) + + double tow = towMS; + tow /= 1000.0; //Convert to seconds + + double subMS = towSubMS; + subMS *= pow(2.0, -32.0); //Convert to ms + subMS /= 1000.0; //Convert to seconds + + tow += subMS; //Construct the time of week + + Serial.print("TIM TP: week: "); + Serial.print(week); //Print the time base week number + Serial.print(" tow: "); + Serial.println(tow, 6); //Print the time of week with six deimal places (i.e. show the microseconds) + } +*/ + + //getTIMTP and getPVT will return true if fresh data is available. + //Note: both methods poll data from the GNSS: + // getTIMTP will poll TIM TP, wait and return true when the TIM TP data arrives. + // Then getPVT will poll NAV PVT, wait and return true when the NAV PVT data arrives. + // The TIM TP data will then be one second old. + // Because TIM TP provides the time of the _next_ time pulse and NAV PVT provides + // the time of the _previous_ time pulse, the two Epochs should be the same! + if (myGNSS.getTIMTP() && myGNSS.getPVT()) + { + // Read the time pulse of week as Unix Epoch + // CAUTION! Assumes the time base is UTC and the week number is GPS + uint32_t microsTP; + uint32_t epochTP = myGNSS.getTIMTPAsEpoch(microsTP); //Read the next time pulse of week as Unix Epoch + + Serial.print("TIM TP as Epoch: "); + Serial.print(epochTP); //Print the time of the next pulse + Serial.print("."); + if (microsTP < 100000) Serial.print("0"); //Pad the zeros if needed + if (microsTP < 10000) Serial.print("0"); + if (microsTP < 1000) Serial.print("0"); + if (microsTP < 100) Serial.print("0"); + if (microsTP < 10) Serial.print("0"); + Serial.println(microsTP); + + // Read the PVT time of week as Unix Epoch + uint32_t microsPVT; + uint32_t epochPVT = myGNSS.getUnixEpoch(microsPVT); //Read the time of week as Unix Epoch + + Serial.print("NAV PVT as Epoch: "); + Serial.print(epochPVT); //Print the time of the next pulse + Serial.print("."); + if (microsPVT < 100000) Serial.print("0"); //Pad the zeros if needed + if (microsPVT < 10000) Serial.print("0"); + if (microsPVT < 1000) Serial.print("0"); + if (microsPVT < 100) Serial.print("0"); + if (microsPVT < 10) Serial.print("0"); + Serial.println(microsPVT); + + Serial.println(); + } +}