From 6627841e7dfcd1d6d91dcb54b50564ba8ea28174 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Sat, 9 Jan 2021 06:44:12 +0000 Subject: [PATCH] Initial commit - based on v2_candidate --- .gitattributes | 2 + .gitignore | 58 + CONTRIBUTING.md | 20 + ISSUE_TEMPLATE.md | 18 + LICENSE.md | 55 + README.md | 113 + Theory.md | 117 + Utils/UBX_Integrity_Checker.py | 399 + .../CallbackExample1_NAV_PVT.ino | 104 + .../CallbackExample2_NAV_ODO.ino | 89 + .../CallbackExample3_TIM_TM2.ino | 115 + .../CallbackExample4_HNR.ino | 117 + .../CallbackExample5_ESF.ino | 179 + .../DataLoggingExample1_NAV_PVT.ino | 230 + .../DataLoggingExample2_TIM_TM2.ino | 241 + ...DataLoggingExample3_RXM_SFRBX_and_RAWX.ino | 273 + ...aLoggingExample4_RXM_without_Callbacks.ino | 247 + .../DataLoggingExample5_Fast_RXM.ino | 272 + .../Example1_calibrateSensor.ino | 75 + .../Example2_getIMUData.ino | 91 + .../Example3_getSensorStatus.ino | 125 + .../Example4_vehicleDynamics.ino | 83 + .../Example5_getHNRData.ino | 94 + .../Example6_getAutoHNRData.ino | 107 + .../Example10_AltitudeMSL.ino | 82 + .../Example1_FactoryDefaultviaI2C.ino | 60 + .../Example2_FactoryDefaultsviaSerial.ino | 110 + .../Example12_UseUart/Example12_UseUart.ino | 98 + .../Example1_AutoPVT/Example1_AutoPVT.ino | 141 + .../Example2_AutoPVT_ExplicitUpdate.ino | 103 + .../Example3_AutoPVTviaUart.ino | 107 + .../Example4_AssumeAutoPVTviaUart.ino | 110 + .../Example14_DebugOutput.ino | 107 + .../Example15_GetDateTime.ino | 107 + .../Example16_Nanosecond_MaxOutput.ino | 109 + .../Example16_PartialSecond_MaxOutput.ino | 118 + .../Example17_Geofence/Example17_Geofence.ino | 169 + .../Example18_PowerSaveMode.ino | 163 + .../Example19_DynamicModel.ino | 117 + .../Example1_BasicNMEARead.ino | 51 + .../Example20_SendCustomCommand.ino | 166 + .../Example21_ModuleInfo.ino | 183 + .../Example22_PowerOff/Example22_PowerOff.ino | 88 + .../Example2_NMEAParsing.ino | 83 + .../Example3_GetPosition.ino | 84 + .../Example4_FixType/Example4_FixType.ino | 96 + .../Example5_SpeedHeadingPrecision.ino | 83 + .../Example6_EnableNMEASentences.ino | 79 + .../Example7_OutputRate.ino | 94 + .../Example8_GetProtocolVersion.ino | 61 + .../Example8_GetProtocolVersion_Serial.ino | 81 + .../Example9_ChangeI2CAddress.ino | 116 + .../Example1_EnableRTCM.ino | 77 + .../Example2_StartRTCMBase.ino | 164 + .../Example3_BaseWithLCD.ino | 195 + ...Example1_GetPositionAndTime_Series_6_7.ino | 109 + ...rkFun_Ublox_Arduino_Library_Series_6_7.cpp | 3477 ++++++ ...parkFun_Ublox_Arduino_Library_Series_6_7.h | 932 ++ ...10_GetHighPrecisionPositionAndAccuracy.ino | 154 + ...11_GetHighPrecisionPositionUsingDouble.ino | 146 + .../Example12_setStaticPosition.ino | 77 + .../Example13_autoHPPOSLLH.ino | 119 + .../Example13_autoHPPOSLLH_with_Callback.ino | 150 + .../Example14_NTRIPServer.ino | 276 + .../ZED-F9P/Example14_NTRIPServer/secrets.h | 7 + .../Example1_GetPositionAccuracy.ino | 76 + .../Example2_ValConfigurationMethod.ino | 63 + .../Example3_StartRTCMBase.ino | 194 + .../Example4_BaseWithLCD.ino | 210 + ...xample5_RelativePositioningInformation.ino | 165 + .../Example6_GetVal/Example6_GetVal.ino | 60 + .../Example7_SetVal/Example7_SetVal.ino | 76 + .../Example8_GetSetPortSettings.ino | 98 + .../Example9_multiSetVal.ino | 90 + .../CMakeLists.txt | 14 + .../nrf52840dk_nrf52840.overlay | 4 + .../prj.conf | 7 + .../src/SparkFun_Ublox_Zephyr_Interface.cpp | 100 + .../src/SparkFun_Ublox_Zephyr_Interface.h | 29 + .../src/SparkFun_Ublox_Zephyr_Library.cpp | 3492 ++++++ .../src/SparkFun_Ublox_Zephyr_Library.h | 901 ++ .../src/main.c | 102 + img/Contributing.JPG | Bin 0 -> 62131 bytes keywords.txt | 604 + library.properties | 9 + src/SparkFun_Ublox_Arduino_Library.cpp | 9737 +++++++++++++++++ src/SparkFun_Ublox_Arduino_Library.h | 1192 ++ src/u-blox_config_keys.h | 839 ++ src/u-blox_structs.h | 1792 +++ 89 files changed, 31927 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 ISSUE_TEMPLATE.md create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 Theory.md create mode 100644 Utils/UBX_Integrity_Checker.py create mode 100644 examples/Callbacks/CallbackExample1_NAV_PVT/CallbackExample1_NAV_PVT.ino create mode 100644 examples/Callbacks/CallbackExample2_NAV_ODO/CallbackExample2_NAV_ODO.ino create mode 100644 examples/Callbacks/CallbackExample3_TIM_TM2/CallbackExample3_TIM_TM2.ino create mode 100644 examples/Callbacks/CallbackExample4_HNR/CallbackExample4_HNR.ino create mode 100644 examples/Callbacks/CallbackExample5_ESF/CallbackExample5_ESF.ino create mode 100644 examples/Data_Logging/DataLoggingExample1_NAV_PVT/DataLoggingExample1_NAV_PVT.ino create mode 100644 examples/Data_Logging/DataLoggingExample2_TIM_TM2/DataLoggingExample2_TIM_TM2.ino create mode 100644 examples/Data_Logging/DataLoggingExample3_RXM_SFRBX_and_RAWX/DataLoggingExample3_RXM_SFRBX_and_RAWX.ino create mode 100644 examples/Data_Logging/DataLoggingExample4_RXM_without_Callbacks/DataLoggingExample4_RXM_without_Callbacks.ino create mode 100644 examples/Data_Logging/DataLoggingExample5_Fast_RXM/DataLoggingExample5_Fast_RXM.ino create mode 100644 examples/Dead_Reckoning/Example1_calibrateSensor/Example1_calibrateSensor.ino create mode 100644 examples/Dead_Reckoning/Example2_getIMUData/Example2_getIMUData.ino create mode 100644 examples/Dead_Reckoning/Example3_getSensorStatus/Example3_getSensorStatus.ino create mode 100644 examples/Dead_Reckoning/Example4_vehicleDynamics/Example4_vehicleDynamics.ino create mode 100644 examples/Dead_Reckoning/Example5_getHNRData/Example5_getHNRData.ino create mode 100644 examples/Dead_Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino create mode 100644 examples/Example10_AltitudeMSL/Example10_AltitudeMSL.ino create mode 100644 examples/Example11_ResetModule/Example1_FactoryDefaultviaI2C/Example1_FactoryDefaultviaI2C.ino create mode 100644 examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino create mode 100644 examples/Example12_UseUart/Example12_UseUart.ino create mode 100644 examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino create mode 100644 examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino create mode 100644 examples/Example13_PVT/Example3_AutoPVTviaUart/Example3_AutoPVTviaUart.ino create mode 100644 examples/Example13_PVT/Example4_AssumeAutoPVTviaUart/Example4_AssumeAutoPVTviaUart.ino create mode 100644 examples/Example14_DebugOutput/Example14_DebugOutput.ino create mode 100644 examples/Example15_GetDateTime/Example15_GetDateTime.ino create mode 100644 examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino create mode 100644 examples/Example16_PartialSecond_MaxOutput/Example16_PartialSecond_MaxOutput.ino create mode 100644 examples/Example17_Geofence/Example17_Geofence.ino create mode 100644 examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino create mode 100644 examples/Example19_DynamicModel/Example19_DynamicModel.ino create mode 100644 examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino create mode 100644 examples/Example20_SendCustomCommand/Example20_SendCustomCommand.ino create mode 100644 examples/Example21_ModuleInfo/Example21_ModuleInfo.ino create mode 100644 examples/Example22_PowerOff/Example22_PowerOff.ino create mode 100644 examples/Example2_NMEAParsing/Example2_NMEAParsing.ino create mode 100644 examples/Example3_GetPosition/Example3_GetPosition.ino create mode 100644 examples/Example4_FixType/Example4_FixType.ino create mode 100644 examples/Example5_SpeedHeadingPrecision/Example5_SpeedHeadingPrecision.ino create mode 100644 examples/Example6_EnableNMEASentences/Example6_EnableNMEASentences.ino create mode 100644 examples/Example7_OutputRate/Example7_OutputRate.ino create mode 100644 examples/Example8_GetProtocolVersion/Example8_GetProtocolVersion.ino create mode 100644 examples/Example8_GetProtocolVersion_Serial/Example8_GetProtocolVersion_Serial.ino create mode 100644 examples/Example9_ChangeI2CAddress/Example9_ChangeI2CAddress.ino create mode 100644 examples/NEO-M8P-2/Example1_EnableRTCM/Example1_EnableRTCM.ino create mode 100644 examples/NEO-M8P-2/Example2_StartRTCMBase/Example2_StartRTCMBase.ino create mode 100644 examples/NEO-M8P-2/Example3_BaseWithLCD/Example3_BaseWithLCD.ino create mode 100644 examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/Example1_GetPositionAndTime_Series_6_7.ino create mode 100644 examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.cpp create mode 100644 examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.h create mode 100644 examples/ZED-F9P/Example10_GetHighPrecisionPositionAndAccuracy/Example10_GetHighPrecisionPositionAndAccuracy.ino create mode 100644 examples/ZED-F9P/Example11_GetHighPrecisionPositionUsingDouble/Example11_GetHighPrecisionPositionUsingDouble.ino create mode 100644 examples/ZED-F9P/Example12_setStaticPosition/Example12_setStaticPosition.ino create mode 100644 examples/ZED-F9P/Example13_autoHPPOSLLH/Example13_autoHPPOSLLH.ino create mode 100644 examples/ZED-F9P/Example13_autoHPPOSLLH_with_Callback/Example13_autoHPPOSLLH_with_Callback.ino create mode 100644 examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino create mode 100644 examples/ZED-F9P/Example14_NTRIPServer/secrets.h create mode 100644 examples/ZED-F9P/Example1_GetPositionAccuracy/Example1_GetPositionAccuracy.ino create mode 100644 examples/ZED-F9P/Example2_ValConfigurationMethod/Example2_ValConfigurationMethod.ino create mode 100644 examples/ZED-F9P/Example3_StartRTCMBase/Example3_StartRTCMBase.ino create mode 100644 examples/ZED-F9P/Example4_BaseWithLCD/Example4_BaseWithLCD.ino create mode 100644 examples/ZED-F9P/Example5_RelativePositioningInformation/Example5_RelativePositioningInformation.ino create mode 100644 examples/ZED-F9P/Example6_GetVal/Example6_GetVal.ino create mode 100644 examples/ZED-F9P/Example7_SetVal/Example7_SetVal.ino create mode 100644 examples/ZED-F9P/Example8_GetSetPortSettings/Example8_GetSetPortSettings.ino create mode 100644 examples/ZED-F9P/Example9_multiSetVal/Example9_multiSetVal.ino create mode 100644 examples/Zephyr/Example1_GetPositionAndTime_Zephyr/CMakeLists.txt create mode 100644 examples/Zephyr/Example1_GetPositionAndTime_Zephyr/nrf52840dk_nrf52840.overlay create mode 100644 examples/Zephyr/Example1_GetPositionAndTime_Zephyr/prj.conf create mode 100644 examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.cpp create mode 100644 examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.h create mode 100644 examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.cpp create mode 100644 examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.h create mode 100644 examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/main.c create mode 100644 img/Contributing.JPG create mode 100644 keywords.txt create mode 100644 library.properties create mode 100644 src/SparkFun_Ublox_Arduino_Library.cpp create mode 100644 src/SparkFun_Ublox_Arduino_Library.h create mode 100644 src/u-blox_config_keys.h create mode 100644 src/u-blox_structs.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58b4bef --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Visual Studio Code files +.vscode + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# VIM backup files +*~ +[._]*.un~ +*.swp + +# Zephyr build files +examples/Zephyr/*/build/* \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ff79aea --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,20 @@ +# How to Contribute + +Thank you so *much* for offering to help out. We truly appreciate it. + +If you'd like to contribute, start by searching through the [issues](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues) and [pull requests](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/pulls) to see whether someone else has raised a similar idea or question. +Please check the [closed issues](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues?q=is%3Aissue+is%3Aclosed) +and [closed pull requests](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/pulls?q=is%3Apr+is%3Aclosed) too - you may find that your issue or feature has already been discussed. + +If you decide to add a feature to this library, please create a PR and follow these best practices: + +* Change as little as possible. Do not submit a PR that changes 100 lines of whitespace. Break up into multiple PRs if necessary. +* If you've added a new feature document it with a simple example sketch. This serves both as a test of your PR and as a quick way for users to quickly learn how to use your new feature. +* If you add new functions also add them to _keywords.txt_ so that they are properly highlighted in Arduino. [Read more](https://www.arduino.cc/en/Hacking/libraryTutorial). +* **Important:** Please submit your PR using the [release_candidate branch](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/tree/release_candidate). That way, we can merge and test your PR quickly without changing the _master_ branch + +![Contributing.JPG](./img/Contributing.JPG) + +## Style guide + +Please read and follow the [Arduino API style guide](https://www.arduino.cc/en/Reference/APIStyleGuide). Also read and consider the [Arduino style guide](https://www.arduino.cc/en/Reference/StyleGuide). diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..84ae55b --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,18 @@ +### Subject of the issue +Describe your issue here. If you reference a datasheet please specify which one and in which section (ie, the protocol manual, section 5.1.2). Additionally, screenshots are easy to paste into github. + +### Your workbench +* What development board or microcontroller are you using? +* What version of hardware or breakout board are you using? +* How is the breakout board wired to your microcontroller? +* How is everything being powered? +* Are there any additional details that may help us help you? + +### Steps to reproduce +Tell us how to reproduce this issue. Please post stripped down example code demonstrating your issue. + +### Expected behavior +Tell us what should happen + +### Actual behavior +Tell us what happens instead diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..e64bd4e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,55 @@ +SparkFun License Information +============================ + +SparkFun uses two different licenses for our files — one for hardware and one for code. + +Hardware +--------- + +**SparkFun hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).** + +Note: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-sa/4.0/legalcode). + +You are free to: + +Share — copy and redistribute the material in any medium or format +Adapt — remix, transform, and build upon the material +for any purpose, even commercially. +The licensor cannot revoke these freedoms as long as you follow the license terms. +Under the following terms: + +Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. +ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. +No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. +Notices: + +You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. +No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. + + +Code +-------- + +**SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT).** + +The MIT License (MIT) + +Copyright (c) 2016 SparkFun Electronics + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..be05b7d --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ +SparkFun u-blox Arduino Library +=========================================================== + + + + + + + + + + + + + + + + +
SparkFun GPS-RTK2 - ZED-F9P (GPS-15136)SparkFun GPS-RTK - NEO-M8P-2 (GPS-15005)SparkFun ZOE-M8Q Breakout (GPS-15193)SparkFun SAM-M8Q Breakout (GPS-15210)SparkFun NEO-M9N Breakout (GPS-15733)
+ +u-blox makes some incredible GNSS receivers covering everything from low-cost, highly configurable modules such as the SAM-M8Q all the way up to the surveyor grade ZED-F9P with precision of the diameter of a dime. This library focuses on configuration and control of u-blox devices over I2C (called DDC by u-blox) and Serial. The UBX protocol is supported over both I2C and serial, and is a much easier and lighterweight interface to a GNSS module. Stop parsing NMEA data! And simply ask for the datums you need. + +This library can be installed via the Arduino Library manager. Search for **SparkFun u-blox GNSS**. + +Although not an integrated part of the library, you will find an example of how to communicate with the older series 6 and 7 modules in the [examples folder](./examples/Series_6_7). + +Max (400kHz) I2C Support +------------------- + +To achieve 400kHz I2C speed please be sure to remove all pull-ups on the I2C bus. Most, if not all, u-blox modules include pull ups on the I2C lines (sometimes called DDC in their manuals). Cut all I2C pull up jumpers and/or remove them from peripheral boards. Otherwise, various data glitches can occur. See issues [38](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues/38) and [40](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/issues/40) for more information. If possible, run the I2C bus at 100kHz. + +------------------- + +Want to help? Please do! We are always looking for ways to improve and build out features of this library. + +* We are always interested in adding SPI support with a checkUbloxSPI() function + +Thanks to: + +* [trycoon](https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/pull/7) for fixing the lack of I2C buffer length defines. +* [tve](https://github.com/tve) for building out serial additions and examples. +* [Redstoned](https://github.com/Redstoned) and [davidallenmann](https://github.com/davidallenmann) for adding PVT date and time. +* [wittend](https://forum.sparkfun.com/viewtopic.php?t=49874) for pointing out the RTCM print bug. +* Big thanks to [PaulZC](https://github.com/PaulZC) for implementing the combined key ValSet method, geofence functions, better saveConfig handling, as well as a bunch of small fixes. +* [RollieRowland](https://github.com/RollieRowland) for adding HPPOSLLH (High Precision Geodetic Position). +* [tedder](https://github.com/tedder) for moving iTOW to PVT instead of HPPOS and comment cleanup. +* [grexjmo](https://github.com/grexjmo) for pushing for a better NMEA sentence configuration method. +* [averywallis](https://github.com/averywallis) for adding good comments to the various constants. +* [blazczak](https://github.com/blazczak) and [geeksville](https://github.com/geeksville) for adding support for the series 6 and 7 modules. +* [bjorn@unsurv](https://github.com/unsurv) for adding powerOff and powerOffWithInterrupt. +* [dotMorten](https://github.com/dotMorten) for the MSGOUT keys, autoHPPOSLLH, autoDOP and upgrades to autoPVT. +* [markuckermann](https://github.com/markuckermann) for spotting the config layer gremlins +* [vid553](https://github.com/vid553) for the Zephyr port +* [balamuruganky](https://github.com/balamuruganky) for the NAV-PVT velocity parameters, getSpeedAccEst, getHeadingAccEst, getInvalidLlh, getHeadVeh, getMagDec and getMagAcc +* [nelarsen](https://github.com/nelarsen) for the buffer overrun improvements +* [mstranne](https://github.com/mstranne) and [shaneperera](https://github.com/shaneperera) for the pushRawData suggestion +* [rubienr](https://github.com/rubienr) for spotting the logical AND issues + +Need a Python version for Raspberry Pi? Checkout the [Qwiic Ublox GPS Py module](https://github.com/sparkfun/Qwiic_Ublox_Gps_Py). + +Need a library for the u-blox and Particle? Checkout the [Particle library](https://github.com/aseelye/SparkFun_Ublox_Particle_Library) fork. + +Contributing +-------------- + +If you would like to contribute to this library: please do, we truly appreciate it, but please follow [these guidelines](./CONTRIBUTING.md). Thanks! + +Repository Contents +------------------- + +* **/examples** - Example sketches for the library (.ino). Run these from the Arduino IDE. +* **/src** - Source files for the library (.cpp, .h). +* **[keywords.txt](./keywords.txt)** - Keywords from this library that will be highlighted in the Arduino IDE. +* **[library.properties](./library.properties)** - General library properties for the Arduino package manager. +* **[CONTRIBUTING.md](./CONTRIBUTING.md)** - Guidelines on how to contribute to this library. +* **[Theory.md](./Theory.md)** - provides detail on how data is processed by the library. + +Documentation +-------------- + +* **[Installing an Arduino Library Guide](https://learn.sparkfun.com/tutorials/installing-an-arduino-library)** - Basic information on how to install an Arduino library. + +Theory +-------------- + +If you would like to learn more about how this library works, including the big changes we made in version 2.0, please see **[Theory.md](./Theory.md)** for full details. + +Products That Use This Library +--------------------------------- +* [GPS-16481](https://www.sparkfun.com/products/16481) - SparkFun GPS-RTK-SMA Breakout - ZED-F9P (Qwiic) +* [GPS-15136](https://www.sparkfun.com/products/15136) - SparkFun GPS-RTK2 Board - ZED-F9P (Qwiic) +* [GPS-16344](https://www.sparkfun.com/products/16344) - SparkFun GPS-RTK Dead Reckoning Breakout - ZED-F9R (Qwiic) +* [GPS-15005](https://www.sparkfun.com/products/15005) - SparkFun GPS-RTK Board - NEO-M8P-2 (Qwiic) +* [GPS-15210](https://www.sparkfun.com/products/15210) - SparkFun GPS Breakout - Chip Antenna, SAM-M8Q (Qwiic) +* [GPS-15193](https://www.sparkfun.com/products/15193) - SparkFun GPS Breakout - Chip Antenna, ZOE-M8Q (Qwiic) +* [GPS-15733](https://www.sparkfun.com/products/15733) - SparkFun GPS Breakout - NEO-M9N, Chip Antenna (Qwiic) +* [GPS-15712](https://www.sparkfun.com/products/15712) - SparkFun GPS Breakout - NEO-M9N, U.FL (Qwiic) +* [GPS-16329](https://www.sparkfun.com/products/16329) - SparkFun GPS Dead Reckoning Breakout - NEO-M8U (Qwiic) +* [SPX-14980](https://www.sparkfun.com/products/14980) - SparkX GPS-RTK Black +* [SPX-15106](https://www.sparkfun.com/products/15106) - SparkX SAM-M8Q + +License Information +------------------- + +This product is _**open source**_! + +Various bits of the code have different licenses applied. Anything SparkFun wrote is beerware; if you see me (or any other SparkFun employee) at the local, and you've found our code helpful, please buy us a round! + +Please use, reuse, and modify these files as you see fit. Please maintain attribution to SparkFun Electronics and release anything derivative under the same license. + +Distributed as-is; no warranty is given. + +- Your friends at SparkFun. diff --git a/Theory.md b/Theory.md new file mode 100644 index 0000000..e463586 --- /dev/null +++ b/Theory.md @@ -0,0 +1,117 @@ +## How I2C (aka DDC) communication works with a u-blox module + +When the user calls one of the methods the library will poll the u-blox module for new data. + +* Wait for a minimum of 25 ms between polls (configured dynamically when update rate is set) +* Write 0xFD to module +* Read two bytes (0xFD and 0xFE) for bytes available +* If 0x7F or 0xFF then no bytes are available +* Otherwise, read number of bytes and process into NMEA, UBX, or RTCM frame. +* If checksum is valid, flag frame as complete. + +This library was originally written to use the I2C interface but Serial has been implemented as well. + +## How data is processed by this library + +In Version 1 of this library, we tried to minimize memory usage by being very careful about how much RAM we allocated to UBX packet storage and processing. We used only three buffers or containers to store the incoming data: **packetBuf** (packetBuffer); **packetCfg** (packetConfiguration); and **packetAck** (packetAcknowledge). Incoming packets were stored in **packetBuf** initially and then diverted into **packetAck** or **packetCfg** as necessary. Once data was received and validated, it would be copied out of **packetCfg** and into 'global' variables with names like ```gpsSecond``` or ```latitude```. We also introduced the concept of _Polling vs. Auto-Reporting_ where messages like PVT (Position, Velocity, Time) could be generated and parsed "automatically". This meant that functions like ```getLatitude``` could be non-blocking, returning the most recent data and requesting fresh data when necessary. But it also meant that _polled_ messages could be _overwritten_ (in **packetCfg**) by any _auto-reported_ messages. The library dealt with this successfully, but it was a headache. + +Version 1 had two main drawbacks. As time went on: +- the RAM use increased as we had to add new 'global' storage for each new data type +- the number of messages which needed "auto" processing through **packetCfg** became complex, requiring significant code changes each time a new "auto" message was added. (We started with NAV-PVT. Then came NAV-HPPOSLLH and NAV-DOP. Things got complicated when HNR-ATT, HNR-INS and HNR-PVT were added to the mix.) + +Version 2 of the library does things differently. Whilst of course trying to keep the library backward-compatible as much as possible, we have taken a fresh approach: +- We have added **packetAuto** which is used to temporarily buffer expected auto-reported messages and prevents data from being overwritten in **packetCfg**. + - The payload for **packetAuto** is allocated dynamically in RAM and deleted after use. + - If insufficient RAM is available, the code falls back to using **packetCfg** to buffer the data instead. +- The library no longer uses 'global' (permanently-allocated) storage for the GNSS data. Instead: + - Each message type has a **typedef struct** defined which matches the format of the UBX message. (_typedef structs_ are just definitions, they don't occupy memory.) You can find the definitions in [_**u-blox_structs.h**_](./src/u-blox_structs.h). + - The struct allows each data field (latitude, longitude, etc.) to be read simply and easily using dot notation. Flags etc. are supported by bit definitions in the struct. The field names are as defined in the u-blox interface description. + - Storage for that message is only _allocated_ in RAM if/when required. The allocation is done using _new_ via a pointer to the struct. +- _Any_ message can be "auto" if required, but can be polled too. +- An optional _callback_ can be associated with the arrival of each message type. A simple scheduler ```checkCallbacks``` triggers the callbacks once I2C/Serial data reception is complete. + - This means that your code no longer needs to wait for the arrival of a message, you are able to request (e.g.) PVT or HNR data and your callback is called once the data arrives. + - The callbacks are not re-entrant. + - The callback receives a _copy_ of the data, so data reception and processing can continue while the callback is executing. Data integrity is preserved. You can call ```checkUblox()``` from inside a callback if needed. +- Incoming data can be copied to a separate buffer to allow automatic writing to a file on SD card, which will be useful for (e.g.) RAWX logging. + - Data is stored in a RingBuffer, the size of which can be set by calling ```setFileBufferSize``` _before_ ```.begin```. + - The default buffer size is zero - to save memory. + - To simplify SD card writing, data can be copied from the RingBuffer to a user-defined linear buffer first using ```extractFileBufferData```. + - Data reception and processing can continue during the SD write. + - User-defined code does the actual writing of data from the linear buffer to the SD card. The u-blox GNSS library itself does not perform the writing and so is not tied to any particular SD library. + - The logged files can be played back and analyzed with (e.g.) u-center or RTKLIB. + +In terms of RAM, you may find that your total RAM use is lower using v2 compared to v1, but it does of course depend on how many message types are being processed. The downside to this is that it is difficult to know in advance how much RAM is required, since it is only allocated if/when required. If the processor runs out of RAM (i.e. the _new_ fails) then a debug error message is generated. + +## "Auto" messages + +In v2.0, the full list of messages which can be processed and logged automatically is: +- UBX-NAV-POSECEF (0x01 0x01): Position solution in ECEF +- UBX-NAV-STATUS (0x01 0x03): Receiver navigation status +- UBX-NAV-DOP (0x01 0x04): Dilution of precision +- UBX-NAV-ATT (0x01 0x05): Attitude solution (**only with ADR or UDR products**) +- UBX-NAV-PVT (0x01 0x07): Navigation position velocity time solution +- UBX-NAV-ODO (0x01 0x09): Odometer solution +- UBX-NAV-VELECEF (0x01 0x11): Velocity solution in ECEF +- UBX-NAV-VELNED (0x01 0x12): Velocity solution in NED frame +- UBX-NAV-HPPOSECEF (0x01 0x13): High precision position solution in ECEF +- UBX-NAV-HPPOSLLH (0x01 0x14): High precision geodetic position solution +- UBX-NAV-CLOCK (0x01 0x22): Clock solution +- UBX-NAV-SVIN (0x01 0x3B): Survey-in data (**only with High Precision GNSS products**) +- UBX-NAV-RELPOSNED (0x01 0x3C): Relative positioning information in NED frame (**only with High Precision GNSS products**) +- UBX-RXM-SFRBX (0x02 0x13): Broadcast navigation data subframe +- UBX-RXM-RAWX (0x02 0x15): Multi-GNSS raw measurement data (**only with ADR or High Precision GNSS or Time Sync products**) +- UBX-TIM-TM2 (0x0D 0x03): Time mark data +- UBX-ESF-ALG (0x10 0x14): IMU alignment information (**only with ADR or UDR products**) +- UBX-ESF-INS (0x10 0x15): Vehicle dynamics information (**only with ADR or UDR products**) +- UBX-ESF-MEAS (0x10 0x02): External sensor fusion measurements (**only with ADR or UDR products**) +- UBX-ESF-RAW (0x10 0x03): Raw sensor measurements (**only with ADR or UDR products**) +- UBX-ESF-STATUS (0x10 0x10): External sensor fusion status (**only with ADR or UDR products**) +- UBX-HNR-PVT (0x28 0x00): High rate output of PVT solution (**only with ADR or UDR products**) +- UBX-HNR-ATT (0x28 0x01): Attitude solution (**only with ADR or UDR products**) +- UBX-HNR-INS (0x28 0x02): Vehicle dynamics information (**only with ADR or UDR products**) + +Notes: +- UBX-NAV-POSLLH is not supported as UBX-NAV-PVT contains the same information +- UBX-NAV-TIMEUTC is not supported as UBX-NAV-PVT contains the same information + +## Migrating your code to v2.0 + +The biggest change in v2.0 is that data is now stored in a _struct_ which matches the u-blox interface description for that message. For example: +- In v1, the NAV PVT (Position Velocity Time) latitude and longitude were stored in 'global' _int32_t_ variables called ```latitude``` and ```longitude``` + - In v2.0, the data is now stored in UBX_NAV_PVT_t *packetUBXNAVPVT + - ```myGPS.latitude``` becomes ```myGPS.packetUBXNAVPVT->data.lat``` + - ```myGPS.longitude``` becomes ```myGPS.packetUBXNAVPVT->data.lon``` + - The helper functions ```myGPS.getLatitude()``` and ```myGPS.getLongitude()``` are still available and work in the same way. +- In v1, the ESF Sensor Fusion data for the Dead Reckoning modules was stored in 'global' variables ```imuMeas```, ```ubloxSen``` and ```vehAtt``` + - In v2.0, the data is now stored in: + - UBX_ESF_ALG_t *packetUBXESFALG contains the IMU alignment information (roll, pitch and yaw) + - UBX_ESF_INS_t *packetUBXESFINS contains the vehicle dynamics information (acceleration and angular rate) + - UBX_ESF_MEAS_t *packetUBXESFMEAS contains the sensor fusion measurements + - UBX_ESF_RAW_t *packetUBXESFRAW contains the raw sensor measurements + - UBX_ESF_STATUS_t *packetUBXESFSTATUS contains the sensor fusion status + - e.g. ```myGPS.imuMeas.fusionMode``` becomes ```myGPS.packetUBXESFSTATUS->data.fusionMode``` + - The helper functions ```getSensorFusionMeasurement```, ```getRawSensorMeasurement``` and ```getSensorFusionStatus``` can be used to extract the sensor data for an individual sensor + - "auto" data can be marked as stale by calling (e.g.) ```myGPS.flushESFALG()``` + - Please see the [**Dead_Reckoning/Example4_vehicleDynamics**](./examples/Dead_Reckoning/Example4_vehicleDynamics/Example4_vehicleDynamics.ino) example for more details +- In v1, the HNR (High Navigation Rate) data for the Dead Reckoning modules was stored in 'global' variables ```hnrAtt```, ```hnrVehDyn``` and ```hnrPVT``` + - In v2.0, e.g.: + - ```myGPS.hnrAtt.roll``` becomes ```myGPS.packetUBXHNRATT->data.roll``` + - ```myGPS.hnrVehDyn.xAccel``` becomes ```myGPS.packetUBXHNRINS->data.xAccel``` + - ```myGPS.hnrPVT.lat``` becomes ```myGPS.packetUBXHNRPVT->data.lat``` + - "auto" data can be marked as stale by calling (e.g.) ```myGPS.flushHNRATT()``` + - Please see the [**Dead_Reckoning/Example6_getAutoHNRData**](./examples/Dead_Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino) example for more details + +Other changes include: +- In v1, NAV_RELPOSNED relPosN, relPosE and relPosD were returned as (float)m. In v2.0 they are returned via packetUBXNAVRELPOSNED->data.relPosN (etc.) as (int32_t)cm. + - New helper functions (```getRelPosN```, ```getRelPosE``` and ```getRelPosD```) provide backward-compatibility + - Please see the [**ZED-F9P/Example5_RelativePositioningInformation**](./examples/ZED-F9P/Example5_RelativePositioningInformation/Example5_RelativePositioningInformation.ino) example for more details +- In v1, NAV_RELPOSNED accN, accE and accD were returned as (float)m. In v2.0 they are returned via packetUBXNAVRELPOSNED->data.accN (etc.) as (uint32_t)mm*0.1. + - New helper functions (```getRelPosAccN```, ```getRelPosAccE``` and ```getRelPosAccD```) provide backward-compatibility + - Please see the [**ZED-F9P/Example5_RelativePositioningInformation**](./examples/ZED-F9P/Example5_RelativePositioningInformation/Example5_RelativePositioningInformation.ino) example for more details +- getSurveyStatus now returns data via UBX_NAV_SVIN_t *packetUBXNAVSVIN + - ```myGPS.svin.active``` becomes ```myGPS.packetUBXNAVSVIN->data.active``` + - ```myGPS.svin.valid``` becomes ```myGPS.packetUBXNAVSVIN->data.valid``` + - ```myGPS.svin.observationTime``` becomes ```myGPS.packetUBXNAVSVIN->data.dur``` and is now uint32_t (not uint16_t) + - ```myGPS.svin.MeanAccuracy``` becomes ```myGPS.packetUBXNAVSVIN->data.meanAcc``` and is now uint32_t * 0.1mm (not float * m) + - New helper functions (```getSurveyInActive```, ```getSurveyInValid```, ```getSurveyInObservationTime``` and ```getSurveyInMeanAccuracy```) provide backward-compatibility + - Please see the [**ZED-F9P/Example3_StartRTCMBase**](./examples/ZED-F9P/Example3_StartRTCMBase/Example3_StartRTCMBase.ino) example for more details diff --git a/Utils/UBX_Integrity_Checker.py b/Utils/UBX_Integrity_Checker.py new file mode 100644 index 0000000..2fac318 --- /dev/null +++ b/Utils/UBX_Integrity_Checker.py @@ -0,0 +1,399 @@ +# Checks the integrity of u-blox binary files + +# Written by: Paul Clark +# Last update: August 26th 2020 + +# Reads a UBX file and checks the integrity of both UBX and NMEA data +# Will rewind and re-sync if an error is found + +# SparkFun code, firmware, and software is released under the MIT License (http://opensource.org/licenses/MIT) +# +# The MIT License (MIT) +# +# Copyright (c) 2020 SparkFun Electronics +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import sys +import os + +# Add byte to checksums sum1 and sum2 +def csum(byte, sum1, sum2): + sum1 = sum1 + byte + sum2 = sum2 + sum1 + sum1 = sum1 & 0xFF + sum2 = sum2 & 0xFF + return sum1,sum2 + +print('UBX Integrity Checker') +print() + +filename = '' + +if filename == '': + # Check if the bin filename was passed in argv + if len(sys.argv) > 1: filename = sys.argv[1] + +# Find first .ubx file in the current directory +firstfile = '' +for root, dirs, files in os.walk("."): + if len(files) > 0: + if root == ".": # Comment this line to check sub-directories too + for afile in files: + if afile[-4:] == '.ubx': + if firstfile == '': firstfile = os.path.join(root, afile) + +# Ask user for .bin filename offering firstfile as the default +if filename == '': filename = input('Enter the UBX filename (default: ' + firstfile + '): ') # Get the filename +if filename == '': filename = firstfile + +# Ask user if the data contains NMEA messages +response = input('Could this file contain any NMEA messages? (Y/n): ') # Get the response +if (response == '') or (response == 'Y') or (response == 'y'): + containsNMEA = True +else: + containsNMEA = False + +print() +print('Processing',filename) +print() +filesize = os.path.getsize(filename) # Record the file size + +# Try to open file for reading +try: + fi = open(filename,"rb") +except: + raise Exception('Invalid file!') + +processed = -1 # The nunber of bytes processed +messages = {} # The collected message types +longest = 0 # The length of the longest UBX message +keepGoing = True + +# Sync 'state machine' +looking_for_B5_dollar = 0 # Looking for either a UBX 0xB5 or an NMEA '$' +looking_for_62 = 1 # Looking for a UBX 0x62 header byte +looking_for_class = 2 # Looking for UBX class byte +looking_for_ID = 3 # Looking for UBX ID byte +looking_for_length_LSB = 4 # Looking for UBX length bytes +looking_for_length_MSB = 5 +processing_payload = 6 # Processing the payload. Keep going until length bytes have been processed +looking_for_checksum_A = 7 # Looking for UBX checksum bytes +looking_for_checksum_B = 8 +sync_lost = 9 # Go into this state if sync is lost (bad checksum etc.) +looking_for_asterix = 10 # Looking for NMEA '*' +looking_for_csum1 = 11 # Looking for NMEA checksum bytes +looking_for_csum2 = 12 +looking_for_term1 = 13 # Looking for NMEA terminating bytes (CR and LF) +looking_for_term2 = 14 + +ubx_nmea_state = sync_lost # Initialize the state machine + +# Storage for UBX messages +ubx_length = 0 +ubx_class = 0 +ubx_ID = 0 +ubx_checksum_A = 0 +ubx_checksum_B = 0 +ubx_expected_checksum_A = 0 +ubx_expected_checksum_B = 0 + +# Storage for NMEA messages +nmea_length = 0 +nmea_char_1 = 0 # e.g. G +nmea_char_2 = 0 # e.g. P +nmea_char_3 = 0 # e.g. G +nmea_char_4 = 0 # e.g. G +nmea_char_5 = 0 # e.g. A +nmea_csum = 0 +nmea_csum1 = 0 +nmea_csum2 = 0 +nmea_expected_csum1 = 0 +nmea_expected_csum2 = 0 + +max_nmea_len = 100 # Maximum length for an NMEA message: use this to detect if we have lost sync while receiving an NMEA message +sync_lost_at = -1 # Record where we lost sync +rewind_to = -1 # Keep a note of where we should rewind to if sync is lost +rewind_attempts = 0 # Keep a note of how many rewinds have been attempted +max_rewinds = 100 # Abort after this many rewinds +rewind_in_progress = False # Flag to indicate if a rewind is in progress +resyncs = 0 # Record the number of successful resyncs +resync_in_progress = False # Flag to indicate if a resync is in progress +message_start_byte = 0 # Record where the latest message started (for resync reporting) + +try: + while keepGoing: + + # Read one byte from the file + fileBytes = fi.read(1) + if (len(fileBytes) == 0): + print('ERROR: Read zero bytes. End of file?! Or zero file size?!') + raise Exception('End of file?! Or zero file size?!') + c = fileBytes[0] + + processed = processed + 1 # Keep a record of how many bytes have been read and processed + + # Process data bytes according to ubx_nmea_state + # For UBX messages: + # Sync Char 1: 0xB5 + # Sync Char 2: 0x62 + # Class byte + # ID byte + # Length: two bytes, little endian + # Payload: length bytes + # Checksum: two bytes + # For NMEA messages: + # Starts with a '$' + # The next five characters indicate the message type (stored in nmea_char_1 to nmea_char_5) + # Message fields are comma-separated + # Followed by an '*' + # Then a two character checksum (the logical exclusive-OR of all characters between the $ and the * as ASCII hex) + # Ends with CR LF + # Only allow a new file to be opened when a complete packet has been processed and ubx_nmea_state has returned to "looking_for_B5_dollar" + # Or when a data error is detected (sync_lost) + + # RXM_RAWX is class 0x02 ID 0x15 + # RXM_SFRBF is class 0x02 ID 0x13 + # TIM_TM2 is class 0x0d ID 0x03 + # NAV_POSLLH is class 0x01 ID 0x02 + # NAV_PVT is class 0x01 ID 0x07 + # NAV-STATUS is class 0x01 ID 0x03 + + if (ubx_nmea_state == looking_for_B5_dollar) or (ubx_nmea_state == sync_lost): + if (c == 0xB5): # Have we found Sync Char 1 (0xB5) if we were expecting one? + if (ubx_nmea_state == sync_lost): + print("UBX Sync Char 1 (0xB5) found at byte "+str(processed)+". Checking for Sync Char 2") + ubx_nmea_state = looking_for_62 # Now look for Sync Char 2 (0x62) + message_start_byte = processed # Record the message start byte for resync reporting + elif (c == 0x24) and (containsNMEA == True): # Have we found an NMEA '$' if we were expecting one? + if (ubx_nmea_state == sync_lost): + print("NMEA $ found at byte "+str(processed)+". Attempting to process the message") + ubx_nmea_state = looking_for_asterix # Now keep going until we receive an asterix + nmea_length = 0 # Reset nmea_length then use it to check for excessive message length + nmea_csum = 0 # Reset the nmea_csum. Update it as each character arrives + nmea_char_1 = 0x30 # Reset the first five NMEA chars to something invalid + nmea_char_2 = 0x30 + nmea_char_3 = 0x30 + nmea_char_4 = 0x30 + nmea_char_5 = 0x30 + message_start_byte = processed # Record the message start byte for resync reporting + else: + #print("Was expecting Sync Char 0xB5 or an NMEA $ but did not receive one!") + if (c == 0x24): + print("Warning: * found at byte "+str(processed)+"! Are you sure this file does not contain NMEA messages?") + sync_lost_at = processed + ubx_nmea_state = sync_lost + elif (ubx_nmea_state == looking_for_62): + if (c == 0x62): # Have we found Sync Char 2 (0x62) when we were expecting one? + ubx_expected_checksum_A = 0 # Reset the expected checksum + ubx_expected_checksum_B = 0 + ubx_nmea_state = looking_for_class # Now look for Class byte + else: + print("Panic!! Was expecting Sync Char 2 (0x62) but did not receive one!") + print("Sync lost at byte "+str(processed)+". Attemting to re-sync") + sync_lost_at = processed + resync_in_progress = True + ubx_nmea_state = sync_lost + elif (ubx_nmea_state == looking_for_class): + ubx_class = c + ubx_expected_checksum_A = ubx_expected_checksum_A + c # Update the expected checksum + ubx_expected_checksum_B = ubx_expected_checksum_B + ubx_expected_checksum_A + ubx_nmea_state = looking_for_ID # Now look for ID byte + elif (ubx_nmea_state == looking_for_ID): + ubx_ID = c + ubx_expected_checksum_A = ubx_expected_checksum_A + c # Update the expected checksum + ubx_expected_checksum_B = ubx_expected_checksum_B + ubx_expected_checksum_A + message_type = '0x%02X 0x%02X'%(ubx_class,ubx_ID) # Record the message type + ubx_nmea_state = looking_for_length_LSB # Now look for length LSB + elif (ubx_nmea_state == looking_for_length_LSB): + ubx_length = c # Store the length LSB + ubx_expected_checksum_A = ubx_expected_checksum_A + c # Update the expected checksum + ubx_expected_checksum_B = ubx_expected_checksum_B + ubx_expected_checksum_A + ubx_nmea_state = looking_for_length_MSB # Now look for length MSB + elif (ubx_nmea_state == looking_for_length_MSB): + ubx_length = ubx_length + (c * 256) # Add the length MSB + ubx_expected_checksum_A = ubx_expected_checksum_A + c # Update the expected checksum + ubx_expected_checksum_B = ubx_expected_checksum_B + ubx_expected_checksum_A + if (ubx_length > longest): # Update the longest UBX message length + longest = ubx_length + rewind_to = processed # If we lose sync due to dropped bytes then rewind to here + ubx_nmea_state = processing_payload # Now look for payload bytes (length: ubx_length) + elif (ubx_nmea_state == processing_payload): + ubx_length = ubx_length - 1 # Decrement length by one + ubx_expected_checksum_A = ubx_expected_checksum_A + c # Update the expected checksum + ubx_expected_checksum_B = ubx_expected_checksum_B + ubx_expected_checksum_A + if (ubx_length == 0): + ubx_expected_checksum_A = ubx_expected_checksum_A & 0xff # Limit checksums to 8-bits + ubx_expected_checksum_B = ubx_expected_checksum_B & 0xff + ubx_nmea_state = looking_for_checksum_A # If we have received length payload bytes, look for checksum bytes + elif (ubx_nmea_state == looking_for_checksum_A): + ubx_checksum_A = c + ubx_nmea_state = looking_for_checksum_B + elif (ubx_nmea_state == looking_for_checksum_B): + ubx_checksum_B = c + ubx_nmea_state = looking_for_B5_dollar # All bytes received so go back to looking for a new Sync Char 1 unless there is a checksum error + if ((ubx_expected_checksum_A != ubx_checksum_A) or (ubx_expected_checksum_B != ubx_checksum_B)): + print("Panic!! UBX checksum error!") + print("Sync lost at byte "+str(processed)+". Attemting to re-sync.") + sync_lost_at = processed + resync_in_progress = True + ubx_nmea_state = sync_lost + else: + # Valid UBX message was received. Check if we have seen this message type before + if message_type in messages: + messages[message_type] += 1 # if we have, increment its count + else: + messages[message_type] = 1 # if we have not, set its count to 1 + rewind_in_progress = False # Clear rewind_in_progress + rewind_to = -1 + if (resync_in_progress == True): # Check if we are resyncing + resync_in_progress = False # Clear the flag now that a valid message has been received + resyncs += 1 # Increment the number of successful resyncs + print("Sync successfully re-established at byte "+str(processed)+". The UBX message started at byte "+str(message_start_byte)) + print() + # NMEA messages + elif (ubx_nmea_state == looking_for_asterix): + nmea_length = nmea_length + 1 # Increase the message length count + if (nmea_length > max_nmea_len): # If the length is greater than max_nmea_len, something bad must have happened (sync_lost) + print("Panic!! Excessive NMEA message length!") + print("Sync lost at byte "+str(processed)+". Attemting to re-sync") + sync_lost_at = processed + resync_in_progress = True + ubx_nmea_state = sync_lost + continue + # If this is one of the first five characters, store it + if (nmea_length <= 5): + if (nmea_length == 1): + nmea_char_1 = c + rewind_to = processed # If we lose sync due to dropped bytes then rewind to here + elif (nmea_length == 2): + nmea_char_2 = c + elif (nmea_length == 3): + nmea_char_3 = c + elif (nmea_length == 4): + nmea_char_4 = c + else: # ubx_length == 5 + nmea_char_5 = c + message_type = chr(nmea_char_1) + chr(nmea_char_2) + chr(nmea_char_3) + chr(nmea_char_4) + chr(nmea_char_5) # Record the message type + # Now check if this is an '*' + if (c == 0x2A): + # Asterix received + # Don't exOR it into the checksum + # Instead calculate what the expected checksum should be (nmea_csum in ASCII hex) + nmea_expected_csum1 = ((nmea_csum & 0xf0) >> 4) + 0x30 # Convert MS nibble to ASCII hex + if (nmea_expected_csum1 >= 0x3A): # : follows 9 so add 7 to convert to A-F + nmea_expected_csum1 += 7 + nmea_expected_csum2 = (nmea_csum & 0x0f) + 0x30 # Convert LS nibble to ASCII hex + if (nmea_expected_csum2 >= 0x3A): # : follows 9 so add 7 to convert to A-F + nmea_expected_csum2 += 7 + # Next, look for the first csum character + ubx_nmea_state = looking_for_csum1 + continue # Don't include the * in the checksum + # Now update the checksum + # The checksum is the exclusive-OR of all characters between the $ and the * + nmea_csum = nmea_csum ^ c + elif (ubx_nmea_state == looking_for_csum1): + # Store the first NMEA checksum character + nmea_csum1 = c + ubx_nmea_state = looking_for_csum2 + elif (ubx_nmea_state == looking_for_csum2): + # Store the second NMEA checksum character + nmea_csum2 = c + # Now check if the checksum is correct + if ((nmea_csum1 != nmea_expected_csum1) or (nmea_csum2 != nmea_expected_csum2)): + # The checksum does not match so sync_lost + print("Panic!! NMEA checksum error!") + print("Sync lost at byte "+str(processed)+". Attemting to re-sync") + sync_lost_at = processed + resync_in_progress = True + ubx_nmea_state = sync_lost + else: + # Checksum was valid so wait for the terminators + ubx_nmea_state = looking_for_term1 + elif (ubx_nmea_state == looking_for_term1): + # Check if this is CR + if (c != 0x0D): + print("Panic!! NMEA CR not found!") + print("Sync lost at byte "+str(processed)+". Attemting to re-sync") + sync_lost_at = processed + resync_in_progress = True + ubx_nmea_state = sync_lost + else: + ubx_nmea_state = looking_for_term2 + elif (ubx_nmea_state == looking_for_term2): + # Check if this is LF + if (c != 0x0A): + print("Panic!! NMEA LF not found!") + print("Sync lost at byte "+str(processed)+". Attemting to re-sync") + sync_lost_at = processed + resync_in_progress = True + ubx_nmea_state = sync_lost + else: + # Valid NMEA message was received. Check if we have seen this message type before + if message_type in messages: + messages[message_type] += 1 # if we have, increment its count + else: + messages[message_type] = 1 # if we have not, set its count to 1 + # LF was received so go back to looking for B5 or a $ + ubx_nmea_state = looking_for_B5_dollar + rewind_in_progress = False # Clear rewind_in_progress + rewind_to = -1 + if (resync_in_progress == True): # Check if we are resyncing + resync_in_progress = False # Clear the flag now that a valid message has been received + resyncs += 1 # Increment the number of successful resyncs + print("Sync successfully re-established at byte "+str(processed)+". The NMEA message started at byte "+str(message_start_byte)) + print() + + # Check if the end of the file has been reached + if (processed >= filesize - 1): keepGoing = False + + # Check if we should attempt to rewind + # Don't rewind if we have not yet seen a valid message + # Don't rewind if a rewind is already in progress + if (ubx_nmea_state == sync_lost) and (len(messages) > 0) and (rewind_in_progress == False) and (rewind_to >= 0): + rewind_attempts += 1 # Increment the number of rewind attempts + if (rewind_attempts > max_rewinds): # Only rewind up to max_rewind times + print("Panic! Maximum rewind attempts reached! Aborting...") + keepGoing = False + else: + print("Sync has been lost. Currently processing byte "+str(processed)+". Rewinding to byte "+str(rewind_to)) + fi.seek(rewind_to) # Rewind the file + processed = rewind_to - 1 # Rewind processed too! (-1 is needed as processed is incremented at the start of the loop) + rewind_in_progress = True # Flag that a rewind is in progress + + +finally: + fi.close() # Close the file + + # Print the file statistics + print() + processed += 1 + print('Processed',processed,'bytes') + print('File size was',filesize) + if (processed != filesize): + print('FILE SIZE MISMATCH!!') + print('Longest UBX message was %i data bytes'%longest) + if len(messages) > 0: + print('Message types and totals were:') + for key in messages.keys(): + print('Message type:',key,' Total:',messages[key]) + if (resyncs > 0): + print('Number of successful resyncs:',resyncs) + print() + print('Bye!') diff --git a/examples/Callbacks/CallbackExample1_NAV_PVT/CallbackExample1_NAV_PVT.ino b/examples/Callbacks/CallbackExample1_NAV_PVT/CallbackExample1_NAV_PVT.ino new file mode 100644 index 0000000..44633df --- /dev/null +++ b/examples/Callbacks/CallbackExample1_NAV_PVT/CallbackExample1_NAV_PVT.ino @@ -0,0 +1,104 @@ +/* + Configuring the GNSS to automatically send position reports over I2C and display them using a callback + By: Paul Clark + SparkFun Electronics + Date: December 30th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the u-blox GNSS to send navigation reports automatically + and access the data via a callback. No more polling! + + 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 GPS 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 GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +// Callback: printPVTdata will be called when new NAV PVT data arrives +// See u-blox_structs.h for the full definition of UBX_NAV_PVT_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoPVTcallback +// / _____ This _must_ be UBX_NAV_PVT_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printPVTdata(UBX_NAV_PVT_data_t ubxDataStruct) +{ + Serial.println(); + + Serial.print(F("Time: ")); // Print the time + uint8_t hms = ubxDataStruct.hour; // Print the hours + if (hms < 10) Serial.print(F("0")); // Print a leading zero if required + Serial.print(hms); + Serial.print(F(":")); + hms = ubxDataStruct.min; // Print the minutes + if (hms < 10) Serial.print(F("0")); // Print a leading zero if required + Serial.print(hms); + Serial.print(F(":")); + hms = ubxDataStruct.sec; // Print the seconds + if (hms < 10) Serial.print(F("0")); // Print a leading zero if required + Serial.print(hms); + Serial.print(F(".")); + unsigned long millisecs = ubxDataStruct.iTOW % 1000; // Print the milliseconds + if (millisecs < 100) Serial.print(F("0")); // Print the trailing zeros correctly + if (millisecs < 10) Serial.print(F("0")); + Serial.print(millisecs); + + long latitude = ubxDataStruct.lat; // Print the latitude + Serial.print(F(" Lat: ")); + Serial.print(latitude); + + long longitude = ubxDataStruct.lon; // Print the longitude + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = ubxDataStruct.hMSL; // Print the height above mean sea level + Serial.print(F(" Height above MSL: ")); + Serial.print(altitude); + Serial.println(F(" (mm)")); +} + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + 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); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + myGPS.setNavigationFrequency(2); //Produce two solutions per second + + myGPS.setAutoPVTcallback(&printPVTdata); // Enable automatic NAV PVT messages with callback to printPVTdata +} + +void loop() +{ + myGPS.checkUblox(); // Check for the arrival of new data and process it. + myGPS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + Serial.print("."); + delay(50); +} diff --git a/examples/Callbacks/CallbackExample2_NAV_ODO/CallbackExample2_NAV_ODO.ino b/examples/Callbacks/CallbackExample2_NAV_ODO/CallbackExample2_NAV_ODO.ino new file mode 100644 index 0000000..9be4072 --- /dev/null +++ b/examples/Callbacks/CallbackExample2_NAV_ODO/CallbackExample2_NAV_ODO.ino @@ -0,0 +1,89 @@ +/* + Configuring the GNSS to automatically send odometer reports over I2C and display the data using a callback + By: Paul Clark + SparkFun Electronics + Date: December 30th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the u-blox GNSS to send odometer reports automatically + and display the data via a callback. No more polling! + + 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 GPS 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 GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +// Callback: printODOdata will be called when new NAV ODO data arrives +// See u-blox_structs.h for the full definition of UBX_NAV_ODO_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoNAVODOcallback +// / _____ This _must_ be UBX_NAV_ODO_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printODOdata(UBX_NAV_ODO_data_t ubxDataStruct) +{ + Serial.println(); + + Serial.print(F("TOW: ")); // Print the Time Of Week + unsigned long iTOW = ubxDataStruct.iTOW; // iTOW is in milliseconds + Serial.print(iTOW); + Serial.print(F(" (ms)")); + + Serial.print(F(" Distance: ")); + unsigned long distance = ubxDataStruct.distance; // Print the distance + Serial.print(distance); + Serial.print(F(" (m)")); + + Serial.print(F(" Total Distance: ")); + unsigned long totalDistance = ubxDataStruct.totalDistance; // Print the total distance + Serial.print(totalDistance); + Serial.println(F(" (m)")); +} + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + 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); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + myGPS.setNavigationFrequency(1); //Produce one solution per second + + //myGPS.resetOdometer(); //Uncomment this line to reset the odometer + + myGPS.setAutoNAVODOcallback(&printODOdata); // Enable automatic NAV ODO messages with callback to printODOdata +} + +void loop() +{ + myGPS.checkUblox(); // Check for the arrival of new data and process it. + myGPS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + Serial.print("."); + delay(50); +} diff --git a/examples/Callbacks/CallbackExample3_TIM_TM2/CallbackExample3_TIM_TM2.ino b/examples/Callbacks/CallbackExample3_TIM_TM2/CallbackExample3_TIM_TM2.ino new file mode 100644 index 0000000..4278a7e --- /dev/null +++ b/examples/Callbacks/CallbackExample3_TIM_TM2/CallbackExample3_TIM_TM2.ino @@ -0,0 +1,115 @@ +/* + Configuring the GNSS to automatically send TIM TM2 reports over I2C and display the data using a callback + By: Paul Clark + SparkFun Electronics + Date: December 30th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the u-blox GNSS to send TIM TM2 reports automatically + and display the data via a callback. No more polling! + + Connecting the PPS (Pulse Per Second) breakout pin to the INT (Interrupt) pin with a jumper wire + will cause a TIM TM2 message to be produced once per second. You can then study the timing of the + pulse edges with nanosecond resolution! + + Note: TIM TM2 can only capture the timing of one rising edge and one falling edge per + navigation solution. So with setNavigationFrequency set to 1Hz, we can only see the timing + of one rising and one falling edge per second. If the frequency of the signal on the INT pin + is higher than 1Hz, we will only be able to see the timing of the most recent edges. + However, the module can count the number of rising edges too, at rates faster than the navigation rate. + + TIM TM2 messages are only produced when a rising or falling edge is detected on the INT pin. + If you disconnect your PPS to INT jumper wire, the messages will stop. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + NEO-M9N: https://www.sparkfun.com/products/17285 + + Hardware Connections: + Plug a Qwiic cable into the GPS 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 GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +int dotsPrinted = 0; // Print dots in rows of 50 while waiting for a TIM TM2 message + +// Callback: printTIMTM2data will be called when new TIM TM2 data arrives +// See u-blox_structs.h for the full definition of UBX_TIM_TM2_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_TM2_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printTIMTM2data(UBX_TIM_TM2_data_t ubxDataStruct) +{ + Serial.println(); + + Serial.print(F("newFallingEdge: ")); // 1 if a new falling edge was detected + Serial.print(ubxDataStruct.flags.bits.newFallingEdge); + + Serial.print(F(" newRisingEdge: ")); // 1 if a new rising edge was detected + Serial.print(ubxDataStruct.flags.bits.newRisingEdge); + + Serial.print(F(" Rising Edge Counter: ")); // Rising edge counter + Serial.print(ubxDataStruct.count); + + Serial.print(F(" towMsR: ")); // Time Of Week of rising edge (ms) + Serial.print(ubxDataStruct.towMsR); + + Serial.print(F(" towSubMsR: ")); // Millisecond fraction of Time Of Week of rising edge in nanoseconds + Serial.print(ubxDataStruct.towSubMsR); + + Serial.print(F(" towMsF: ")); // Time Of Week of falling edge (ms) + Serial.print(ubxDataStruct.towMsF); + + Serial.print(F(" towSubMsF: ")); // Millisecond fraction of Time Of Week of falling edge in nanoseconds + Serial.println(ubxDataStruct.towSubMsF); + + dotsPrinted = 0; // Reset dotsPrinted +} + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + 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); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + myGPS.setNavigationFrequency(1); //Produce one solution per second + + myGPS.setAutoTIMTM2callback(&printTIMTM2data); // Enable automatic TIM TM2 messages with callback to printTIMTM2data +} + +void loop() +{ + myGPS.checkUblox(); // Check for the arrival of new data and process it. + myGPS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + Serial.print("."); + delay(50); + if (++dotsPrinted > 50) + { + Serial.println(); + dotsPrinted = 0; + } +} diff --git a/examples/Callbacks/CallbackExample4_HNR/CallbackExample4_HNR.ino b/examples/Callbacks/CallbackExample4_HNR/CallbackExample4_HNR.ino new file mode 100644 index 0000000..668686e --- /dev/null +++ b/examples/Callbacks/CallbackExample4_HNR/CallbackExample4_HNR.ino @@ -0,0 +1,117 @@ +/* + By: Paul Clark + SparkFun Electronics + Date: December, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example configures the High Navigation Rate on the NEO-M8U and then + reads and displays the attitude solution, vehicle dynamics information + and high rate position, velocity and time. + + This example uses callbacks to process the HNR data automatically. No more polling! + + Please make sure your NEO-M8U is running UDR firmware >= 1.31. Please update using u-center if necessary: + https://www.u-blox.com/en/product/neo-m8u-module#tab-documentation-resources + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +// Callback: printHNRATTdata will be called when new HNR ATT data arrives +// See u-blox_structs.h for the full definition of UBX_HNR_ATT_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoHNRATTcallback +// / _____ This _must_ be UBX_HNR_ATT_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printHNRATTdata(UBX_HNR_ATT_data_t ubxDataStruct) +{ + Serial.println(); + Serial.print(F("Roll: ")); // Print selected data + Serial.print((float)ubxDataStruct.roll / 100000.0, 2); // Convert roll to degrees + Serial.print(F(" Pitch: ")); + Serial.print((float)ubxDataStruct.pitch / 100000.0, 2); // Convert pitch to degrees + Serial.print(F(" Heading: ")); + Serial.println((float)ubxDataStruct.heading / 100000.0, 2); // Convert heading to degrees +} + +// Callback: printHNRINSdata will be called when new HNR INS data arrives +// See u-blox_structs.h for the full definition of UBX_HNR_INS_data_t +void printHNRINSdata(UBX_HNR_INS_data_t ubxDataStruct) +{ + Serial.print(F("xAccel: ")); // Print selected data + Serial.print(ubxDataStruct.xAccel); + Serial.print(F(" yAccel: ")); + Serial.print(ubxDataStruct.yAccel); + Serial.print(F(" zAccel: ")); + Serial.println(ubxDataStruct.zAccel); +} + +// Callback: printHNRPVTdata will be called when new HNR PVT data arrives +// See u-blox_structs.h for the full definition of UBX_HNR_PVT_data_t +void printHNRPVTdata(UBX_HNR_PVT_data_t ubxDataStruct) +{ + Serial.print(F("ns: ")); // Print selected data + Serial.print(ubxDataStruct.nano); + Serial.print(F(" Lat: ")); + Serial.print(ubxDataStruct.lat); + Serial.print(F(" Lon: ")); + Serial.println(ubxDataStruct.lon); +} + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages on Serial + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + if (myGPS.setHNRNavigationRate(10) == true) //Set the High Navigation Rate to 10Hz + Serial.println(F("setHNRNavigationRate was successful")); + else + Serial.println(F("setHNRNavigationRate was NOT successful")); + + if (myGPS.setAutoHNRATTcallback(&printHNRATTdata) == true) // Enable automatic HNR ATT messages with callback to printHNRATTdata + Serial.println(F("setAutoHNRATTcallback successful")); + + if (myGPS.setAutoHNRINScallback(&printHNRINSdata) == true) // Enable automatic HNR INS messages with callback to printHNRINSdata + Serial.println(F("setAutoHNRINScallback successful")); + + if (myGPS.setAutoHNRPVTcallback(&printHNRPVTdata) == true) // Enable automatic HNR PVT messages with callback to printHNRPVTdata + Serial.println(F("setAutoHNRPVTcallback successful")); +} + +void loop() +{ + myGPS.checkUblox(); // Check for the arrival of new data and process it. + myGPS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + Serial.print("."); + delay(25); +} diff --git a/examples/Callbacks/CallbackExample5_ESF/CallbackExample5_ESF.ino b/examples/Callbacks/CallbackExample5_ESF/CallbackExample5_ESF.ino new file mode 100644 index 0000000..1341ab5 --- /dev/null +++ b/examples/Callbacks/CallbackExample5_ESF/CallbackExample5_ESF.ino @@ -0,0 +1,179 @@ +/* + By: Paul Clark + SparkFun Electronics + Date: December, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example configures the External Sensor Fusion messages on the NEO-M8U and + uses callbacks to process and display the ESF data automatically. No more polling! + + Please make sure your NEO-M8U is running UDR firmware >= 1.31. Please update using u-center if necessary: + https://www.u-blox.com/en/product/neo-m8u-module#tab-documentation-resources + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +// Callback: printESFALGdata will be called when new ESF ALG data arrives +// See u-blox_structs.h for the full definition of UBX_ESF_ALG_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoESFALGcallback +// / _____ This _must_ be UBX_ESF_ALG_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printESFALGdata(UBX_ESF_ALG_data_t ubxDataStruct) +{ + Serial.println(); + + Serial.print(F("TOW: ")); // Print the Time Of Week + unsigned long iTOW = ubxDataStruct.iTOW; // iTOW is in milliseconds + Serial.print(iTOW); + Serial.print(F(" (ms)")); + + Serial.print(F(" Roll: ")); // Print selected data + Serial.print((float)ubxDataStruct.roll / 100.0, 2); // Convert roll to degrees + + Serial.print(F(" Pitch: ")); + Serial.print((float)ubxDataStruct.pitch / 100.0, 2); // Convert pitch to degrees + + Serial.print(F(" Yaw: ")); + Serial.print((float)ubxDataStruct.yaw / 100.0, 2); // Convert yaw to degrees + + Serial.println(F(" (Degrees)")); +} + +// Callback: printESFINSdata will be called when new ESF INS data arrives +// See u-blox_structs.h for the full definition of UBX_ESF_INS_data_t +void printESFINSdata(UBX_ESF_INS_data_t ubxDataStruct) +{ + Serial.print(F("xAccel: ")); // Print selected data + Serial.print(ubxDataStruct.xAccel); + + Serial.print(F(" yAccel: ")); + Serial.print(ubxDataStruct.yAccel); + + Serial.print(F(" zAccel: ")); + Serial.print(ubxDataStruct.zAccel); + + Serial.println(F(" (m/s^2)")); +} + +// Callback: printESFMEASdata will be called when new ESF MEAS data arrives +// See u-blox_structs.h for the full definition of UBX_ESF_MEAS_data_t +// and UBX_ESF_MEAS_sensorData_t +void printESFMEASdata(UBX_ESF_MEAS_data_t ubxDataStruct) +{ + Serial.println(); + + Serial.print(F("id: ")); // Print selected data + Serial.print(ubxDataStruct.id); + + Serial.print(F(" numMeas: ")); + Serial.println(ubxDataStruct.flags.bits.numMeas); + + for (uint8_t num = 0; num < ubxDataStruct.flags.bits.numMeas; num++) // For each sensor + { + Serial.print(F("Sensor ")); + Serial.print(num); + + UBX_ESF_MEAS_sensorData_t sensorData; + myGPS.getSensorFusionMeasurement(&sensorData, ubxDataStruct, num); // Extract the data for one sensor + + Serial.print(F(": Type: ")); + Serial.print(sensorData.data.bits.dataType); + Serial.print(F(" Data: ")); + Serial.println(sensorData.data.bits.dataField); + } +} + +// Callback: printESFSTATUSdata will be called when new ESF STATUS data arrives +// See u-blox_structs.h for the full definition of UBX_ESF_STATUS_data_t +// and UBX_ESF_STATUS_sensorStatus_t +void printESFSTATUSdata(UBX_ESF_STATUS_data_t ubxDataStruct) +{ + Serial.print(F("fusionMode: ")); // Print selected data + Serial.print(ubxDataStruct.fusionMode); + + Serial.print(F(" numSens: ")); + Serial.println(ubxDataStruct.numSens); + + for (uint8_t num = 0; num < ubxDataStruct.numSens; num++) // For each sensor + { + Serial.print(F("Sensor ")); + Serial.print(num); + + UBX_ESF_STATUS_sensorStatus_t sensorStatus; + myGPS.getSensorFusionStatus(&sensorStatus, ubxDataStruct, num); // Extract the data for one sensor + + Serial.print(F(": Type: ")); + Serial.print(sensorStatus.sensStatus1.bits.type); + Serial.print(F(" Used: ")); + Serial.print(sensorStatus.sensStatus1.bits.used); + Serial.print(F(" Ready: ")); + Serial.print(sensorStatus.sensStatus1.bits.ready); + Serial.print(F(" Calib Status: ")); + Serial.print(sensorStatus.sensStatus2.bits.calibStatus); + Serial.print(F(" Noisy: ")); + Serial.println(sensorStatus.faults.bits.noisyMeas); + } +} + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages on Serial + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("Ublox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + myGPS.setNavigationFrequency(1); //Produce one solution per second + myGPS.setHNRNavigationRate(1); //Set the High Navigation Rate to 1Hz + + myGPS.setI2CpollingWait(50); //Allow checkUblox to poll I2C data every 50ms to keep up with the ESF MEAS messages + + if (myGPS.setAutoESFALGcallback(&printESFALGdata) == true) // Enable automatic ESF ALG messages with callback to printESFALGdata + Serial.println(F("setAutoESFALGcallback successful")); + + if (myGPS.setAutoESFINScallback(&printESFINSdata) == true) // Enable automatic ESF INS messages with callback to printESFINSdata + Serial.println(F("setAutoESFINScallback successful")); + + if (myGPS.setAutoESFMEAScallback(&printESFMEASdata) == true) // Enable automatic ESF MEAS messages with callback to printESFMEASdata + Serial.println(F("setAutoESFMEAScallback successful")); + + if (myGPS.setAutoESFSTATUScallback(&printESFSTATUSdata) == true) // Enable automatic ESF STATUS messages with callback to printESFSTATUSdata + Serial.println(F("setAutoESFSTATUScallback successful")); +} + +void loop() +{ + myGPS.checkUblox(); // Check for the arrival of new data and process it. + myGPS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + Serial.print("."); + delay(25); +} diff --git a/examples/Data_Logging/DataLoggingExample1_NAV_PVT/DataLoggingExample1_NAV_PVT.ino b/examples/Data_Logging/DataLoggingExample1_NAV_PVT/DataLoggingExample1_NAV_PVT.ino new file mode 100644 index 0000000..0b656cf --- /dev/null +++ b/examples/Data_Logging/DataLoggingExample1_NAV_PVT/DataLoggingExample1_NAV_PVT.ino @@ -0,0 +1,230 @@ +/* + Configuring the GNSS to automatically send NAV PVT reports over I2C and log them to file on SD card + By: Paul Clark + SparkFun Electronics + Date: December 30th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the u-blox GNSS to send NAV PVT reports automatically + and log the data to SD card in UBX format. + + This code is intended to be run on the MicroMod Data Logging Carrier Board using the Artemis Processor + but can be adapted by changing the chip select pin and SPI definitions: + https://www.sparkfun.com/products/16829 + https://www.sparkfun.com/products/16401 + + Hardware Connections: + Please see: https://learn.sparkfun.com/tutorials/micromod-data-logging-carrier-board-hookup-guide + Insert the Artemis Processor into the MicroMod Data Logging Carrier Board and secure with the screw. + Connect your GNSS breakout to the Carrier Board using a Qwiic cable. + Connect an antenna to your GNSS board if required. + Insert a formatted micro-SD card into the socket on the Carrier Board. + Connect the Carrier Board to your computer using a USB-C cable. + Ensure you have the SparkFun Apollo3 boards installed: http://boardsmanager/All#SparkFun_Apollo3 + This code has been tested using version 1.2.1 of the Apollo3 boards on Arduino IDE 1.8.13. + Select "SparkFun Artemis MicroMod" as the board type. + Press upload to upload the code onto the Artemis. + Open the Serial Monitor at 115200 baud to see the output. + + To minimise I2C bus errors, it is a good idea to open the I2C pull-up split pad links on + both the MicroMod Data Logging Carrier Board and the u-blox module breakout. + + Data is logged in u-blox UBX format. Please see the u-blox protocol specification for more details. + You can replay and analyze the data using u-center: + https://www.u-blox.com/en/product/u-center + + 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 + +*/ + +#include +#include +#include //Needed for I2C to GNSS + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +File myFile; //File that all GNSS data is written to + +#define sdChipSelect CS //Primary SPI Chip Select is CS for the MicroMod Artemis Processor. Adjust for your processor if necessary. + +#define packetLength 100 // NAV PVT is 92 + 8 bytes in length (including the sync chars, class, id, length and checksum bytes) + +// Callback: printPVTdata will be called when new NAV PVT data arrives +// See u-blox_structs.h for the full definition of UBX_NAV_PVT_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoPVTcallback +// / _____ This _must_ be UBX_NAV_PVT_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printPVTdata(UBX_NAV_PVT_data_t ubxDataStruct) +{ + Serial.println(); + + Serial.print(F("Time: ")); // Print the time + uint8_t hms = ubxDataStruct.hour; // Print the hours + if (hms < 10) Serial.print(F("0")); // Print a leading zero if required + Serial.print(hms); + Serial.print(F(":")); + hms = ubxDataStruct.min; // Print the minutes + if (hms < 10) Serial.print(F("0")); // Print a leading zero if required + Serial.print(hms); + Serial.print(F(":")); + hms = ubxDataStruct.sec; // Print the seconds + if (hms < 10) Serial.print(F("0")); // Print a leading zero if required + Serial.print(hms); + Serial.print(F(".")); + unsigned long millisecs = ubxDataStruct.iTOW % 1000; // Print the milliseconds + if (millisecs < 100) Serial.print(F("0")); // Print the trailing zeros correctly + if (millisecs < 10) Serial.print(F("0")); + Serial.print(millisecs); + + long latitude = ubxDataStruct.lat; // Print the latitude + Serial.print(F(" Lat: ")); + Serial.print(latitude); + + long longitude = ubxDataStruct.lon; // Print the longitude + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = ubxDataStruct.hMSL; // Print the height above mean sea level + Serial.print(F(" Height above MSL: ")); + Serial.print(altitude); + Serial.println(F(" (mm)")); +} + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); // Start I2C communication with the GNSS + +#if defined(AM_PART_APOLLO3) + Wire.setPullups(0); // On the Artemis, we can disable the internal I2C pull-ups too to help reduce bus errors +#endif + + while (Serial.available()) // Make sure the Serial buffer is empty + { + Serial.read(); + } + + Serial.println(F("Press any key to start logging.")); + + while (!Serial.available()) // Wait for the user to press a key + { + ; // Do nothing + } + + delay(100); // Wait, just in case multiple characters were sent + + while (Serial.available()) // Empty the Serial buffer + { + Serial.read(); + } + + Serial.println("Initializing SD card..."); + + // See if the card is present and can be initialized: + if (!SD.begin(sdChipSelect)) + { + Serial.println("Card failed, or not present. Freezing..."); + // don't do anything more: + while (1); + } + Serial.println("SD card initialized."); + + // Create or open a file called "NAV_PVT.ubx" on the SD card. + // If the file already exists, the new data is appended to the end of the file. + myFile = SD.open("NAV_PVT.ubx", FILE_WRITE); + if(!myFile) + { + Serial.println(F("Failed to create UBX data file! Freezing...")); + while (1); + } + + //myGPS.enableDebugging(); // Uncomment this line to enable helpful GNSS debug messages on Serial + + // NAV PVT messages are 100 bytes long. + // In this example, the data will arrive no faster than one message per second. + // So, setting the file buffer size to 301 bytes should be more than adequate. + // I.e. room for three messages plus an empty tail byte. + myGPS.setFileBufferSize(301); // setFileBufferSize must be called _before_ .begin + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing...")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + // (This will also disable any "auto" messages that were enabled and saved by other examples and reduce the load on the I2C bus) + //myGPS.factoryDefault(); delay(5000); + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + myGPS.setNavigationFrequency(1); //Produce one navigation solution per second + + myGPS.setAutoPVTcallback(&printPVTdata); // Enable automatic NAV PVT messages with callback to printPVTdata + + myGPS.logNAVPVT(); // Enable NAV PVT data logging + + Serial.println(F("Press any key to stop logging.")); +} + +void loop() +{ + myGPS.checkUblox(); // Check for the arrival of new data and process it. + myGPS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + if (myGPS.fileBufferAvailable() >= packetLength) // Check to see if a new packetLength-byte NAV PVT message has been stored + { + uint8_t myBuffer[packetLength]; // Create our own buffer to hold the data while we write it to SD card + + myGPS.extractFileBufferData((uint8_t *)&myBuffer, packetLength); // Extract exactly packetLength bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, packetLength); // Write exactly packetLength bytes from myBuffer to the ubxDataFile on the SD card + + //printBuffer(myBuffer); // Uncomment this line to print the data as Hexadecimal bytes + } + + if (Serial.available()) // Check if the user wants to stop logging + { + myFile.close(); // Close the data file + Serial.println(F("Logging stopped. Freezing...")); + while(1); // Do nothing more + } + + Serial.print("."); + delay(50); +} + +// Print the buffer contents as Hexadecimal bytes +// You should see: +// SYNC CHAR 1: 0xB5 +// SYNC CHAR 2: 0x62 +// CLASS: 0x01 for NAV +// ID: 0x07 for PVT +// LENGTH: 2-bytes Little Endian (0x5C00 = 92 bytes for NAV PVT) +// PAYLOAD: LENGTH bytes +// CHECKSUM_A +// CHECKSUM_B +// Please see the u-blox protocol specification for more details +void printBuffer(uint8_t *ptr) +{ + for (int i = 0; i < packetLength; i++) + { + if (ptr[i] < 16) Serial.print("0"); // Print a leading zero if required + Serial.print(ptr[i], HEX); // Print the byte as Hexadecimal + Serial.print(" "); + } + Serial.println(); +} diff --git a/examples/Data_Logging/DataLoggingExample2_TIM_TM2/DataLoggingExample2_TIM_TM2.ino b/examples/Data_Logging/DataLoggingExample2_TIM_TM2/DataLoggingExample2_TIM_TM2.ino new file mode 100644 index 0000000..2bd8c02 --- /dev/null +++ b/examples/Data_Logging/DataLoggingExample2_TIM_TM2/DataLoggingExample2_TIM_TM2.ino @@ -0,0 +1,241 @@ +/* + Configuring the GNSS to automatically send TIM TM2 reports over I2C and log them to file on SD card + By: Paul Clark + SparkFun Electronics + Date: December 30th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the u-blox GNSS to send TIM TM2 reports automatically + and log the data to SD card in UBX format. + + This code is intended to be run on the MicroMod Data Logging Carrier Board using the Artemis Processor + but can be adapted by changing the chip select pin and SPI definitions: + https://www.sparkfun.com/products/16829 + https://www.sparkfun.com/products/16401 + + Hardware Connections: + Please see: https://learn.sparkfun.com/tutorials/micromod-data-logging-carrier-board-hookup-guide + Insert the Artemis Processor into the MicroMod Data Logging Carrier Board and secure with the screw. + Connect your GNSS breakout to the Carrier Board using a Qwiic cable. + Connect an antenna to your GNSS board if required. + Insert a formatted micro-SD card into the socket on the Carrier Board. + Connect the Carrier Board to your computer using a USB-C cable. + Ensure you have the SparkFun Apollo3 boards installed: http://boardsmanager/All#SparkFun_Apollo3 + This code has been tested using version 1.2.1 of the Apollo3 boards on Arduino IDE 1.8.13. + Select "SparkFun Artemis MicroMod" as the board type. + Press upload to upload the code onto the Artemis. + Open the Serial Monitor at 115200 baud to see the output. + + To minimise I2C bus errors, it is a good idea to open the I2C pull-up split pad links on + both the MicroMod Data Logging Carrier Board and the u-blox module breakout. + + Connecting the PPS (Pulse Per Second) breakout pin to the INT (Interrupt) pin with a jumper wire + will cause a TIM TM2 message to be produced once per second. You can then study the timing of the + pulse edges with nanosecond resolution! + + Note: TIM TM2 can only capture the timing of one rising edge and one falling edge per + navigation solution. So with setNavigationFrequency set to 1Hz, we can only see the timing + of one rising and one falling edge per second. If the frequency of the signal on the INT pin + is higher than 1Hz, we will only be able to see the timing of the most recent edges. + However, the module can count the number of rising edges too, at rates faster than the navigation rate. + + TIM TM2 messages are only produced when a rising or falling edge is detected on the INT pin. + If you disconnect your PPS to INT jumper wire, the messages will stop. + + Data is logged in u-blox UBX format. Please see the u-blox protocol specification for more details. + You can replay and analyze the data using u-center: + https://www.u-blox.com/en/product/u-center + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + NEO-M9N: https://www.sparkfun.com/products/17285 + +*/ + +#include +#include +#include //Needed for I2C to GNSS + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +File myFile; //File that all GNSS data is written to + +#define sdChipSelect CS //Primary SPI Chip Select is CS for the MicroMod Artemis Processor. Adjust for your processor if necessary. + +#define packetLength 36 // TIM TM2 is 28 + 8 bytes in length (including the sync chars, class, id, length and checksum bytes) + +int dotsPrinted = 0; // Print dots in rows of 50 while waiting for a TIM TM2 message + +// Callback: printTIMTM2data will be called when new TIM TM2 data arrives +// See u-blox_structs.h for the full definition of UBX_TIM_TM2_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_TM2_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printTIMTM2data(UBX_TIM_TM2_data_t ubxDataStruct) +{ + Serial.println(); + + Serial.print(F("newFallingEdge: ")); // 1 if a new falling edge was detected + Serial.print(ubxDataStruct.flags.bits.newFallingEdge); + + Serial.print(F(" newRisingEdge: ")); // 1 if a new rising edge was detected + Serial.print(ubxDataStruct.flags.bits.newRisingEdge); + + Serial.print(F(" Rising Edge Counter: ")); // Rising edge counter + Serial.print(ubxDataStruct.count); + + Serial.print(F(" towMsR: ")); // Time Of Week of rising edge (ms) + Serial.print(ubxDataStruct.towMsR); + + Serial.print(F(" towSubMsR: ")); // Millisecond fraction of Time Of Week of rising edge in nanoseconds + Serial.print(ubxDataStruct.towSubMsR); + + Serial.print(F(" towMsF: ")); // Time Of Week of falling edge (ms) + Serial.print(ubxDataStruct.towMsF); + + Serial.print(F(" towSubMsF: ")); // Millisecond fraction of Time Of Week of falling edge in nanoseconds + Serial.println(ubxDataStruct.towSubMsF); + + dotsPrinted = 0; // Reset dotsPrinted +} + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); // Start I2C communication with the GNSS + +#if defined(AM_PART_APOLLO3) + Wire.setPullups(0); // On the Artemis, we can disable the internal I2C pull-ups too to help reduce bus errors +#endif + + while (Serial.available()) // Make sure the Serial buffer is empty + { + Serial.read(); + } + + Serial.println(F("Press any key to start logging.")); + + while (!Serial.available()) // Wait for the user to press a key + { + ; // Do nothing + } + + delay(100); // Wait, just in case multiple characters were sent + + while (Serial.available()) // Empty the Serial buffer + { + Serial.read(); + } + + Serial.println("Initializing SD card..."); + + // See if the card is present and can be initialized: + if (!SD.begin(sdChipSelect)) + { + Serial.println("Card failed, or not present. Freezing..."); + // don't do anything more: + while (1); + } + Serial.println("SD card initialized."); + + // Create or open a file called "TIM_TM2.ubx" on the SD card. + // If the file already exists, the new data is appended to the end of the file. + myFile = SD.open("TIM_TM2.ubx", FILE_WRITE); + if(!myFile) + { + Serial.println(F("Failed to create UBX data file! Freezing...")); + while (1); + } + + //myGPS.enableDebugging(); // Uncomment this line to enable helpful GNSS debug messages on Serial + + // TIM TM2 messages are 36 bytes long. + // In this example, the data will arrive no faster than one message per second. + // So, setting the file buffer size to 109 bytes should be more than adequate. + // I.e. room for three messages plus an empty tail byte. + myGPS.setFileBufferSize(109); // setFileBufferSize must be called _before_ .begin + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing...")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + // (This will also disable any "auto" messages that were enabled and saved by other examples and reduce the load on the I2C bus) + //myGPS.factoryDefault(); delay(5000); + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + myGPS.setNavigationFrequency(1); //Produce one navigation solution per second + + myGPS.setAutoTIMTM2callback(&printTIMTM2data); // Enable automatic TIM TM2 messages with callback to printTIMTM2data + + myGPS.logTIMTM2(); // Enable TIM TM2 data logging + + Serial.println(F("Press any key to stop logging.")); +} + +void loop() +{ + myGPS.checkUblox(); // Check for the arrival of new data and process it. + myGPS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + if (myGPS.fileBufferAvailable() >= packetLength) // Check to see if a new packetLength-byte TIM TM2 message has been stored + { + uint8_t myBuffer[packetLength]; // Create our own buffer to hold the data while we write it to SD card + + myGPS.extractFileBufferData((uint8_t *)&myBuffer, packetLength); // Extract exactly packetLength bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, packetLength); // Write exactly packetLength bytes from myBuffer to the ubxDataFile on the SD card + + //printBuffer(myBuffer); // Uncomment this line to print the data + } + + if (Serial.available()) // Check if the user wants to stop logging + { + myFile.close(); // Close the data file + Serial.println(F("Logging stopped. Freezing...")); + while(1); // Do nothing more + } + + Serial.print("."); // Print dots in rows of 50 + delay(50); + if (++dotsPrinted > 50) + { + Serial.println(); + dotsPrinted = 0; + } +} + +// Print the buffer contents as Hexadecimal +// You should see: +// SYNC CHAR 1: 0xB5 +// SYNC CHAR 2: 0x62 +// CLASS: 0x0D for TIM +// ID: 0x03 for TM2 +// LENGTH: 2-bytes Little Endian (0x1C00 = 28 bytes for TIM TM2) +// PAYLOAD: LENGTH bytes +// CHECKSUM_A +// CHECKSUM_B +// Please see the u-blox protocol specification for more details +void printBuffer(uint8_t *ptr) +{ + for (int i = 0; i < packetLength; i++) + { + if (ptr[i] < 16) Serial.print("0"); // Print a leading zero if required + Serial.print(ptr[i], HEX); // Print the byte as Hexadecimal + Serial.print(" "); + } + Serial.println(); +} diff --git a/examples/Data_Logging/DataLoggingExample3_RXM_SFRBX_and_RAWX/DataLoggingExample3_RXM_SFRBX_and_RAWX.ino b/examples/Data_Logging/DataLoggingExample3_RXM_SFRBX_and_RAWX/DataLoggingExample3_RXM_SFRBX_and_RAWX.ino new file mode 100644 index 0000000..4c8dcf3 --- /dev/null +++ b/examples/Data_Logging/DataLoggingExample3_RXM_SFRBX_and_RAWX/DataLoggingExample3_RXM_SFRBX_and_RAWX.ino @@ -0,0 +1,273 @@ +/* + Configuring the GNSS to automatically send RXM SFRBX and RAWX reports over I2C and log them to file on SD card + By: Paul Clark + SparkFun Electronics + Date: December 30th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the u-blox GNSS to send RXM SFRBX and RAWX reports automatically + and log the data to SD card in UBX format. + + ** Please note: this example will only work on u-blox ADR or High Precision GNSS or Time Sync products ** + + ** Please note: this example will only work on processors like the Artemis which have plenty of RAM available ** + + Data is logged in u-blox UBX format. Please see the u-blox protocol specification for more details. + You can replay and analyze the data using u-center: + https://www.u-blox.com/en/product/u-center + Or you can use (e.g.) RTKLIB to analyze the data and extract your precise location or produce + Post-Processed Kinematic data: + https://rtklibexplorer.wordpress.com/ + http://rtkexplorer.com/downloads/rtklib-code/ + + This code is intended to be run on the MicroMod Data Logging Carrier Board using the Artemis Processor + but can be adapted by changing the chip select pin and SPI definitions: + https://www.sparkfun.com/products/16829 + https://www.sparkfun.com/products/16401 + + Hardware Connections: + Please see: https://learn.sparkfun.com/tutorials/micromod-data-logging-carrier-board-hookup-guide + Insert the Artemis Processor into the MicroMod Data Logging Carrier Board and secure with the screw. + Connect your GNSS breakout to the Carrier Board using a Qwiic cable. + Connect an antenna to your GNSS board if required. + Insert a formatted micro-SD card into the socket on the Carrier Board. + Connect the Carrier Board to your computer using a USB-C cable. + Ensure you have the SparkFun Apollo3 boards installed: http://boardsmanager/All#SparkFun_Apollo3 + This code has been tested using version 1.2.1 of the Apollo3 boards on Arduino IDE 1.8.13. + Select "SparkFun Artemis MicroMod" as the board type. + Press upload to upload the code onto the Artemis. + Open the Serial Monitor at 115200 baud to see the output. + + To minimise I2C bus errors, it is a good idea to open the I2C pull-up split pad links on + both the MicroMod Data Logging Carrier Board and the u-blox module breakout. + + 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 +*/ + +#include +#include +#include //Needed for I2C to GNSS + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +File myFile; //File that all GNSS data is written to + +#define sdChipSelect CS //Primary SPI Chip Select is CS for the MicroMod Artemis Processor. Adjust for your processor if necessary. + +#define sdWriteSize 512 // Write data to the SD card in blocks of 512 bytes +#define fileBufferSize 16384 // Allocate 16KBytes of RAM for UBX message storage + +unsigned long lastPrint; // Record when the last Serial print took place + +// Note: we'll keep a count of how many SFRBX and RAWX messages arrive - but the count will not be completely accurate. +// If two or more SFRBX messages arrive together as a group and are processed by one call to checkUblox, the count will +// only increase by one. + +int numSFRBX = 0; // Keep count of how many SFRBX message groups have been received (see note above) +int numRAWX = 0; // Keep count of how many RAWX message groups have been received (see note above) + +// Callback: newSFRBX will be called when new RXM SFRBX data arrives +// See u-blox_structs.h for the full definition of UBX_RXMSFRBX_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoRXMSFRBXcallback +// / _____ This _must_ be UBX_RXM_SFRBX_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void newSFRBX(UBX_RXM_SFRBX_data_t ubxDataStruct) +{ + numSFRBX++; // Increment the count +} + +// Callback: newRAWX will be called when new RXM RAWX data arrives +// See u-blox_structs.h for the full definition of UBX_RXMRAWX_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoRXMRAWXcallback +// / _____ This _must_ be UBX_RXM_RAWX_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void newRAWX(UBX_RXM_RAWX_data_t ubxDataStruct) +{ + numRAWX++; // Increment the count +} + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + pinMode(LED_BUILTIN, OUTPUT); // Flash LED_BUILTIN each time we write to the SD card + digitalWrite(LED_BUILTIN, LOW); + + Wire.begin(); // Start I2C communication + +#if defined(AM_PART_APOLLO3) + Wire.setPullups(0); // On the Artemis, we can disable the internal I2C pull-ups too to help reduce bus errors +#endif + + while (Serial.available()) // Make sure the Serial buffer is empty + { + Serial.read(); + } + + Serial.println(F("Press any key to start logging.")); + + while (!Serial.available()) // Wait for the user to press a key + { + ; // Do nothing + } + + delay(100); // Wait, just in case multiple characters were sent + + while (Serial.available()) // Empty the Serial buffer + { + Serial.read(); + } + + Serial.println("Initializing SD card..."); + + // See if the card is present and can be initialized: + if (!SD.begin(sdChipSelect)) + { + Serial.println("Card failed, or not present. Freezing..."); + // don't do anything more: + while (1); + } + Serial.println("SD card initialized."); + + // Create or open a file called "RXM_RAWX.ubx" on the SD card. + // If the file already exists, the new data is appended to the end of the file. + myFile = SD.open("RXM_RAWX.ubx", FILE_WRITE); + if(!myFile) + { + Serial.println(F("Failed to create UBX data file! Freezing...")); + while (1); + } + + //myGPS.enableDebugging(); // Uncomment this line to enable lots of helpful GNSS debug messages on Serial + //myGPS.enableDebugging(Serial, true); // Or, uncomment this line to enable only the important GNSS debug messages on Serial + + myGPS.disableUBX7Fcheck(); // RAWX data can legitimately contain 0x7F, so we need to disable the "7F" check in checkUbloxI2C + + // RAWX messages can be over 2KBytes in size, so we need to make sure we allocate enough RAM to hold all the data. + // SD cards can occasionally 'hiccup' and a write takes much longer than usual. The buffer needs to be big enough + // to hold the backlog of data if/when this happens. + // getMaxFileBufferAvail will tell us the maximum number of bytes which the file buffer has contained. + myGPS.setFileBufferSize(fileBufferSize); // setFileBufferSize must be called _before_ .begin + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing...")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + // (This will also disable any "auto" messages that were enabled and saved by other examples and reduce the load on the I2C bus) + //myGPS.factoryDefault(); delay(5000); + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + myGPS.setNavigationFrequency(1); //Produce one navigation solution per second (that's plenty for Precise Point Positioning) + + myGPS.setAutoRXMSFRBXcallback(&newSFRBX); // Enable automatic RXM SFRBX messages with callback to newSFRBX + + myGPS.logRXMSFRBX(); // Enable RXM SFRBX data logging + + myGPS.setAutoRXMRAWXcallback(&newRAWX); // Enable automatic RXM RAWX messages with callback to newRAWX + + myGPS.logRXMRAWX(); // Enable RXM RAWX data logging + + Serial.println(F("Press any key to stop logging.")); + + lastPrint = millis(); // Initialize lastPrint +} + +void loop() +{ + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + myGPS.checkUblox(); // Check for the arrival of new data and process it. + myGPS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + while (myGPS.fileBufferAvailable() >= sdWriteSize) // Check to see if we have at least sdWriteSize waiting in the buffer + { + digitalWrite(LED_BUILTIN, HIGH); // Flash LED_BUILTIN each time we write to the SD card + + uint8_t myBuffer[sdWriteSize]; // Create our own buffer to hold the data while we write it to SD card + + myGPS.extractFileBufferData((uint8_t *)&myBuffer, sdWriteSize); // Extract exactly sdWriteSize bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, sdWriteSize); // Write exactly sdWriteSize bytes from myBuffer to the ubxDataFile on the SD card + + // In case the SD writing is slow or there is a lot of data to write, keep checking for the arrival of new data + myGPS.checkUblox(); // Check for the arrival of new data and process it. + myGPS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + digitalWrite(LED_BUILTIN, LOW); // Turn LED_BUILTIN off again + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if (millis() > (lastPrint + 1000)) // Print the message count once per second + { + Serial.print(F("Number of message groups received: SFRBX: ")); // Print how many message groups have been received (see note above) + Serial.print(numSFRBX); + Serial.print(F(" RAWX: ")); + Serial.println(numRAWX); + + uint16_t maxBufferBytes = myGPS.getMaxFileBufferAvail(); // Get how full the file buffer has been (not how full it is now) + + //Serial.print(F("The maximum number of bytes which the file buffer has contained is: ")); // It is a fun thing to watch how full the buffer gets + //Serial.println(maxBufferBytes); + + if (maxBufferBytes > ((fileBufferSize / 5) * 4)) // Warn the user if fileBufferSize was more than 80% full + { + Serial.println(F("Warning: the file buffer has been over 80% full. Some data may have been lost.")); + } + + lastPrint = millis(); // Update lastPrint + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if (Serial.available()) // Check if the user wants to stop logging + { + uint16_t remainingBytes = myGPS.fileBufferAvailable(); // Check if there are any bytes remaining in the file buffer + + while (remainingBytes > 0) // While there is still data in the file buffer + { + digitalWrite(LED_BUILTIN, HIGH); // Flash LED_BUILTIN while we write to the SD card + + uint8_t myBuffer[sdWriteSize]; // Create our own buffer to hold the data while we write it to SD card + + uint16_t bytesToWrite = remainingBytes; // Write the remaining bytes to SD card sdWriteSize bytes at a time + if (bytesToWrite > sdWriteSize) + { + bytesToWrite = sdWriteSize; + } + + myGPS.extractFileBufferData((uint8_t *)&myBuffer, bytesToWrite); // Extract bytesToWrite bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, bytesToWrite); // Write bytesToWrite bytes from myBuffer to the ubxDataFile on the SD card + + remainingBytes -= bytesToWrite; // Decrement remainingBytes + } + + digitalWrite(LED_BUILTIN, LOW); // Turn LED_BUILTIN off + + myFile.close(); // Close the data file + + Serial.println(F("Logging stopped. Freezing...")); + while(1); // Do nothing more + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +} diff --git a/examples/Data_Logging/DataLoggingExample4_RXM_without_Callbacks/DataLoggingExample4_RXM_without_Callbacks.ino b/examples/Data_Logging/DataLoggingExample4_RXM_without_Callbacks/DataLoggingExample4_RXM_without_Callbacks.ino new file mode 100644 index 0000000..477915e --- /dev/null +++ b/examples/Data_Logging/DataLoggingExample4_RXM_without_Callbacks/DataLoggingExample4_RXM_without_Callbacks.ino @@ -0,0 +1,247 @@ +/* + Configuring the GNSS to automatically send RXM SFRBX and RAWX reports over I2C and log them to file on SD card + ** without using callbacks ** + By: Paul Clark + SparkFun Electronics + Date: December 30th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the u-blox GNSS to send RXM SFRBX and RAWX reports automatically + and log the data to SD card in UBX format ** without using callbacks ** + + ** Please note: this example will only work on u-blox ADR or High Precision GNSS or Time Sync products ** + + ** Please note: this example will only work on processors like the Artemis which have plenty of RAM available ** + + Data is logged in u-blox UBX format. Please see the u-blox protocol specification for more details. + You can replay and analyze the data using u-center: + https://www.u-blox.com/en/product/u-center + Or you can use (e.g.) RTKLIB to analyze the data and extract your precise location or produce + Post-Processed Kinematic data: + https://rtklibexplorer.wordpress.com/ + http://rtkexplorer.com/downloads/rtklib-code/ + + This code is intended to be run on the MicroMod Data Logging Carrier Board using the Artemis Processor + but can be adapted by changing the chip select pin and SPI definitions: + https://www.sparkfun.com/products/16829 + https://www.sparkfun.com/products/16401 + + Hardware Connections: + Please see: https://learn.sparkfun.com/tutorials/micromod-data-logging-carrier-board-hookup-guide + Insert the Artemis Processor into the MicroMod Data Logging Carrier Board and secure with the screw. + Connect your GNSS breakout to the Carrier Board using a Qwiic cable. + Connect an antenna to your GNSS board if required. + Insert a formatted micro-SD card into the socket on the Carrier Board. + Connect the Carrier Board to your computer using a USB-C cable. + Ensure you have the SparkFun Apollo3 boards installed: http://boardsmanager/All#SparkFun_Apollo3 + This code has been tested using version 1.2.1 of the Apollo3 boards on Arduino IDE 1.8.13. + Select "SparkFun Artemis MicroMod" as the board type. + Press upload to upload the code onto the Artemis. + Open the Serial Monitor at 115200 baud to see the output. + + To minimise I2C bus errors, it is a good idea to open the I2C pull-up split pad links on + both the MicroMod Data Logging Carrier Board and the u-blox module breakout. + + 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 +*/ + +#include +#include +#include //Needed for I2C to GNSS + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +File myFile; //File that all GNSS data is written to + +#define sdChipSelect CS //Primary SPI Chip Select is CS for the MicroMod Artemis Processor. Adjust for your processor if necessary. + +#define sdWriteSize 512 // Write data to the SD card in blocks of 512 bytes +#define fileBufferSize 16384 // Allocate 16KBytes of RAM for UBX message storage + +unsigned long lastPrint; // Record when the last Serial print took place +unsigned long bytesWritten = 0; // Record how many bytes have been written to SD card + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + pinMode(LED_BUILTIN, OUTPUT); // Flash LED_BUILTIN each time we write to the SD card + digitalWrite(LED_BUILTIN, LOW); + + Wire.begin(); // Start I2C communication + +#if defined(AM_PART_APOLLO3) + Wire.setPullups(0); // On the Artemis, we can disable the internal I2C pull-ups too to help reduce bus errors +#endif + + while (Serial.available()) // Make sure the Serial buffer is empty + { + Serial.read(); + } + + Serial.println(F("Press any key to start logging.")); + + while (!Serial.available()) // Wait for the user to press a key + { + ; // Do nothing + } + + delay(100); // Wait, just in case multiple characters were sent + + while (Serial.available()) // Empty the Serial buffer + { + Serial.read(); + } + + Serial.println("Initializing SD card..."); + + // See if the card is present and can be initialized: + if (!SD.begin(sdChipSelect)) + { + Serial.println("Card failed, or not present. Freezing..."); + // don't do anything more: + while (1); + } + Serial.println("SD card initialized."); + + // Create or open a file called "RXM_RAWX.ubx" on the SD card. + // If the file already exists, the new data is appended to the end of the file. + myFile = SD.open("RXM_RAWX.ubx", FILE_WRITE); + if(!myFile) + { + Serial.println(F("Failed to create UBX data file! Freezing...")); + while (1); + } + + //myGPS.enableDebugging(); // Uncomment this line to enable lots of helpful GNSS debug messages on Serial + //myGPS.enableDebugging(Serial, true); // Or, uncomment this line to enable only the important GNSS debug messages on Serial + + myGPS.disableUBX7Fcheck(); // RAWX data can legitimately contain 0x7F, so we need to disable the "7F" check in checkUbloxI2C + + // RAWX messages can be over 2KBytes in size, so we need to make sure we allocate enough RAM to hold all the data. + // SD cards can occasionally 'hiccup' and a write takes much longer than usual. The buffer needs to be big enough + // to hold the backlog of data if/when this happens. + // getMaxFileBufferAvail will tell us the maximum number of bytes which the file buffer has contained. + myGPS.setFileBufferSize(fileBufferSize); // setFileBufferSize must be called _before_ .begin + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing...")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + // (This will also disable any "auto" messages that were enabled and saved by other examples and reduce the load on the I2C bus) + //myGPS.factoryDefault(); delay(5000); + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + myGPS.setNavigationFrequency(1); //Produce one navigation solution per second (that's plenty for Precise Point Positioning) + + myGPS.setAutoRXMSFRBX(true, false); // Enable automatic RXM SFRBX messages: without callback; without implicit update + + myGPS.logRXMSFRBX(); // Enable RXM SFRBX data logging + + myGPS.setAutoRXMRAWX(true, false); // Enable automatic RXM RAWX messages: without callback; without implicit update + + myGPS.logRXMRAWX(); // Enable RXM RAWX data logging + + Serial.println(F("Press any key to stop logging.")); + + lastPrint = millis(); // Initialize lastPrint +} + +void loop() +{ + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + myGPS.checkUblox(); // Check for the arrival of new data and process it. + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + while (myGPS.fileBufferAvailable() >= sdWriteSize) // Check to see if we have at least sdWriteSize waiting in the buffer + { + digitalWrite(LED_BUILTIN, HIGH); // Flash LED_BUILTIN each time we write to the SD card + + uint8_t myBuffer[sdWriteSize]; // Create our own buffer to hold the data while we write it to SD card + + myGPS.extractFileBufferData((uint8_t *)&myBuffer, sdWriteSize); // Extract exactly sdWriteSize bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, sdWriteSize); // Write exactly sdWriteSize bytes from myBuffer to the ubxDataFile on the SD card + + bytesWritten += sdWriteSize; // Update bytesWritten + + // In case the SD writing is slow or there is a lot of data to write, keep checking for the arrival of new data + myGPS.checkUblox(); // Check for the arrival of new data and process it. + + digitalWrite(LED_BUILTIN, LOW); // Turn LED_BUILTIN off again + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if (millis() > (lastPrint + 1000)) // Print bytesWritten once per second + { + Serial.print(F("The number of bytes written to SD card is ")); // Print how many bytes have been written to SD card + Serial.println(bytesWritten); + + uint16_t maxBufferBytes = myGPS.getMaxFileBufferAvail(); // Get how full the file buffer has been (not how full it is now) + + //Serial.print(F("The maximum number of bytes which the file buffer has contained is: ")); // It is a fun thing to watch how full the buffer gets + //Serial.println(maxBufferBytes); + + if (maxBufferBytes > ((fileBufferSize / 5) * 4)) // Warn the user if fileBufferSize was more than 80% full + { + Serial.println(F("Warning: the file buffer has been over 80% full. Some data may have been lost.")); + } + + lastPrint = millis(); // Update lastPrint + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if (Serial.available()) // Check if the user wants to stop logging + { + uint16_t remainingBytes = myGPS.fileBufferAvailable(); // Check if there are any bytes remaining in the file buffer + + while (remainingBytes > 0) // While there is still data in the file buffer + { + digitalWrite(LED_BUILTIN, HIGH); // Flash LED_BUILTIN while we write to the SD card + + uint8_t myBuffer[sdWriteSize]; // Create our own buffer to hold the data while we write it to SD card + + uint16_t bytesToWrite = remainingBytes; // Write the remaining bytes to SD card sdWriteSize bytes at a time + if (bytesToWrite > sdWriteSize) + { + bytesToWrite = sdWriteSize; + } + + myGPS.extractFileBufferData((uint8_t *)&myBuffer, bytesToWrite); // Extract bytesToWrite bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, bytesToWrite); // Write bytesToWrite bytes from myBuffer to the ubxDataFile on the SD card + + bytesWritten += bytesToWrite; // Update bytesWritten + + remainingBytes -= bytesToWrite; // Decrement remainingBytes + } + + digitalWrite(LED_BUILTIN, LOW); // Turn LED_BUILTIN off + + Serial.print(F("The total number of bytes written to SD card is ")); // Print how many bytes have been written to SD card + Serial.println(bytesWritten); + + myFile.close(); // Close the data file + + Serial.println(F("Logging stopped. Freezing...")); + while(1); // Do nothing more + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +} diff --git a/examples/Data_Logging/DataLoggingExample5_Fast_RXM/DataLoggingExample5_Fast_RXM.ino b/examples/Data_Logging/DataLoggingExample5_Fast_RXM/DataLoggingExample5_Fast_RXM.ino new file mode 100644 index 0000000..3ce095c --- /dev/null +++ b/examples/Data_Logging/DataLoggingExample5_Fast_RXM/DataLoggingExample5_Fast_RXM.ino @@ -0,0 +1,272 @@ +/* + Configuring the GNSS to automatically send RXM SFRBX and RAWX reports over I2C and log them to file on SD card + without using callbacks and ** as fast as your module can go! ** + By: Paul Clark + SparkFun Electronics + Date: December 30th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the u-blox GNSS to send RXM SFRBX and RAWX reports automatically + and log the data to SD card in UBX format without using callbacks and ** as fast as your module can go! ** + + ** Please note: this example will only work on u-blox ADR or High Precision GNSS or Time Sync products ** + + ** Please note: this example will only work on processors like the Artemis which have plenty of RAM available ** + + Data is logged in u-blox UBX format. Please see the u-blox protocol specification for more details. + You can replay and analyze the data using u-center: + https://www.u-blox.com/en/product/u-center + Or you can use (e.g.) RTKLIB to analyze the data and extract your precise location or produce + Post-Processed Kinematic data: + https://rtklibexplorer.wordpress.com/ + http://rtkexplorer.com/downloads/rtklib-code/ + + This code is intended to be run on the MicroMod Data Logging Carrier Board using the Artemis Processor + but can be adapted by changing the chip select pin and SPI definitions: + https://www.sparkfun.com/products/16829 + https://www.sparkfun.com/products/16401 + + Hardware Connections: + Please see: https://learn.sparkfun.com/tutorials/micromod-data-logging-carrier-board-hookup-guide + Insert the Artemis Processor into the MicroMod Data Logging Carrier Board and secure with the screw. + Connect your GNSS breakout to the Carrier Board using a Qwiic cable. + Connect an antenna to your GNSS board if required. + Insert a formatted micro-SD card into the socket on the Carrier Board. + Connect the Carrier Board to your computer using a USB-C cable. + Ensure you have the SparkFun Apollo3 boards installed: http://boardsmanager/All#SparkFun_Apollo3 + This code has been tested using version 1.2.1 of the Apollo3 boards on Arduino IDE 1.8.13. + Select "SparkFun Artemis MicroMod" as the board type. + Press upload to upload the code onto the Artemis. + Open the Serial Monitor at 115200 baud to see the output. + + To minimise I2C bus errors, it is a good idea to open the I2C pull-up split pad links on + both the MicroMod Data Logging Carrier Board and the u-blox module breakout. + + 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 +*/ + +#include +#include +#include //Needed for I2C to GNSS + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +File myFile; //File that all GNSS data is written to + +#define sdChipSelect CS //Primary SPI Chip Select is CS for the MicroMod Artemis Processor. Adjust for your processor if necessary. + +#define sdWriteSize 512 // Write data to the SD card in blocks of 512 bytes +#define fileBufferSize 32768 // Allocate 32KBytes of RAM for UBX message storage + +unsigned long lastPrint; // Record when the last Serial print took place +unsigned long bytesWritten = 0; // Record how many bytes have been written to SD card + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + pinMode(LED_BUILTIN, OUTPUT); // Flash LED_BUILTIN each time we write to the SD card + digitalWrite(LED_BUILTIN, LOW); + + Wire.begin(); // Start I2C communication + +#if defined(AM_PART_APOLLO3) + Wire.setPullups(0); // On the Artemis, we can disable the internal I2C pull-ups too to help reduce bus errors +#endif + + while (Serial.available()) // Make sure the Serial buffer is empty + { + Serial.read(); + } + + Serial.println(F("Press any key to start logging.")); + + while (!Serial.available()) // Wait for the user to press a key + { + ; // Do nothing + } + + delay(100); // Wait, just in case multiple characters were sent + + while (Serial.available()) // Empty the Serial buffer + { + Serial.read(); + } + + Serial.println("Initializing SD card..."); + + // See if the card is present and can be initialized: + if (!SD.begin(sdChipSelect)) + { + Serial.println("Card failed, or not present. Freezing..."); + // don't do anything more: + while (1); + } + Serial.println("SD card initialized."); + + // Create or open a file called "Fast_RXM.ubx" on the SD card. + // If the file already exists, the new data is appended to the end of the file. + myFile = SD.open("Fast_RXM.ubx", FILE_WRITE); + if(!myFile) + { + Serial.println(F("Failed to create UBX data file! Freezing...")); + while (1); + } + + //myGPS.enableDebugging(); // Uncomment this line to enable lots of helpful GNSS debug messages on Serial + //myGPS.enableDebugging(Serial, true); // Or, uncomment this line to enable only the important GNSS debug messages on Serial + + myGPS.disableUBX7Fcheck(); // RAWX data can legitimately contain 0x7F, so we need to disable the "7F" check in checkUbloxI2C + + // RAWX messages can be over 2KBytes in size, so we need to make sure we allocate enough RAM to hold all the data. + // SD cards can occasionally 'hiccup' and a write takes much longer than usual. The buffer needs to be big enough + // to hold the backlog of data if/when this happens. + // getMaxFileBufferAvail will tell us the maximum number of bytes which the file buffer has contained. + myGPS.setFileBufferSize(fileBufferSize); // setFileBufferSize must be called _before_ .begin + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing...")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + // (This will also disable any "auto" messages that were enabled and saved by other examples and reduce the load on the I2C bus) + //myGPS.factoryDefault(); delay(5000); + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + // Modules like the ZED-F9P can produce RAW navigation data at rates of up to 25Hz but not while using all of the GNSS constellations. + // Please consult the data sheet for the Performance figures for your module. + // In this example we make sure GPS is enabled and then disable Galileo, GLONASS, BeiDou, SBAS and QZSS to achieve 25Hz. + myGPS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GPS); // Make sure GPS is enabled (we must leave at least one major GNSS enabled!) + myGPS.enableGNSS(false, SFE_UBLOX_GNSS_ID_SBAS); // Disable SBAS + myGPS.enableGNSS(false, SFE_UBLOX_GNSS_ID_GALILEO); // Disable Galileo + myGPS.enableGNSS(false, SFE_UBLOX_GNSS_ID_BEIDOU); // Disable BeiDou + myGPS.enableGNSS(false, SFE_UBLOX_GNSS_ID_IMES); // Disable IMES + myGPS.enableGNSS(false, SFE_UBLOX_GNSS_ID_QZSS); // Disable QZSS + myGPS.enableGNSS(false, SFE_UBLOX_GNSS_ID_GLONASS); // Disable GLONASS + + delay(2000); // Give the module some extra time to get ready + + //Produce 7 navigation solutions per second. That's a lot of RAWX data - especially when using both GPS bands L1 and L2. + //The SD library and card need to be able to cope with the data rate too. You may need a faster SD library to go above 7Hz. + myGPS.setNavigationFrequency(7); + + myGPS.setAutoRXMSFRBX(true, false); // Enable automatic RXM SFRBX messages: without callback; without implicit update + + myGPS.logRXMSFRBX(); // Enable RXM SFRBX data logging + + myGPS.setAutoRXMRAWX(true, false); // Enable automatic RXM RAWX messages: without callback; without implicit update + + myGPS.logRXMRAWX(); // Enable RXM RAWX data logging + + Serial.println(F("Press any key to stop logging.")); + + lastPrint = millis(); // Initialize lastPrint +} + +void loop() +{ + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + myGPS.checkUblox(); // Check for the arrival of new data and process it. + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + while (myGPS.fileBufferAvailable() >= sdWriteSize) // Check to see if we have at least sdWriteSize waiting in the buffer + { + digitalWrite(LED_BUILTIN, HIGH); // Flash LED_BUILTIN each time we write to the SD card + + uint8_t myBuffer[sdWriteSize]; // Create our own buffer to hold the data while we write it to SD card + + myGPS.extractFileBufferData((uint8_t *)&myBuffer, sdWriteSize); // Extract exactly sdWriteSize bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, sdWriteSize); // Write exactly sdWriteSize bytes from myBuffer to the ubxDataFile on the SD card + + bytesWritten += sdWriteSize; // Update bytesWritten + + // In case the SD writing is slow or there is a lot of data to write, keep checking for the arrival of new data + myGPS.checkUblox(); // Check for the arrival of new data and process it. + + digitalWrite(LED_BUILTIN, LOW); // Turn LED_BUILTIN off again + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if (millis() > (lastPrint + 1000)) // Print bytesWritten once per second + { + Serial.print(F("The number of bytes written to SD card is: ")); // Print how many bytes have been written to SD card + Serial.println(bytesWritten); + + uint16_t maxBufferBytes = myGPS.getMaxFileBufferAvail(); // Get how full the file buffer has been (not how full it is now) + + //Serial.print(F("The maximum number of bytes which the file buffer has contained is: ")); // It is a fun thing to watch how full the buffer gets + //Serial.println(maxBufferBytes); + + if (maxBufferBytes > ((fileBufferSize / 5) * 4)) // Warn the user if fileBufferSize was more than 80% full + { + Serial.println(F("Warning: the file buffer has been over 80% full. Some data may have been lost.")); + } + + lastPrint = millis(); // Update lastPrint + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if (Serial.available()) // Check if the user wants to stop logging + { + myGPS.setAutoRXMSFRBX(false, false); // Disable the automatic RXM SFRBX messages + myGPS.setAutoRXMRAWX(false, false); // Disable the automatic RXM RAWX messages + + delay(1000); // Allow time for any remaining messages to arrive + myGPS.checkUblox(); // Process any remaining data + + uint16_t remainingBytes = myGPS.fileBufferAvailable(); // Check if there are any bytes remaining in the file buffer + + while (remainingBytes > 0) // While there is still data in the file buffer + { + digitalWrite(LED_BUILTIN, HIGH); // Flash LED_BUILTIN while we write to the SD card + + uint8_t myBuffer[sdWriteSize]; // Create our own buffer to hold the data while we write it to SD card + + uint16_t bytesToWrite = remainingBytes; // Write the remaining bytes to SD card sdWriteSize bytes at a time + if (bytesToWrite > sdWriteSize) + { + bytesToWrite = sdWriteSize; + } + + myGPS.extractFileBufferData((uint8_t *)&myBuffer, bytesToWrite); // Extract bytesToWrite bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, bytesToWrite); // Write bytesToWrite bytes from myBuffer to the ubxDataFile on the SD card + + bytesWritten += bytesToWrite; // Update bytesWritten + + remainingBytes -= bytesToWrite; // Decrement remainingBytes + } + + digitalWrite(LED_BUILTIN, LOW); // Turn LED_BUILTIN off + + Serial.print(F("The total number of bytes written to SD card is: ")); // Print how many bytes have been written to SD card + Serial.println(bytesWritten); + + uint16_t maxBufferBytes = myGPS.getMaxFileBufferAvail(); // Show how full the file buffer has been (not how full it is now) + Serial.print(F("The maximum number of bytes which the file buffer has contained is: ")); + Serial.println(maxBufferBytes); + + myFile.close(); // Close the data file + + Serial.println(F("Logging stopped. Freezing...")); + while(1); // Do nothing more + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +} diff --git a/examples/Dead_Reckoning/Example1_calibrateSensor/Example1_calibrateSensor.ino b/examples/Dead_Reckoning/Example1_calibrateSensor/Example1_calibrateSensor.ino new file mode 100644 index 0000000..9ef8c03 --- /dev/null +++ b/examples/Dead_Reckoning/Example1_calibrateSensor/Example1_calibrateSensor.ino @@ -0,0 +1,75 @@ +/* + By: Elias Santistevan + SparkFun Electronics + Date: May, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + ZED-F9R: https://www.sparkfun.com/products/16344 + + Hardware Connections: + Plug a Qwiic cable into the GNSS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + + To take advantage of the internal IMU of either the Dead Reckoning GNSS + boards (ZED-F9R, NEO-M8U), you must first calibrate it. This includes securing the GNSS module + to your vehicle so that it is stable within 2 degrees and that the frame of + reference of the board is consistent with the picture outlined in the + Receiver-Description-Prot-Spec Datasheet under Automotive/Untethered Dead + Reckoning. You may also check either the ZED-F9R or NEO-M8U Hookup Guide for + more information. After the board is secure, you'll need to put the module + through certain conditions for proper calibration: acceleration, turning, + stopping for a few minutes, getting to a speed over 30km/h all under a clear sky + with good GNSS signal. This example simply looks at the + "fusionMode" status which indicates whether the SparkFun Dead Reckoning is + initializing - 0, calibrated - 1, or if an error has occurred - 2,3. +*/ + +#include //Needed for I2C to GNSS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + //myGPS.resetIMUalignment(); // Uncomment this line to reset the IMU alignment +} + +void loop() +{ + // ESF data is produced at the navigation rate, so by default we'll get fresh data once per second + if (myGPS.getEsfInfo()) // Poll new ESF STATUS data + { + Serial.print(F("Fusion Mode: ")); + Serial.print(myGPS.packetUBXESFSTATUS->data.fusionMode); + if (myGPS.packetUBXESFSTATUS->data.fusionMode == 0) + Serial.println(F(" Sensor is initializing...")); + else if (myGPS.packetUBXESFSTATUS->data.fusionMode == 1) + Serial.println(F(" Sensor is calibrated!")); + else if (myGPS.packetUBXESFSTATUS->data.fusionMode == 2) + Serial.println(F(" Sensor fusion is suspended!")); + else if (myGPS.packetUBXESFSTATUS->data.fusionMode == 3) + Serial.println(F(" Sensor fusion is disabled!")); + } + + delay(250); +} diff --git a/examples/Dead_Reckoning/Example2_getIMUData/Example2_getIMUData.ino b/examples/Dead_Reckoning/Example2_getIMUData/Example2_getIMUData.ino new file mode 100644 index 0000000..6ff851e --- /dev/null +++ b/examples/Dead_Reckoning/Example2_getIMUData/Example2_getIMUData.ino @@ -0,0 +1,91 @@ +/* + By: Elias Santistevan + SparkFun Electronics + Date: May, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + ZED-F9R: https://www.sparkfun.com/products/16344 + + Hardware Connections: + Plug a Qwiic cable into the GNSS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + + After calibrating the module, also known as "Fusion Mode", you can get + data directly from the IMU. This data is integrated directly into the GNSS + output, but is provided by the module as well. + +*/ + +#include //Needed for I2C to GNSS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + if (myGPS.getEsfInfo()){ + + Serial.print(F("Fusion Mode: ")); + Serial.println(myGPS.packetUBXESFSTATUS->data.fusionMode); + + if (myGPS.packetUBXESFSTATUS->data.fusionMode == 1){ + Serial.println(F("Fusion Mode is Initialized!")); + } + else { + Serial.println(F("Fusion Mode is either disabled or not initialized!")); + Serial.println(F("Please see the previous example for more information.")); + } + } +} + +void loop() +{ + // ESF data is produced at the navigation rate, so by default we'll get fresh data once per second + if (myGPS.getEsfIns()) // Poll new ESF INS data + { + Serial.print(F("X Ang Rate: ")); + Serial.print(myGPS.packetUBXESFINS->data.xAngRate); + Serial.print(F(" Y Ang Rate: ")); + Serial.print(myGPS.packetUBXESFINS->data.yAngRate); + Serial.print(F(" Z Ang Rate: ")); + Serial.print(myGPS.packetUBXESFINS->data.zAngRate); + Serial.print(F(" X Accel: ")); + Serial.print(myGPS.packetUBXESFINS->data.xAccel); + Serial.print(F(" Y Accel: ")); + Serial.print(myGPS.packetUBXESFINS->data.yAccel); + Serial.print(F(" Z Accel: ")); + Serial.print(myGPS.packetUBXESFINS->data.zAccel); + + // These values also have "validity checks" that can be provided by the + // ublox library by reading bitfield0 + Serial.print(F(" Validity: ")); + Serial.print(myGPS.packetUBXESFINS->data.bitfield0.bits.xAngRateValid); + Serial.print(myGPS.packetUBXESFINS->data.bitfield0.bits.yAngRateValid); + Serial.print(myGPS.packetUBXESFINS->data.bitfield0.bits.zAngRateValid); + Serial.print(myGPS.packetUBXESFINS->data.bitfield0.bits.xAccelValid); + Serial.print(myGPS.packetUBXESFINS->data.bitfield0.bits.yAccelValid); + Serial.println(myGPS.packetUBXESFINS->data.bitfield0.bits.zAccelValid); + } + + delay(250); +} diff --git a/examples/Dead_Reckoning/Example3_getSensorStatus/Example3_getSensorStatus.ino b/examples/Dead_Reckoning/Example3_getSensorStatus/Example3_getSensorStatus.ino new file mode 100644 index 0000000..a324394 --- /dev/null +++ b/examples/Dead_Reckoning/Example3_getSensorStatus/Example3_getSensorStatus.ino @@ -0,0 +1,125 @@ +/* + By: Elias Santistevan + SparkFun Electronics + Date: May, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + ZED-F9R: https://www.sparkfun.com/products/16344 + + Hardware Connections: + Plug a Qwiic cable into the GNSS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + + After calibrating the module, also known as "Fusion Mode", you can get + data directly from the IMU. This example code walks you through trouble + shooting or identifying the different states of any individual + "external" (which include internal) sensors you've hooked up (vehicle speed + sensor) or the internal IMU used by the modules. You can see if the sensor is + being used, if it's calibrated, ready, what data type it returns, the state + of the measurement etc. + +*/ + +#include //Needed for I2C to GNSS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + // GetEsfInfo also gets the number of sensors used by the ublox module, this + // includes (in the case of the ZED-F9R) wheel tick input from the vehicle + // speed sensor attached to the module. + if (myGPS.getEsfInfo()){ + + Serial.print(F("Fusion Mode: ")); + Serial.println(myGPS.packetUBXESFSTATUS->data.fusionMode); + + if (myGPS.packetUBXESFSTATUS->data.fusionMode == 1){ + Serial.println(F("Fusion Mode is Initialized!")); + } + else { + Serial.println(F("Fusion Mode is either disabled or not initialized!")); + Serial.println(F("Please see the previous example for more information.")); + } + } +} + +void loop() +{ + // ESF data is produced at the navigation rate, so by default we'll get fresh data once per second + if (myGPS.getEsfInfo()) // Poll new ESF STATUS data + { + UBX_ESF_STATUS_sensorStatus_t sensorStatus; // Create storage for the individual sensor status + + //See ublox receiver description or our hookup guide for information on the return values + + Serial.println(F(" ")); + Serial.println(F(" C ")); + Serial.println(F(" a ")); + Serial.println(F(" l ")); + Serial.println(F(" i ")); + Serial.println(F(" b M ")); + Serial.println(F(" r i N")); + Serial.println(F(" a B s o")); + Serial.println(F(" S t T B a s i")); + Serial.println(F("S e B i i a d e s")); + Serial.println(F("e n e o m d d y")); + Serial.println(F("n s i I n e T ")); + Serial.println(F("s o n s M i M M")); + Serial.println(F("o r g S S e m e e")); + Serial.println(F("r R t t a e a a")); + Serial.println(F(" T U e a a s s s")); + Serial.println(F("N y s a t t u T u u")); + Serial.println(F("o p e d u u r a r r")); + Serial.println(F(". e d y s s e g e e")); + Serial.println(F(" ")); + + for(uint8_t i = 0; i < myGPS.packetUBXESFSTATUS->data.numSens; i++) + { + myGPS.getSensorFusionStatus(&sensorStatus, i); // Extract the individual sensor data for this sensor + + Serial.print(i); Serial.print(F(" ")); // Print the sensor number + + // Print the sensor type + Serial.print(sensorStatus.sensStatus1.bits.type); + if (sensorStatus.sensStatus1.bits.type < 10) Serial.print(F(" ")); + Serial.print(F(" ")); + + Serial.print(sensorStatus.sensStatus1.bits.used); Serial.print(F(" ")); // Print the used flag + Serial.print(sensorStatus.sensStatus1.bits.ready); Serial.print(F(" ")); // Print the ready flag + + Serial.print(sensorStatus.sensStatus2.bits.calibStatus); Serial.print(F(" ")); // Print the calibration status + Serial.print(sensorStatus.sensStatus2.bits.timeStatus); Serial.print(F(" ")); // Print the time status + + Serial.print(sensorStatus.faults.bits.badMeas); Serial.print(F(" ")); // Print the bad measurement flag + Serial.print(sensorStatus.faults.bits.badTTag); Serial.print(F(" ")); // Print the time tag flag + Serial.print(sensorStatus.faults.bits.missingMeas); Serial.print(F(" ")); // Print the missing measurement flag + Serial.print(sensorStatus.faults.bits.noisyMeas); // Print the noisy measure flag + + Serial.println(); + } + } + + delay(250); +} diff --git a/examples/Dead_Reckoning/Example4_vehicleDynamics/Example4_vehicleDynamics.ino b/examples/Dead_Reckoning/Example4_vehicleDynamics/Example4_vehicleDynamics.ino new file mode 100644 index 0000000..41b23d7 --- /dev/null +++ b/examples/Dead_Reckoning/Example4_vehicleDynamics/Example4_vehicleDynamics.ino @@ -0,0 +1,83 @@ +/* + By: Elias Santistevan + SparkFun Electronics + Date: May, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + ZED-F9R: https://www.sparkfun.com/products/16344 + + Hardware Connections: + Plug a Qwiic cable into the GNSS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + + After calibrating the module and securing it to your vehicle such that it's + stable within 2 degrees, and the board is oriented correctly with regards to + the vehicle's frame, you can now read the vehicle's "attitude". The attitude + includes the vehicle's heading, pitch, and roll. You can also check the + accuracy of those readings. + +*/ + +#include //Needed for I2C to GNSS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + if (myGPS.getEsfInfo()){ + + Serial.print(F("Fusion Mode: ")); + Serial.println(myGPS.packetUBXESFSTATUS->data.fusionMode); + + if (myGPS.packetUBXESFSTATUS->data.fusionMode == 1){ + Serial.println(F("Fusion Mode is Initialized!")); + } + else { + Serial.println(F("Fusion Mode is either disabled or not initialized!")); + Serial.println(F("Please see the previous example for more information.")); + } + } +} + +void loop() +{ + // ESF data is produced at the navigation rate, so by default we'll get fresh data once per second + if (myGPS.getEsfAlignment()) // Poll new ESF ALG data + { + Serial.print(F("Status: ")); + Serial.print(myGPS.packetUBXESFALG->data.flags.bits.status); + Serial.print(F(" Roll: ")); + Serial.print(myGPS.getESFroll(), 2); // Use the helper function to get the roll in degrees + Serial.print(F(" Pitch: ")); + Serial.print(myGPS.getESFpitch(), 2); // Use the helper function to get the pitch in degrees + Serial.print(F(" Heading: ")); + Serial.print(myGPS.getESFyaw(), 2); // Use the helper function to get the yaw in degrees + Serial.print(F(" Errors: ")); + Serial.print(myGPS.packetUBXESFALG->data.error.bits.tiltAlgError); + Serial.print(myGPS.packetUBXESFALG->data.error.bits.yawAlgError); + Serial.println(myGPS.packetUBXESFALG->data.error.bits.angleError); + } + + delay(250); +} diff --git a/examples/Dead_Reckoning/Example5_getHNRData/Example5_getHNRData.ino b/examples/Dead_Reckoning/Example5_getHNRData/Example5_getHNRData.ino new file mode 100644 index 0000000..2ab7f76 --- /dev/null +++ b/examples/Dead_Reckoning/Example5_getHNRData/Example5_getHNRData.ino @@ -0,0 +1,94 @@ +/* + By: Paul Clark + SparkFun Electronics + Date: December, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example configures the High Navigation Rate on the NEO-M8U and then + polls and displays the attitude solution, vehicle dynamics information + and high rate position, velocity and time. + + This example polls the high rate data. + (The next example uses "autoHNR" to receive the HNR data automatically.) + + Please make sure your NEO-M8U is running UDR firmware >= 1.31. Please update using u-center if necessary: + https://www.u-blox.com/en/product/neo-m8u-module#tab-documentation-resources + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages on Serial + + if (myGPS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("Warning! u-blox GPS did not begin correctly.")); + Serial.println(F("(This may be because the I2C port is busy with HNR messages.)")); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + if (myGPS.setHNRNavigationRate(10) == true) //Set the High Navigation Rate to 10Hz + Serial.println(F("setHNRNavigationRate was successful")); + else + Serial.println(F("setHNRNavigationRate was NOT successful")); + + myGPS.setAutoHNRATT(false); //Make sure auto HNR attitude messages are disabled + myGPS.setAutoHNRINS(false); //Make sure auto HNR vehicle dynamics messages are disabled + myGPS.setAutoHNRPVT(false); //Make sure auto HNR PVT messages are disabled +} + +void loop() +{ + // Poll and print selected HNR data + if (myGPS.getHNRAtt(125) == true) // Request HNR Att data using a 125ms timeout + { + Serial.print(F("Roll: ")); + Serial.print(myGPS.getHNRroll(), 2); // Use the helper function to get the roll in degrees + Serial.print(F(" Pitch: ")); + Serial.print(myGPS.getHNRpitch(), 2); // Use the helper function to get the pitch in degrees + Serial.print(F(" Heading: ")); + Serial.println(myGPS.getHNRheading(), 2); // Use the helper function to get the heading in degrees + } + if (myGPS.getHNRDyn(125) == true) // Request HNR Dyn data using a 125ms timeout + { + Serial.print(F("xAccel: ")); + Serial.print(myGPS.packetUBXHNRINS->data.xAccel); + Serial.print(F(" yAccel: ")); + Serial.print(myGPS.packetUBXHNRINS->data.yAccel); + Serial.print(F(" zAccel: ")); + Serial.println(myGPS.packetUBXHNRINS->data.zAccel); + } + if (myGPS.getHNRPVT(125) == true) // Request HNR PVT data using a 125ms timeout + { + Serial.print(F("ns: ")); + Serial.print(myGPS.packetUBXHNRPVT->data.nano); + Serial.print(F(" Lat: ")); + Serial.print(myGPS.packetUBXHNRPVT->data.lat); + Serial.print(F(" Lon: ")); + Serial.println(myGPS.packetUBXHNRPVT->data.lon); + } +} diff --git a/examples/Dead_Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino b/examples/Dead_Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino new file mode 100644 index 0000000..f42e466 --- /dev/null +++ b/examples/Dead_Reckoning/Example6_getAutoHNRData/Example6_getAutoHNRData.ino @@ -0,0 +1,107 @@ +/* + By: Paul Clark + SparkFun Electronics + Date: December, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example configures the High Navigation Rate on the NEO-M8U and then + reads and displays the attitude solution, vehicle dynamics information + and high rate position, velocity and time. + + This example uses "autoHNR" to receive the HNR data automatically. + + Please make sure your NEO-M8U is running UDR firmware >= 1.31. Please update using u-center if necessary: + https://www.u-blox.com/en/product/neo-m8u-module#tab-documentation-resources + + Feel like supporting open source hardware? + Buy a board from SparkFun! + NEO-M8U: https://www.sparkfun.com/products/16329 + + Hardware Connections: + Plug a Qwiic cable into the GPS and a Redboard Qwiic + If you don't have a platform with a Qwiic connection use the + SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Open the serial monitor at 115200 baud to see the output + +*/ + +#include //Needed for I2C to GPS + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +boolean usingAutoHNRAtt = false; +boolean usingAutoHNRDyn = false; +boolean usingAutoHNRPVT = false; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages on Serial + + if (myGPS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("Warning! u-blox GPS did not begin correctly.")); + Serial.println(F("(This may be because the I2C port is busy with HNR messages.)")); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + if (myGPS.setHNRNavigationRate(10) == true) //Set the High Navigation Rate to 10Hz + Serial.println(F("setHNRNavigationRate was successful")); + else + Serial.println(F("setHNRNavigationRate was NOT successful")); + + usingAutoHNRAtt = myGPS.setAutoHNRATT(true); //Attempt to enable auto HNR attitude messages + if (usingAutoHNRAtt) + Serial.println(F("AutoHNRATT successful")); + + usingAutoHNRDyn = myGPS.setAutoHNRINS(true); //Attempt to enable auto HNR vehicle dynamics messages + if (usingAutoHNRDyn) + Serial.println(F("AutoHNRINS successful")); + + usingAutoHNRPVT = myGPS.setAutoHNRPVT(true); //Attempt to enable auto HNR PVT messages + if (usingAutoHNRPVT) + Serial.println(F("AutoHNRPVT successful")); +} + +void loop() +{ + if (usingAutoHNRAtt && (myGPS.getHNRAtt() == true)) // If setAutoHNRAtt was successful and new data is available + { + Serial.print(F("Roll: ")); // Print selected data + Serial.print(myGPS.getHNRroll(), 2); // Use the helper function to get the roll in degrees + Serial.print(F(" Pitch: ")); + Serial.print(myGPS.getHNRpitch(), 2); // Use the helper function to get the pitch in degrees + Serial.print(F(" Heading: ")); + Serial.println(myGPS.getHNRheading(), 2); // Use the helper function to get the heading in degrees + myGPS.flushHNRATT(); // Mark data as stale + } + if (usingAutoHNRDyn && (myGPS.getHNRDyn() == true)) // If setAutoHNRDyn was successful and new data is available + { + Serial.print(F("xAccel: ")); // Print selected data + Serial.print(myGPS.packetUBXHNRINS->data.xAccel); + Serial.print(F(" yAccel: ")); + Serial.print(myGPS.packetUBXHNRINS->data.yAccel); + Serial.print(F(" zAccel: ")); + Serial.println(myGPS.packetUBXHNRINS->data.zAccel); + myGPS.flushHNRINS(); // Mark data as stale + } + if (usingAutoHNRPVT && (myGPS.getHNRPVT() == true)) // If setAutoHNRPVT was successful and new data is available + { + Serial.print(F("ns: ")); // Print selected data + Serial.print(myGPS.packetUBXHNRPVT->data.nano); + Serial.print(F(" Lat: ")); + Serial.print(myGPS.packetUBXHNRPVT->data.lat); + Serial.print(F(" Lon: ")); + Serial.println(myGPS.packetUBXHNRPVT->data.lon); + myGPS.flushHNRPVT(); // Mark data as stale + } +} diff --git a/examples/Example10_AltitudeMSL/Example10_AltitudeMSL.ino b/examples/Example10_AltitudeMSL/Example10_AltitudeMSL.ino new file mode 100644 index 0000000..9a5c39e --- /dev/null +++ b/examples/Example10_AltitudeMSL/Example10_AltitudeMSL.ino @@ -0,0 +1,82 @@ +/* + Reading two altitudes - Mean Sea Level and Ellipsode + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a u-blox module for its lat/long/altitude. + + getAltitude() reports mm above ellipsode model of the globe. There are some + instances where altitude above Mean Sea Level is better. This example shows how + to use getAltitudeMSL(). The difference varies but is ~20m. + Ellipsoid model: https://www.esri.com/news/arcuser/0703/geoid1of3.html + Difference between Ellipsoid Model and Mean Sea Level: https://eos-gnss.com/elevation-for-beginners/ + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Tracks the passing of 2000ms (2 seconds) + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1); + } +} + +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 > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + long altitudeMSL = myGPS.getAltitudeMSL(); + Serial.print(F(" AltMSL: ")); + Serial.print(altitudeMSL); + Serial.print(F(" (mm)")); + + Serial.println(); + } +} diff --git a/examples/Example11_ResetModule/Example1_FactoryDefaultviaI2C/Example1_FactoryDefaultviaI2C.ino b/examples/Example11_ResetModule/Example1_FactoryDefaultviaI2C/Example1_FactoryDefaultviaI2C.ino new file mode 100644 index 0000000..6da8b00 --- /dev/null +++ b/examples/Example11_ResetModule/Example1_FactoryDefaultviaI2C/Example1_FactoryDefaultviaI2C.ino @@ -0,0 +1,60 @@ +/* + Send command to reset module over I2C + By: Nathan Seidle + Date: January 29rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to reset the U-Blox module to factory defaults over I2C. + + 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: + Connect the U-Blox serial port to Serial1 + If you're using an Uno or don't have a 2nd serial port (Serial1), consider using software serial + Open the serial monitor at 115200 baud to see the output +*/ + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1); + } + + while (Serial.available()) Serial.read(); //Trash any incoming chars + Serial.println("Press a key to reset module to factory defaults"); + while (Serial.available() == false) ; //Wait for user to send character + + myGPS.factoryReset(); //Reset everything: baud rate, I2C address, update rate, everything. + + delay(5000); // Wait while the module restarts + + while (myGPS.begin() == false) //Attempt to re-connect + { + delay(1000); + Serial.println(F("Attempting to re-connect to u-blox GNSS...")); + } + + Serial.println("Unit has now been factory reset. Freezing..."); + while(1); // Do nothing more +} + +void loop() +{ + +} diff --git a/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino b/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino new file mode 100644 index 0000000..fe974b6 --- /dev/null +++ b/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino @@ -0,0 +1,110 @@ +/* + Test baud rate changes on serial, factory reset, and hard reset. + By: Thorsten von Eicken + Date: January 29rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to reset the U-Blox module to factory defaults over serial. + + 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: + Connect the U-Blox serial port to Serial1 + If you're using a Uno or don't have a 2nd serial port (Serial1), use SoftwareSerial instead (see below) + Open the serial monitor at 115200 baud to see the output +*/ + +#include //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +#include + +//#define mySerial Serial1 // Uncomment this line to connect via Serial1 +// - or - +SoftwareSerial mySerial(10, 11); // Uncomment this line to connect via SoftwareSerial(RX, TX). Connect pin 10 to GNSS TX pin. + +#define defaultRate 9600 // Uncomment this line if you are using an M8 - which defaults to 9600 Baud on UART1 +// - or - +//#define defaultRate 38400 // Uncomment this line if you are using an F9 - which defaults to 38400 Baud on UART1 + +int state = 0; // steps through auto-baud, reset, etc states + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + //myGPS.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial +} + +void loop() +{ + Serial.print("===== STATE "); + Serial.println(state); + switch (state) { + case 0: // auto-baud connection, then switch to 38400 and save config + do { + Serial.println("GNSS: trying 38400 baud"); + mySerial.begin(38400); + if (myGPS.begin(mySerial)) break; + + delay(100); + Serial.println("GNSS: trying 9600 baud"); + mySerial.begin(9600); + if (myGPS.begin(mySerial)) { + Serial.println("GNSS: connected at 9600 baud, switching to 38400"); + myGPS.setSerialRate(38400); + delay(100); + } else { + delay(2000); //Wait a bit before trying again to limit the Serial output flood + } + } while(1); + myGPS.setUART1Output(COM_TYPE_UBX); //Set the UART port to output UBX only + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + Serial.println("GNSS serial connected, saved config"); + state++; + break; + case 1: // hardReset, expect to see GNSS back at 38400 baud + Serial.println("Issuing hardReset (cold start)"); + myGPS.hardReset(); + delay(2000); + mySerial.begin(38400); + if (myGPS.begin(mySerial)) { + Serial.println("Success."); + state++; + } else { + Serial.println("*** GNSS did not respond at 38400 baud, starting over."); + state = 0; + } + break; + case 2: // factoryReset, expect to see GNSS back at defaultRate baud + Serial.println("Issuing factoryReset"); + myGPS.factoryReset(); + delay(5000); // takes more than one second... a loop to resync would be best + mySerial.begin(defaultRate); + if (myGPS.begin(mySerial)) { + Serial.println("Success."); + state++; + } else { + Serial.println("*** GNSS did not come back at defaultRate baud, starting over."); + state = 0; + } + break; + case 3: // print version info + // Note: this may fail on boards like the UNO (ATmega328P) with modules like the ZED-F9P + // because getProtocolVersion returns a lot of data - more than the UNO's serial buffer can hold + Serial.print("GNSS protocol version: "); + Serial.print(myGPS.getProtocolVersionHigh()); + Serial.print('.'); + Serial.println(myGPS.getProtocolVersionLow()); + Serial.println("All finished! Freezing..."); + while(1); + } + delay(1000); +} diff --git a/examples/Example12_UseUart/Example12_UseUart.ino b/examples/Example12_UseUart/Example12_UseUart.ino new file mode 100644 index 0000000..b1add42 --- /dev/null +++ b/examples/Example12_UseUart/Example12_UseUart.ino @@ -0,0 +1,98 @@ +/* + Reading lat and long via UBX binary commands using UART @38400 baud - free from I2C + By: Nathan Seidle, Adapted from Example3_GetPosition by Thorsten von Eicken + SparkFun Electronics + Date: January 28rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the library and U-Blox for serial port use as well as + switching the module from the default 9600 baud to 38400. + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 10,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Connect the U-Blox serial TX pin to Uno pin 10 + Connect the U-Blox serial RX pin to Uno pin 11 + Open the serial monitor at 115200 baud to see the output +*/ + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +#include +SoftwareSerial mySerial(10, 11); // RX, TX. Pin 10 on Uno goes to TX pin on GNSS module. + +long lastTime = 0; //Simple local timer. Limits amount of I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + //Assume that the U-Blox GNSS is running at 9600 baud (the default) or at 38400 baud. + //Loop until we're in sync and then ensure it's at 38400 baud. + do { + Serial.println("GNSS: trying 38400 baud"); + mySerial.begin(38400); + if (myGPS.begin(mySerial) == true) break; + + delay(100); + Serial.println("GNSS: trying 9600 baud"); + mySerial.begin(9600); + if (myGPS.begin(mySerial) == true) { + Serial.println("GNSS: connected at 9600 baud, switching to 38400"); + myGPS.setSerialRate(38400); + delay(100); + } else { + //myGPS.factoryReset(); + delay(2000); //Wait a bit before trying again to limit the Serial output + } + } while(1); + Serial.println("GNSS serial connected"); + + myGPS.setUART1Output(COM_TYPE_UBX); //Set the UART port to output UBX only + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR +} + +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 > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + } +} diff --git a/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino b/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino new file mode 100644 index 0000000..d5dd31d --- /dev/null +++ b/examples/Example13_PVT/Example1_AutoPVT/Example1_AutoPVT.ino @@ -0,0 +1,141 @@ +/* + Configuring the GNSS to automatically send position reports over I2C + By: Nathan Seidle and Thorsten von Eicken + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the U-Blox GNSS the send navigation reports automatically + and retrieving the latest one via getPVT. This eliminates the blocking in getPVT while the GNSS + produces a fresh navigation solution at the expense of returning a slighly old solution. + + This can be used over serial or over I2C, this example shows the I2C use. With serial the GNSS + simply outputs the UBX_NAV_PVT packet. With I2C it queues it into its internal I2C buffer (4KB in + size?) where it can be retrieved in the next I2C poll. + + 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 //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + 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); //Tell the GNSS to "send" each solution + //myGPS.saveConfiguration(); //Optional: Save the current settings to flash and BBR +} + +void loop() +{ + // Calling getPVT returns true if there actually is a fresh navigation solution available. + // Start the reading only when valid LLH is available + if (myGPS.getPVT() && (myGPS.getInvalidLlh() == false)) + { + Serial.println(); + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + int PDOP = myGPS.getPDOP(); + Serial.print(F(" PDOP: ")); + Serial.print(PDOP); + Serial.print(F(" (10^-2)")); + + int nedNorthVel = myGPS.getNedNorthVel(); + Serial.print(F(" VelN: ")); + Serial.print(nedNorthVel); + Serial.print(F(" (mm/s)")); + + int nedEastVel = myGPS.getNedEastVel(); + Serial.print(F(" VelE: ")); + Serial.print(nedEastVel); + Serial.print(F(" (mm/s)")); + + int nedDownVel = myGPS.getNedDownVel(); + Serial.print(F(" VelD: ")); + Serial.print(nedDownVel); + Serial.print(F(" (mm/s)")); + + int verticalAccEst = myGPS.getVerticalAccEst(); + Serial.print(F(" VAccEst: ")); + Serial.print(verticalAccEst); + Serial.print(F(" (mm)")); + + int horizontalAccEst = myGPS.getHorizontalAccEst(); + Serial.print(F(" HAccEst: ")); + Serial.print(horizontalAccEst); + Serial.print(F(" (mm)")); + + int speedAccEst = myGPS.getSpeedAccEst(); + Serial.print(F(" SpeedAccEst: ")); + Serial.print(speedAccEst); + Serial.print(F(" (mm/s)")); + + int headAccEst = myGPS.getHeadingAccEst(); + Serial.print(F(" HeadAccEst: ")); + Serial.print(headAccEst); + Serial.print(F(" (degrees * 10^-5)")); + + if (myGPS.getHeadVehValid() == true) { + int headVeh = myGPS.getHeadVeh(); + Serial.print(F(" HeadVeh: ")); + Serial.print(headVeh); + Serial.print(F(" (degrees * 10^-5)")); + + int magDec = myGPS.getMagDec(); + Serial.print(F(" MagDec: ")); + Serial.print(magDec); + Serial.print(F(" (degrees * 10^-2)")); + + int magAcc = myGPS.getMagAcc(); + Serial.print(F(" MagAcc: ")); + Serial.print(magAcc); + Serial.print(F(" (degrees * 10^-2)")); + } + + Serial.println(); + } else { + Serial.print("."); + delay(50); + } +} diff --git a/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino b/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino new file mode 100644 index 0000000..92f85b3 --- /dev/null +++ b/examples/Example13_PVT/Example2_AutoPVT_ExplicitUpdate/Example2_AutoPVT_ExplicitUpdate.ino @@ -0,0 +1,103 @@ +/* + Configuring the GNSS to automatically send position reports over I2C, with explicit data parsing calls + By: Nathan Seidle Thorsten von Eicken and Felix Jirka + SparkFun Electronics + Date: July 1st, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the U-Blox GNSS the send navigation reports automatically + and retrieving the latest one via checkUblox when available. + This eliminates the implicit update in getPVT when accessing data fields twice. + Also this reduces the memory overhead of a separate buffer while introducing a slight error by inconsistencies because of the unsynchronized updates (on a multi core system). + + This can be used over serial or over I2C, this example shows the I2C use. With serial the GNSS + simply outputs the UBX_NAV_PVT packet. With I2C it queues it into its internal I2C buffer (4KB in + size?) where it can be retrieved in the next I2C poll. + + 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 //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + 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 GNSS to "send" each solution and the lib not to update stale data implicitly + //myGPS.saveConfiguration(); //Optional: Save the current settings to flash and BBR +} + +/* + 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 GNSS location significantly within a 2Hz - 5Hz update rate. + Also you could oversample (10Hz - 20Hz) the data to smooth out such issues... +*/ +void loop() +{ + static uint16_t counter = 0; + + if (counter % 10 == 0) + { + // update your AHRS filter here for a ~100Hz update rate + // GNSS data will be quasi static but data from your IMU will be changing + } + // debug output each half second + if (counter % 500 == 0) + { + Serial.println(); + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + } + // call checkUblox all 50ms to capture the GNSS data + if (counter % 50 == 0) + { + myGPS.checkUblox(); + } + delay(1); + counter++; +} diff --git a/examples/Example13_PVT/Example3_AutoPVTviaUart/Example3_AutoPVTviaUart.ino b/examples/Example13_PVT/Example3_AutoPVTviaUart/Example3_AutoPVTviaUart.ino new file mode 100644 index 0000000..53e75c3 --- /dev/null +++ b/examples/Example13_PVT/Example3_AutoPVTviaUart/Example3_AutoPVTviaUart.ino @@ -0,0 +1,107 @@ +/* + Configuring the GNSS to automatically send position reports over Serial + By: Nathan Seidle, Adapted from Example11 by Felix Jirka + SparkFun Electronics + Date: July 2nd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the U-Blox GNSS the send navigation reports automatically + and retrieving the latest one via getPVT. This eliminates the blocking in getPVT while the GNSS + produces a fresh navigation solution at the expense of returning a slighly old solution. + + This can be used over serial or over I2C, this example shows the Serial use. With serial the GNSS + simply outputs the UBX_NAV_PVT packet. With I2C it queues it into its internal I2C buffer (4KB in + size?) where it can be retrieved in the next I2C poll. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +#include + +//#define mySerial Serial2 // Uncomment this line to connect via Serial2 +// - or - +SoftwareSerial mySerial(10, 11); // Uncomment this line to connect via SoftwareSerial(RX, TX). Connect pin 10 to GNSS TX pin. + +//#define baudRate 9600 // Uncomment this line to select 9600 Baud for the M8 +// - or - +#define baudRate 38400 // Uncomment this line to select 38400 Baud for the F9 + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + mySerial.begin(baudRate); // Start the Serial port + + if (myGPS.begin(mySerial) == false) //Connect to the u-blox module using Serial + { + Serial.println(F("u-blox GNSS not detected. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setUART1Output(COM_TYPE_UBX); //Set the UART1 port to output UBX only (turn off NMEA noise) + myGPS.setNavigationFrequency(2); //Produce two solutions per second + myGPS.setAutoPVT(true); //Tell the GNSS to "send" each solution + //myGPS.saveConfiguration(); //Optional: Save the current settings to flash and BBR +} + +void loop() +{ + // getPVT will return true if there actually is a fresh navigation solution available. + // Important note: the PVT message is 100 bytes long. We need to call getPVT often enough + // to prevent serial buffer overflows on boards like the original RedBoard / UNO. + // At 38400 Baud, the 100 PVT bytes will arrive in 26ms. + // On the RedBoard, we need to call getPVT every 5ms to keep up. + if (myGPS.getPVT()) + { + Serial.println(); + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + } + else + { + delay(5); // Delay for 5ms only + + static int counter = 0; // Print a dot every 50ms + counter++; + if (counter > 10) + { + Serial.print("."); + counter = 0; + } + } +} diff --git a/examples/Example13_PVT/Example4_AssumeAutoPVTviaUart/Example4_AssumeAutoPVTviaUart.ino b/examples/Example13_PVT/Example4_AssumeAutoPVTviaUart/Example4_AssumeAutoPVTviaUart.ino new file mode 100644 index 0000000..6045a01 --- /dev/null +++ b/examples/Example13_PVT/Example4_AssumeAutoPVTviaUart/Example4_AssumeAutoPVTviaUart.ino @@ -0,0 +1,110 @@ +/* + Reading lat and long via UBX binary commands using an RX-only UART + By: Nathan Seidle, Adapted from Example11 by Felix Jirka + SparkFun Electronics + Date: July 2nd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the library for serial port use with a single wire connection using the assumeAutoPVT method. + Saving your pins for other stuff :-) + + This example only works correctly if the module has already been configured is sending PVT messages via its UART1 TX pin. + Run the previous example to enable the auto PVT messages if you need to. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Preconditions: + U-Blox module is configured to send cyclical PVT message + + Hardware Connections: + Connect the U-Blox serial TX pin to Rx of Serial2 (default: GPIO16) on your ESP32 + Or, switch to SoftwareSerial + Open the serial monitor at 115200 baud to see the output +*/ + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +#include + +//#define mySerial Serial2 // Uncomment this line to connect via Serial2 +// - or - +SoftwareSerial mySerial(10, 11); // Uncomment this line to connect via SoftwareSerial(RX, TX). Connect pin 10 to GNSS TX pin. + +//#define baudRate 9600 // Uncomment this line to select 9600 Baud for the M8 +// - or - +#define baudRate 38400 // Uncomment this line to select 38400 Baud for the F9 + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + //Use any Serial port with at least a RX Pin connected or a receive only version of SoftwareSerial here + //Assume that the U-Blox GNSS is running at baudRate baud + mySerial.begin(baudRate); + // No need to check return value as internal call to isConnected() will not succeed + myGPS.begin(mySerial); + + // Tell the library we are expecting the module to send PVT messages by itself to our Rx pin. + // You can set second parameter to "false" if you want to control the parsing and eviction of the data (need to call checkUblox cyclically) + myGPS.assumeAutoPVT(true, true); + +} + +void loop() +{ + // If implicit updates are enabled, calling getPVT will trigger parsing of the incoming messages + // and return true once a PVT message has been parsed. + // In case you want to use explicit updates, wrap this in a timer and call checkUblox() + // as often as needed, not to overflow your UART buffers. + // + // Important note: the PVT message is 100 bytes long. We need to call getPVT often enough + // to prevent serial buffer overflows on boards like the original RedBoard / UNO. + // At 38400 Baud, the 100 PVT bytes will arrive in 26ms. + // On the RedBoard, we need to call getPVT every 5ms to keep up. + if (myGPS.getPVT()) + { + Serial.println(); + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + } + else + { + delay(5); // Delay for 5ms only + + static int counter = 0; // Print a dot every 50ms + counter++; + if (counter > 10) + { + Serial.print("."); + counter = 0; + } + } +} diff --git a/examples/Example14_DebugOutput/Example14_DebugOutput.ino b/examples/Example14_DebugOutput/Example14_DebugOutput.ino new file mode 100644 index 0000000..983b0de --- /dev/null +++ b/examples/Example14_DebugOutput/Example14_DebugOutput.ino @@ -0,0 +1,107 @@ +/* + Debug Output + By: Nathan Seidle, Adapted from Example3_GetPosition by Thorsten von Eicken + SparkFun Electronics + Date: January 28rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the debug output from the library. + Debug shows various packet and status outputs. These prints can be directed + towards Serial (as in Serial.print) or any other port (Serial1, SerialUSB, etc). + + You can also limit the debug messages to the "critical" ones by adding an extra argument. + + The debug messages can be disabled again by calling disableDebugging() + + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +unsigned long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. +int counter = 0; // Disable the debug messages when counter reaches 20 + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) //Connect to the Ublox module using Wire port + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + //myGPS.saveConfiguration(); //Optional: Save the current settings to flash and BBR + + myGPS.enableDebugging(); //Enable all the debug messages over Serial (default) + + //myGPS.enableDebugging(SerialUSB); //Enable debug messages over Serial USB + + //myGPS.enableDebugging(Serial, true); //Enable only the critical debug messages over Serial + +} + +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 > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.print(F(" ")); + Serial.print(myGPS.getYear()); + Serial.print(F("-")); + Serial.print(myGPS.getMonth()); + Serial.print(F("-")); + Serial.print(myGPS.getDay()); + Serial.print(F(" ")); + Serial.print(myGPS.getHour()); + Serial.print(F(":")); + Serial.print(myGPS.getMinute()); + Serial.print(F(":")); + Serial.println(myGPS.getSecond()); + + Serial.println(); + + counter++; // Increment counter + if (counter == 20) + { + myGPS.disableDebugging(); // Disable the debug messages when counter reaches 20 + } + } +} diff --git a/examples/Example15_GetDateTime/Example15_GetDateTime.ino b/examples/Example15_GetDateTime/Example15_GetDateTime.ino new file mode 100644 index 0000000..abfe800 --- /dev/null +++ b/examples/Example15_GetDateTime/Example15_GetDateTime.ino @@ -0,0 +1,107 @@ +/* + Getting time and date using u-blox commands + By: davidallenmann + SparkFun Electronics + Date: April 16th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a u-blox module for the current time and date. We also + turn off the NMEA output on the I2C port. This decreases the amount of I2C traffic + dramatically. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + //myGPS.saveConfiguration(); //Optional: Save the current settings to flash and BBR +} + +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 > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + Serial.print(myGPS.getYear()); + Serial.print("-"); + Serial.print(myGPS.getMonth()); + Serial.print("-"); + Serial.print(myGPS.getDay()); + Serial.print(" "); + Serial.print(myGPS.getHour()); + Serial.print(":"); + Serial.print(myGPS.getMinute()); + Serial.print(":"); + Serial.print(myGPS.getSecond()); + + Serial.print(" Time is "); + if (myGPS.getTimeValid() == false) + { + Serial.print("not "); + } + Serial.print("valid Date is "); + if (myGPS.getDateValid() == false) + { + Serial.print("not "); + } + Serial.print("valid"); + + Serial.println(); + } +} diff --git a/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino b/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino new file mode 100644 index 0000000..f349364 --- /dev/null +++ b/examples/Example16_Nanosecond_MaxOutput/Example16_Nanosecond_MaxOutput.ino @@ -0,0 +1,109 @@ +/* + Getting time and date using u-blox commands + By: davidallenmann + SparkFun Electronics + Date: April 16th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a u-blox module for the current time and date. We also + turn off the NMEA output on the I2C port. This decreases the amount of I2C traffic + dramatically. + + Note: you will need to set your Serial Monitor to 500000 Baud to see the output + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(500000); //Increase serial speed to maximize + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + Wire.setClock(400000); // Increase I2C clock speed to 400kHz + + //myGPS.enableDebugging(); //Uncomment this line to enable debug messages over Serial + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + //myGPS.saveConfiguration(); //Optional: Save the current settings to flash and BBR + + myGPS.setNavigationFrequency(5); //Set output to 5 times a second + + byte rate = myGPS.getNavigationFrequency(); //Get the update rate of this module + Serial.print("Current update rate: "); + Serial.println(rate); +} + +void loop() +{ + // Calling getPVT returns true if there actually is a fresh navigation solution available. + if (myGPS.getPVT()) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.print(" "); + Serial.print(myGPS.getYear()); + Serial.print("-"); + Serial.print(myGPS.getMonth()); + Serial.print("-"); + Serial.print(myGPS.getDay()); + Serial.print(" "); + Serial.print(myGPS.getHour()); + Serial.print(":"); + Serial.print(myGPS.getMinute()); + Serial.print(":"); + Serial.print(myGPS.getSecond()); + 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 new file mode 100644 index 0000000..b644985 --- /dev/null +++ b/examples/Example16_PartialSecond_MaxOutput/Example16_PartialSecond_MaxOutput.ino @@ -0,0 +1,118 @@ +/* + Getting time and date using u-blox commands + By: Nathan Seidle + SparkFun Electronics + Date: April 16th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to use the Millisecond and Nanosecond output as well as increase the + I2C speed (100 to 400kHz), and serial output (115200 to 500kbps). + + Note: you will need to set your Serial Monitor to 500000 Baud to see the output + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(500000); //Increase serial speed to maximize + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + Wire.setClock(400000); // Increase I2C clock speed to 400kHz + + //myGPS.enableDebugging(); //Uncomment this line to enable debug messages over Serial + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + // Note: not all u-blox modules can output solutions at 10Hz - or not while tracking all satellite constellations + // If the rate drops back to 1Hz, you're asking too much of your module + 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(); //Optional: Save the current settings to flash and BBR +} + +void loop() +{ + // Calling getPVT returns true if there actually is a fresh navigation solution available. + if (myGPS.getPVT()) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.print(" "); + Serial.print(myGPS.getYear()); + Serial.print("-"); + Serial.print(myGPS.getMonth()); + Serial.print("-"); + Serial.print(myGPS.getDay()); + Serial.print(" "); + Serial.print(myGPS.getHour()); + Serial.print(":"); + Serial.print(myGPS.getMinute()); + Serial.print(":"); + Serial.print(myGPS.getSecond()); + Serial.print("."); + //Pretty print leading zeros + int mseconds = myGPS.getMillisecond(); + if (mseconds < 100) + Serial.print("0"); + if (mseconds < 10) + Serial.print("0"); + Serial.print(mseconds); + + Serial.print(" nanoSeconds: "); + Serial.print(myGPS.getNanosecond()); + + Serial.println(); + } +} diff --git a/examples/Example17_Geofence/Example17_Geofence.ino b/examples/Example17_Geofence/Example17_Geofence.ino new file mode 100644 index 0000000..36ad944 --- /dev/null +++ b/examples/Example17_Geofence/Example17_Geofence.ino @@ -0,0 +1,169 @@ +/* + u-blox M8 geofence example + + Written by Paul Clark (PaulZC) + 10th December 2019 + + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example demonstrates how to use the addGeofence and getGeofenceState functions + + 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/15210 + ZOE-M8Q: https://www.sparkfun.com/products/15193 + + This example powers up the GNSS and reads the fix. + Once a valid 3D fix has been found, the code reads the latitude and longitude. + The code then sets four geofences around that position with a radii of 5m, 10m, 15m and 20m with 95% confidence. + The code then monitors the geofence status. + The LED will be illuminated if you are inside the _combined_ geofence (i.e. within the 20m radius). + + This code has been tested on the ZOE-M8Q. +*/ + +#define LED LED_BUILTIN // Change this if your LED is on a different pin + +#include // Needed for I2C + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + pinMode(LED, OUTPUT); + + // Set up the I2C pins + Wire.begin(); + + // Start the console serial port + Serial.begin(115200); + while (!Serial); // Wait for the user to open the serial monitor + delay(100); + Serial.println(); + Serial.println(); + Serial.println(F("u-blox M8 geofence example")); + Serial.println(); + Serial.println(); + + delay(1000); // Let the GNSS power up + + if (myGPS.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. Freezing.")); + while (1); + } + + //myGPS.enableDebugging(); // Enable debug messages + myGPS.setI2COutput(COM_TYPE_UBX); // Limit I2C output to UBX (disable the NMEA noise) + + Serial.println(F("Waiting for a 3D fix...")); + + byte fixType = 0; + + while (fixType < 3) + { + fixType = myGPS.getFixType(); // Get the fix type + Serial.print(F("Fix: ")); // Print it + Serial.print(fixType); + if(fixType == 0) Serial.print(F(" = No fix")); + else if(fixType == 1) Serial.print(F(" = Dead reckoning")); + else if(fixType == 2) Serial.print(F(" = 2D")); + else if(fixType == 3) Serial.print(F(" = 3D")); + else if(fixType == 4) Serial.print(F(" = GNSS + Dead reckoning")); + else if(fixType == 5) Serial.print(F(" = Time only")); + Serial.println(); + delay(1000); + } + + Serial.println(F("3D fix found!")); + + long latitude = myGPS.getLatitude(); // Get the latitude in degrees * 10^-7 + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); // Get the longitude in degrees * 10^-7 + Serial.print(F(" Long: ")); + Serial.println(longitude); + + uint32_t radius = 500; // Set the radius to 5m (radius is in m * 10^-2 i.e. cm) + + byte confidence = 2; // Set the confidence level: 0=none, 1=68%, 2=95%, 3=99.7%, 4=99.99% + + // Call clearGeofences() to clear all existing geofences. + Serial.print(F("Clearing any existing geofences. clearGeofences returned: ")); + Serial.println(myGPS.clearGeofences()); + + // It is possible to define up to four geofences. + // Call addGeofence up to four times to define them. + Serial.println(F("Setting the geofences:")); + + Serial.print(F("addGeofence for geofence 1 returned: ")); + Serial.println(myGPS.addGeofence(latitude, longitude, radius, confidence)); + + radius = 1000; // 10m + Serial.print(F("addGeofence for geofence 2 returned: ")); + Serial.println(myGPS.addGeofence(latitude, longitude, radius, confidence)); + + radius = 1500; // 15m + Serial.print(F("addGeofence for geofence 3 returned: ")); + Serial.println(myGPS.addGeofence(latitude, longitude, radius, confidence)); + + radius = 2000; // 20m + Serial.print(F("addGeofence for geofence 4 returned: ")); + Serial.println(myGPS.addGeofence(latitude, longitude, radius, confidence)); +} + +void loop() +{ + geofenceState currentGeofenceState; // Create storage for the geofence state + + boolean result = myGPS.getGeofenceState(currentGeofenceState); + + Serial.print(F("getGeofenceState returned: ")); // Print the combined state + Serial.print(result); // Get the geofence state + + if (!result) // If getGeofenceState did not return true + { + Serial.println(F(".")); // Tidy up + return; // and go round the loop again + } + + Serial.print(F(". status is: ")); // Print the status + Serial.print(currentGeofenceState.status); + + Serial.print(F(". numFences is: ")); // Print the numFences + Serial.print(currentGeofenceState.numFences); + + Serial.print(F(". combState is: ")); // Print the combined state + Serial.print(currentGeofenceState.combState); + + if (currentGeofenceState.combState == 0) + { + Serial.print(F(" = Unknown")); + digitalWrite(LED, LOW); + } + if (currentGeofenceState.combState == 1) + { + Serial.print(F(" = Inside")); + digitalWrite(LED, HIGH); + } + else if (currentGeofenceState.combState == 2) + { + Serial.print(F(" = Outside")); + digitalWrite(LED, LOW); + } + + Serial.print(F(". The individual states are: ")); // Print the state of each geofence + for(int i = 0; i < currentGeofenceState.numFences; i++) + { + if (i > 0) Serial.print(F(",")); + Serial.print(currentGeofenceState.states[i]); + } + Serial.println(); + + delay(1000); +} diff --git a/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino b/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino new file mode 100644 index 0000000..32957b8 --- /dev/null +++ b/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino @@ -0,0 +1,163 @@ +/* + Power Save Mode + By: Paul Clark (PaulZC) + Date: April 22nd, 2020 + + Based extensively on Example3_GetPosition + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to put the u-blox module into power save mode and then + query its lat/long/altitude. We also turn off the NMEA output on the I2C port. + This decreases the amount of I2C traffic dramatically. + + ** When it is able to ** the module will reduce its current draw. + For the ZOE-M8Q with a passive antenna, you should see the current drop + from (approx.) 25-28mA to (approx.) 9mA when power save mode kicks in. + + Note: this will fail on the ZED (protocol version >= 27) as UBX-CFG-RXM is not supported + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 10,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + //myGPS.saveConfiguration(); //Optional: Uncomment this line to save the current settings to flash and BBR + + Serial.println("Power save example."); + Serial.println("1) Enable power saving"); + Serial.println("2) Disable power saving"); +} + +void loop() +{ + if (Serial.available()) + { + byte incoming = Serial.read(); + + if (incoming == '1') + { + // Put the GNSS into power save mode + // (If you want to disable power save mode, call myGPS.powerSaveMode(false) instead) + // This will fail on the ZED (protocol version >= 27) as UBX-CFG-RXM is not supported + if (myGPS.powerSaveMode()) // Defaults to true + Serial.println(F("Power Save Mode enabled.")); + else + Serial.println(F("***!!! Power Save Mode FAILED !!!***")); + } + else if (incoming == '2') + { + //Go to normal power mode (not power saving mode) + if (myGPS.powerSaveMode(false)) + Serial.println(F("Power Save Mode disabled.")); + else + Serial.println(F("***!!! Power Save Disable FAILED !!!***")); + } + + // Read and print the new low power mode + uint8_t lowPowerMode = myGPS.getPowerSaveMode(); + if (lowPowerMode == 255) + { + Serial.println(F("***!!! getPowerSaveMode FAILED !!!***")); + } + else + { + Serial.print(F("The low power mode is: ")); + Serial.print(lowPowerMode); + if (lowPowerMode == 0) + { + Serial.println(F(" (Continuous)")); + } + else if (lowPowerMode == 1) + { + Serial.println(F(" (Power Save)")); + } + else if (lowPowerMode == 4) + { + Serial.println(F(" (Continuous)")); + } + else + { + Serial.println(F(" (Unknown!)")); + } + } + } + + //Query module every 10 seconds so it is easier to monitor the current draw + if (millis() - lastTime > 10000) + { + lastTime = millis(); //Update the timer + + byte fixType = myGPS.getFixType(); // Get the fix type + Serial.print(F("Fix: ")); + Serial.print(fixType); + if (fixType == 0) + Serial.print(F("(No fix)")); + else if (fixType == 1) + Serial.print(F("(Dead reckoning)")); + else if (fixType == 2) + Serial.print(F("(2D)")); + else if (fixType == 3) + Serial.print(F("(3D)")); + else if (fixType == 4) + Serial.print(F("(GNSS + Dead reckoning)")); + + long latitude = myGPS.getLatitude(); + Serial.print(F(" Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + Serial.println(); + } +} diff --git a/examples/Example19_DynamicModel/Example19_DynamicModel.ino b/examples/Example19_DynamicModel/Example19_DynamicModel.ino new file mode 100644 index 0000000..9459d6d --- /dev/null +++ b/examples/Example19_DynamicModel/Example19_DynamicModel.ino @@ -0,0 +1,117 @@ +/* + Set Dynamic Model + By: Paul Clark (PaulZC) + Date: April 22nd, 2020 + + Based extensively on Example3_GetPosition + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to change the u-blox module's dynamic platform model and then + query its lat/long/altitude. We also turn off the NMEA output on the I2C port. + This decreases the amount of I2C traffic dramatically. + + Possible values for the dynamic model are: PORTABLE, STATIONARY, PEDESTRIAN, AUTOMOTIVE, + SEA, AIRBORNE1g, AIRBORNE2g, AIRBORNE4g, WRIST, BIKE + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 10,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + // If we are going to change the dynamic platform model, let's do it here. + // Possible values are: + // PORTABLE, STATIONARY, PEDESTRIAN, AUTOMOTIVE, SEA, AIRBORNE1g, AIRBORNE2g, AIRBORNE4g, WRIST, BIKE + + if (myGPS.setDynamicModel(DYN_MODEL_PORTABLE) == false) // Set the dynamic model to PORTABLE + { + Serial.println(F("***!!! Warning: setDynamicModel failed !!!***")); + } + else + { + Serial.println(F("Dynamic platform model changed successfully!")); + } + + // Let's read the new dynamic model to see if it worked + uint8_t newDynamicModel = myGPS.getDynamicModel(); + if (newDynamicModel == DYN_MODEL_UNKNOWN) + { + Serial.println(F("***!!! Warning: getDynamicModel failed !!!***")); + } + else + { + Serial.print(F("The new dynamic model is: ")); + Serial.println(newDynamicModel); + } + + //myGPS.saveConfigSelective(VAL_CFG_SUBSEC_NAVCONF); //Uncomment this line to save only the NAV settings to flash and BBR +} + +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 > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + Serial.println(); + } +} diff --git a/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino b/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino new file mode 100644 index 0000000..8798986 --- /dev/null +++ b/examples/Example1_BasicNMEARead/Example1_BasicNMEARead.ino @@ -0,0 +1,51 @@ +/* + Read NMEA sentences over I2C using u-blox module SAM-M8Q, NEO-M8P, ZED-F9P, etc + By: Nathan Seidle + SparkFun Electronics + Date: August 22nd, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example reads the NMEA setences from the u-blox module over I2c and outputs + them to the serial port + + 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 "SparkFun_Ublox_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_Ublox_GPS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + //This will pipe all NMEA sentences to the serial port so we can see them + myGPS.setNMEAOutputPort(Serial); +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + delay(250); //Don't pound too hard on the I2C bus +} diff --git a/examples/Example20_SendCustomCommand/Example20_SendCustomCommand.ino b/examples/Example20_SendCustomCommand/Example20_SendCustomCommand.ino new file mode 100644 index 0000000..c6b016e --- /dev/null +++ b/examples/Example20_SendCustomCommand/Example20_SendCustomCommand.ino @@ -0,0 +1,166 @@ +/* + Send Custom Command + By: Paul Clark (PaulZC) + Date: April 20th, 2020 + + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how you can create and send a custom UBX packet + using the SparkFun u-blox library. + + Previously it was possible to create and send a custom packet + through the library but it would always appear to timeout as + some of the internal functions referred to the internal private + struct packetCfg. + The most recent version of the library allows sendCommand to + use a custom packet as if it were packetCfg and so: + - sendCommand will return a sfe_ublox_status_e enum as if + it had been called from within the library + - the custom packet will be updated with data returned by the module + (previously this was not possible from outside the library) + + 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 +*/ + +#define NAV_RATE 5 // The new navigation rate in Hz (measurements per second) + +#include //Needed for I2C to GNSS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); // You may need to increase this for high navigation rates! + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + // Let's configure the module's navigation rate as if we were using setNavigationFrequency + + // Let's create our custom packet + uint8_t customPayload[MAX_PAYLOAD_SIZE]; // This array holds the payload data 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_RATE; // 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 navigation rate. The results will be loaded into customCfg. + if (myGPS.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) + ; + } + + // Referring to the message definition for UBX-CFG-RATE we see that the measurement rate + // is stored in payload bytes 0 and 1 as a uint16_t in LSB-first (little endian) format + + uint16_t rate = (customPayload[1] << 8) | customPayload[0]; // Extract the current rate (ms) + float f_rate = 1000.0 / ((float)rate); // Convert the navigation rate to Hz (measurements per second) + + // Print the current measurement rate + Serial.print(F("The current measurement rate is: ")); + Serial.println(f_rate, 1); + + // Let's change it + rate = 1000 / NAV_RATE; // Load the new value into rate + customPayload[0] = rate & 0xFF; // Store it in the payload + customPayload[1] = rate >> 8; + + // Print the new measurement rate + Serial.print(F("The new measurement rate will be: ")); + Serial.println(NAV_RATE); + + // We don't need to update customCfg.len as it will have been set to 6 + // when sendCommand read the data + + // Now we write the custom packet back again to change the setting + if (myGPS.sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_SENT) // This time we are only expecting an ACK + { + Serial.println(F("sendCommand (set) failed! Freezing.")); + while (1) + ; + } + else + { + Serial.println(F("Navigation rate updated. Here we go...")); + } + + myGPS.setAutoPVT(true); // Enable AutoPVT. The module will generate measurements automatically without being polled. + + //myGPS.saveConfigSelective(VAL_CFG_SUBSEC_NAVCONF); //Uncomment this line to save only the NAV settings to flash and BBR +} + +void loop() +{ + //Query the module as fast as possible + int32_t latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + int32_t longitude = myGPS.getLongitude(); + Serial.print(F(" Lon: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + int32_t altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + uint16_t milliseconds = myGPS.getMillisecond(); + Serial.print(F(" Milliseconds: ")); + Serial.print(altitude); + Serial.println(); +} diff --git a/examples/Example21_ModuleInfo/Example21_ModuleInfo.ino b/examples/Example21_ModuleInfo/Example21_ModuleInfo.ino new file mode 100644 index 0000000..f5f8178 --- /dev/null +++ b/examples/Example21_ModuleInfo/Example21_ModuleInfo.ino @@ -0,0 +1,183 @@ +/* + Module Info - extracts and prints the full module information from UBX_MON_VER + using a custom command. + By: @mayopan + Date: May 9th, 2020 + + Based on: + Send Custom Command + By: Paul Clark (PaulZC) + Date: April 20th, 2020 + + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Previously it was possible to create and send a custom packet + through the library but it would always appear to timeout as + some of the internal functions referred to the internal private + struct packetCfg. + The most recent version of the library allows sendCommand to + use a custom packet as if it were packetCfg and so: + - sendCommand will return a sfe_ublox_status_e enum as if + it had been called from within the library + - the custom packet will be updated with data returned by the module + (previously this was not possible from outside the library) + + 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 + +#define MAX_PAYLOAD_SIZE 384 // Override MAX_PAYLOAD_SIZE for getModuleInfo which can return up to 348 bytes + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS + +// Extend the class for getModuleInfo +class SFE_UBLOX_GPS_ADD : public SFE_UBLOX_GPS +{ +public: + boolean getModuleInfo(uint16_t maxWait = 1100); //Queries module, texts + + struct minfoStructure // Structure to hold the module info (uses 341 bytes of RAM) + { + char swVersion[30]; + char hwVersion[10]; + uint8_t extensionNo = 0; + char extension[10][30]; + } minfo; +}; + +SFE_UBLOX_GPS_ADD myGPS; + +void setup() +{ + Serial.begin(115200); // You may need to increase this for high navigation rates! + while (!Serial) + ; //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + Serial.print(F("Polling module info")); + if (myGPS.getModuleInfo(1100) == false) // Try to get the module info + { + Serial.print(F("getModuleInfo failed! Freezing...")); + while (1) + ; + } + + Serial.println(); + Serial.println(); + Serial.println(F("Module Info : ")); + Serial.print(F("Soft version: ")); + Serial.println(myGPS.minfo.swVersion); + Serial.print(F("Hard version: ")); + Serial.println(myGPS.minfo.hwVersion); + Serial.print(F("Extensions:")); + Serial.println(myGPS.minfo.extensionNo); + for (int i = 0; i < myGPS.minfo.extensionNo; i++) + { + Serial.print(" "); + Serial.println(myGPS.minfo.extension[i]); + } + Serial.println(); + Serial.println(F("Done!")); +} + +void loop() +{ +} + +boolean SFE_UBLOX_GPS_ADD::getModuleInfo(uint16_t maxWait) +{ + myGPS.minfo.hwVersion[0] = 0; + myGPS.minfo.swVersion[0] = 0; + for (int i = 0; i < 10; i++) + myGPS.minfo.extension[i][0] = 0; + myGPS.minfo.extensionNo = 0; + + // Let's create our custom packet + uint8_t customPayload[MAX_PAYLOAD_SIZE]; // This array holds the payload data 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 module information can be read using the UBX-MON-VER message. So let's load our + // custom packet with the correct information so we can read (poll / get) the module information. + + customCfg.cls = UBX_CLASS_MON; // This is the message Class + customCfg.id = UBX_MON_VER; // 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) + + // Now let's send the command. The module info is returned in customPayload + + if (sendCommand(&customCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + // Now let's extract the module info from customPayload + + uint16_t position = 0; + for (int i = 0; i < 30; i++) + { + minfo.swVersion[i] = customPayload[position]; + position++; + } + for (int i = 0; i < 10; i++) + { + minfo.hwVersion[i] = customPayload[position]; + position++; + } + + while (customCfg.len >= position + 30) + { + for (int i = 0; i < 30; i++) + { + minfo.extension[minfo.extensionNo][i] = customPayload[position]; + position++; + } + minfo.extensionNo++; + if (minfo.extensionNo > 9) + break; + } + + return (true); //Success! +} diff --git a/examples/Example22_PowerOff/Example22_PowerOff.ino b/examples/Example22_PowerOff/Example22_PowerOff.ino new file mode 100644 index 0000000..e091f20 --- /dev/null +++ b/examples/Example22_PowerOff/Example22_PowerOff.ino @@ -0,0 +1,88 @@ +/* + Powering off a ublox GNSS module + By: bjorn + unsurv.org + Date: July 20th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows you how to turn off the ublox module to lower the power consumption. + There are two functions: one just specifies a duration in milliseconds the other also specifies a pin on the GNSS device to wake it up with. + By driving a voltage from LOW to HIGH or HIGH to LOW on the chosen module pin you wake the device back up. + Note: Doing so on the INT0 pin when using the regular powerOff(durationInMs) function will wake the device anyway. (tested on SAM-M8Q) + Note: While powered off, you should not query the device for data or it might wake up. This can be used to wake the device but is not recommended. + Works best when also putting your microcontroller to sleep. + + 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. + To force the device to wake up you need to connect to a pin (for example INT0) seperately on the module. + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +// define a digital pin capable of driving HIGH and LOW +#define WAKEUP_PIN 5 + +// Possible GNSS interrupt pins for powerOffWithInterrupt are: +// VAL_RXM_PMREQ_WAKEUPSOURCE_UARTRX = uartrx +// VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 = extint0 (default) +// VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT1 = extint1 +// VAL_RXM_PMREQ_WAKEUPSOURCE_SPICS = spics +// These values can be or'd (|) together to enable interrupts on multiple pins + +void wakeUp() { + + Serial.print("-- waking up module via pin " + String(WAKEUP_PIN)); + Serial.println(" on your microcontroller --"); + + digitalWrite(WAKEUP_PIN, LOW); + delay(1000); + digitalWrite(WAKEUP_PIN, HIGH); + delay(1000); + digitalWrite(WAKEUP_PIN, LOW); +} + + +void setup() { + + pinMode(WAKEUP_PIN, OUTPUT); + digitalWrite(WAKEUP_PIN, LOW); + + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Enable debug messages + + if (myGPS.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. Freezing.")); + while (1); + } + + // Powering off for 20s, you should see the power consumption drop. + Serial.println("-- Powering off module for 20s --"); + + myGPS.powerOff(20000); + //myGPS.powerOffWithInterrupt(20000, VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0); + + delay(10000); + + // After 10 seconds wake the device via the specified pin on your microcontroller and module. + wakeUp(); +} + +void loop() { + //Do nothing +} diff --git a/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino b/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino new file mode 100644 index 0000000..224f149 --- /dev/null +++ b/examples/Example2_NMEAParsing/Example2_NMEAParsing.ino @@ -0,0 +1,83 @@ +/* + Read NMEA sentences over I2C using u-blox module SAM-M8Q, NEO-M8P, etc + By: Nathan Seidle + SparkFun Electronics + Date: August 22nd, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example reads the NMEA characters over I2C and pipes them to MicroNMEA + This example will output your current long/lat and satellites in view + + 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 + + For more MicroNMEA info see https://github.com/stevemarple/MicroNMEA + + 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 + Go outside! Wait ~25 seconds and you should see your lat/long +*/ + +#include //Needed for I2C to GNSS + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +#include //http://librarymanager/All#MicroNMEA +char nmeaBuffer[100]; +MicroNMEA nmea(nmeaBuffer, sizeof(nmeaBuffer)); + +void setup() +{ + Serial.begin(115200); + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.begin() == false) + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + if(nmea.isValid() == true) + { + long latitude_mdeg = nmea.getLatitude(); + long longitude_mdeg = nmea.getLongitude(); + + Serial.print("Latitude (deg): "); + Serial.println(latitude_mdeg / 1000000., 6); + Serial.print("Longitude (deg): "); + Serial.println(longitude_mdeg / 1000000., 6); + } + else + { + Serial.print("No Fix - "); + Serial.print("Num. satellites: "); + Serial.println(nmea.getNumSatellites()); + } + + delay(250); //Don't pound too hard on the I2C bus +} + +//This function gets called from the SparkFun u-blox Arduino Library +//As each NMEA character comes in you can specify what to do with it +//Useful for passing to other libraries like tinyGPS, MicroNMEA, or even +//a buffer, radio, etc. +void SFE_UBLOX_GPS::processNMEA(char incoming) +{ + //Take the incoming char from the u-blox I2C port and pass it on to the MicroNMEA lib + //for sentence cracking + nmea.process(incoming); +} diff --git a/examples/Example3_GetPosition/Example3_GetPosition.ino b/examples/Example3_GetPosition/Example3_GetPosition.ino new file mode 100644 index 0000000..720c856 --- /dev/null +++ b/examples/Example3_GetPosition/Example3_GetPosition.ino @@ -0,0 +1,84 @@ +/* + Reading lat and long via UBX binary commands - no more NMEA parsing! + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a u-blox module for its lat/long/altitude. We also + turn off the NMEA output on the I2C port. This decreases the amount of I2C traffic + dramatically. + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 10,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR +} + +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 > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + byte SIV = myGPS.getSIV(); + Serial.print(F(" SIV: ")); + Serial.print(SIV); + + Serial.println(); + } +} diff --git a/examples/Example4_FixType/Example4_FixType.ino b/examples/Example4_FixType/Example4_FixType.ino new file mode 100644 index 0000000..1e8540f --- /dev/null +++ b/examples/Example4_FixType/Example4_FixType.ino @@ -0,0 +1,96 @@ +/* + Get fix type and RTK fix type if available + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a u-blox module for fix type and RTK fix type. + The fix type is as follows: + 0 = no fix + 1 = dead reckoning (requires external sensors) + 2 = 2D (not quite enough satellites in view) + 3 = 3D (the standard fix) + 4 = GNSS + dead reckoning (requires external sensors) + 5 = Time fix only + + Additionally, if we are doing RTK, we can figure out if we have a floating + RTK solution or if we have been able to resolve a fixec solution (better precision). + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + //Wire.setClock(400000); //Optional. Increase I2C clock speed to 400kHz. + + if (myGPS.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. Freezing.")); + while (1); + } +} + +void loop() +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + + byte fixType = myGPS.getFixType(); + Serial.print(F(" Fix: ")); + if(fixType == 0) Serial.print(F("No fix")); + else if(fixType == 1) Serial.print(F("Dead reckoning")); + else if(fixType == 2) Serial.print(F("2D")); + else if(fixType == 3) Serial.print(F("3D")); + else if(fixType == 4) Serial.print(F("GNSS + Dead reckoning")); + else if(fixType == 5) Serial.print(F("Time only")); + + byte RTK = myGPS.getCarrierSolutionType(); + Serial.print(" RTK: "); + Serial.print(RTK); + if (RTK == 0) Serial.print(F(" (No solution)")); + else if (RTK == 1) Serial.print(F(" (High precision floating fix)")); + else if (RTK == 2) Serial.print(F(" (High precision fix)")); + + Serial.println(); + } + +} diff --git a/examples/Example5_SpeedHeadingPrecision/Example5_SpeedHeadingPrecision.ino b/examples/Example5_SpeedHeadingPrecision/Example5_SpeedHeadingPrecision.ino new file mode 100644 index 0000000..758c70e --- /dev/null +++ b/examples/Example5_SpeedHeadingPrecision/Example5_SpeedHeadingPrecision.ino @@ -0,0 +1,83 @@ +/* + Get Speed/Heading and dilution of precision via UBX binary commands - no more NMEA parsing! + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a u-blox module for its lat/long/altitude. + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 1,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1); + } +} + +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 > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + + long speed = myGPS.getGroundSpeed(); + Serial.print(F(" Speed: ")); + Serial.print(speed); + Serial.print(F(" (mm/s)")); + + long heading = myGPS.getHeading(); + Serial.print(F(" Heading: ")); + Serial.print(heading); + Serial.print(F(" (degrees * 10^-5)")); + + int pDOP = myGPS.getPDOP(); + Serial.print(F(" pDOP: ")); + Serial.print(pDOP / 100.0, 2); // Convert pDOP scaling from 0.01 to 1 + + Serial.println(); + } +} diff --git a/examples/Example6_EnableNMEASentences/Example6_EnableNMEASentences.ino b/examples/Example6_EnableNMEASentences/Example6_EnableNMEASentences.ino new file mode 100644 index 0000000..868ae6b --- /dev/null +++ b/examples/Example6_EnableNMEASentences/Example6_EnableNMEASentences.ino @@ -0,0 +1,79 @@ +/* + Turn on/off various NMEA sentences. + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to turn on/off the NMEA sentences being output + over UART1. We use the I2C interface on the u-blox module for configuration + but you won't see any output from this sketch. You'll need to hook up + a Serial Basic or other USB to Serial device to UART1 on your u-blox module + to see the output. + + This example turns off all sentences except for the GPGGA and GPVTG sentences. + + 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 RedBoard + 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 + Hookup a Serial Basic (https://www.sparkfun.com/products/15096) to UART1 on the u-blox module. Open a terminal at 57600bps + and see GPGGA and GPVTG sentences. +*/ +#include //Needed for I2C to GNSS + +#include "SparkFun_Ublox_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +unsigned long lastGNSSsend = 0; + +void setup() +{ + Serial.begin(115200); // Serial debug output over USB visible from Arduino IDE + Serial.println("Example showing how to enable/disable certain NMEA sentences"); + + Wire.begin(); + + if (myGPS.begin() == false) + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing.")); + while (1) + ; + } + + //Disable or enable various NMEA sentences over the UART1 interface + myGPS.disableNMEAMessage(UBX_NMEA_GLL, COM_PORT_UART1); //Several of these are on by default on ublox board so let's disable them + myGPS.disableNMEAMessage(UBX_NMEA_GSA, COM_PORT_UART1); + myGPS.disableNMEAMessage(UBX_NMEA_GSV, COM_PORT_UART1); + myGPS.disableNMEAMessage(UBX_NMEA_RMC, COM_PORT_UART1); + myGPS.enableNMEAMessage(UBX_NMEA_GGA, COM_PORT_UART1); //Only leaving GGA & VTG enabled at current navigation rate + myGPS.enableNMEAMessage(UBX_NMEA_VTG, COM_PORT_UART1); + + //Here's the advanced configure method + //Some of the other examples in this library enable the PVT message so let's disable it + myGPS.configureMessage(UBX_CLASS_NAV, UBX_NAV_PVT, COM_PORT_UART1, 0); //Message Class, ID, and port we want to configure, sendRate of 0 (disable). + + myGPS.setUART1Output(COM_TYPE_NMEA); //Turn off UBX and RTCM sentences on the UART1 interface + + myGPS.setSerialRate(57600); //Set UART1 to 57600bps. + + //myGPS.saveConfiguration(); //Optional: Save these settings to NVM + + Serial.println(F("Messages configured. NMEA now being output over the UART1 port on the u-blox module at 57600bps.")); +} + +void loop() +{ + if (millis() - lastGNSSsend > 200) + { + myGPS.checkUblox(); //See if new data is available, but we don't want to get NMEA here. Go check UART1. + lastGNSSsend = millis(); + } +} diff --git a/examples/Example7_OutputRate/Example7_OutputRate.ino b/examples/Example7_OutputRate/Example7_OutputRate.ino new file mode 100644 index 0000000..9550ac0 --- /dev/null +++ b/examples/Example7_OutputRate/Example7_OutputRate.ino @@ -0,0 +1,94 @@ +/* + Set update rate to 10Hz + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to increase the output of the module from 1Hz to 4Hz. + The max output rate various from model to model. RTFM! But you cannot do harm + to the module. + + We also disable NMEA output on the I2C bus and use only UBX. This dramatically + decreases the amount of data that needs to be transmitted. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +unsigned long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. +unsigned long startTime = 0; //Used to calc the actual update rate. +unsigned long updateCount = 0; //Used to calc the actual update rate. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + // Increase I2C clock speed to 400kHz to cope with the high navigation rate + // (We normally recommend running the bus at 100kHz) + Wire.setClock(400000); + + if (myGPS.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. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.setNavigationFrequency(5); //Set output to 5 times a second + + uint8_t rate = myGPS.getNavigationFrequency(); //Get the update rate of this module + Serial.print("Current update rate: "); + Serial.println(rate); + + startTime = millis(); +} + +void loop() +{ + //Query module every 25 ms. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available. This is defined + //by the update freq. + if (millis() - lastTime > 25) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + + updateCount++; + + //Calculate the actual update rate based on the sketch start time and the + //number of updates we've received. + Serial.print(F(" Rate: ")); + Serial.print( updateCount / ((millis() - startTime) / 1000.0), 2); + Serial.print(F("Hz")); + + Serial.println(); + } +} diff --git a/examples/Example8_GetProtocolVersion/Example8_GetProtocolVersion.ino b/examples/Example8_GetProtocolVersion/Example8_GetProtocolVersion.ino new file mode 100644 index 0000000..b5d70a5 --- /dev/null +++ b/examples/Example8_GetProtocolVersion/Example8_GetProtocolVersion.ino @@ -0,0 +1,61 @@ +/* + Reading the protocol version of a u-blox module + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a u-blox module for its protocol version. + + Various modules have various protocol version. We've seen v18 up to v27. Depending + on the protocol version there are different commands available. This is a handy + way to predict which commands will or won't work. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1); + } + + Serial.print(F("Version: ")); + byte versionHigh = myGPS.getProtocolVersionHigh(); + Serial.print(versionHigh); + Serial.print("."); + byte versionLow = myGPS.getProtocolVersionLow(); + Serial.print(versionLow); +} + +void loop() +{ + //Do nothing +} diff --git a/examples/Example8_GetProtocolVersion_Serial/Example8_GetProtocolVersion_Serial.ino b/examples/Example8_GetProtocolVersion_Serial/Example8_GetProtocolVersion_Serial.ino new file mode 100644 index 0000000..3c13902 --- /dev/null +++ b/examples/Example8_GetProtocolVersion_Serial/Example8_GetProtocolVersion_Serial.ino @@ -0,0 +1,81 @@ +/* + Reading the protocol version of a u-blox module + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a u-blox module for its protocol version. + + Note: this may fail on boards like the UNO (ATmega328P) with modules like the ZED-F9P + because getProtocolVersion returns a lot of data - more than the UNO's serial buffer can hold + + Various modules have various protocol version. We've seen v18 up to v27. Depending + on the protocol version there are different commands available. This is a handy + way to predict which commands will or won't work. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 + +#define mySerial Serial1 // Uncomment this line to connect via Serial1 +// - or - +//SoftwareSerial mySerial(10, 11); // Uncomment this line to connect via SoftwareSerial(RX, TX). Connect pin 10 to GNSS TX pin. + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Serial.println("Trying 38400 baud"); + mySerial.begin(38400); + if (myGPS.begin(mySerial)) + { + Serial.println("GNSS connected at 38400 baud"); + } + else + { + Serial.println("Trying 9600 baud"); + mySerial.begin(9600); + if (myGPS.begin(mySerial)) + { + Serial.println("GNSS connected at 9600 baud"); + } + else + { + Serial.println("Could not connect to GNSS. Freezing..."); + while(1); // Do nothing more + } + } + + Serial.print(F("Version: ")); + byte versionHigh = myGPS.getProtocolVersionHigh(); + Serial.print(versionHigh); + Serial.print("."); + byte versionLow = myGPS.getProtocolVersionLow(); + Serial.print(versionLow); +} + +void loop() +{ + //Do nothing +} diff --git a/examples/Example9_ChangeI2CAddress/Example9_ChangeI2CAddress.ino b/examples/Example9_ChangeI2CAddress/Example9_ChangeI2CAddress.ino new file mode 100644 index 0000000..61a3cd5 --- /dev/null +++ b/examples/Example9_ChangeI2CAddress/Example9_ChangeI2CAddress.ino @@ -0,0 +1,116 @@ +/* + Change the I2C address of a u-blox module using I2C + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to change the I2C address of a u-blox module + + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Tracks the passing of 2000ms (2 seconds) + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + byte oldAddress = 0x42; //The default address for u-blox modules is 0x42 + byte newAddress = 0x3F; //Address you want to change to. Valid is 0x08 to 0x77. + + while (Serial.available()) Serial.read(); //Trash any incoming chars + Serial.print("Press a key to change address to 0x"); + Serial.println(newAddress, HEX); + while (Serial.available() == false) ; //Wait for user to send character + + //myGPS.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + if (myGPS.begin(Wire, oldAddress) == true) //Connect to the u-blox module using Wire port and the old address + { + Serial.print("GNSS found at address 0x"); + Serial.println(oldAddress, HEX); + + myGPS.setI2CAddress(newAddress); //Change I2C address of this device + //Device's I2C address is stored to memory and loaded on each power-on + + delay(2000); // Allow time for the change to take + + if (myGPS.begin(Wire, newAddress) == true) + { + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + + Serial.print("Address successfully changed to 0x"); + Serial.println(newAddress, HEX); + Serial.print("Now load another example sketch using .begin(Wire, 0x"); + Serial.print(newAddress, HEX); + Serial.println(") to use this GPS module"); + Serial.println("Freezing..."); + while (1); + } + } + + //Something went wrong, begin looking for the I2C device + Serial.println("Address change probably failed. Beginning an I2C scan."); + + Wire.begin(); +} + +void loop() { + + byte address; + int nDevices; + + Serial.println("Scanning..."); + + nDevices = 0; + for (address = 1; address < 127; address++ ) + { + Wire.beginTransmission(address); + byte error = Wire.endTransmission(); + + if (error == 0) + { + Serial.print("I2C device found at address 0x"); + if (address < 16) + Serial.print("0"); + Serial.print(address, HEX); + Serial.println(" !"); + + nDevices++; + } +// else if (error == 4) +// { +// Serial.print("Unknown error at address 0x"); +// if (address < 16) +// Serial.print("0"); +// Serial.println(address, HEX); +// } + } + + if (nDevices == 0) + Serial.println("No I2C devices found\n"); + else + Serial.println("done\n"); + + delay(5000); // wait 5 seconds for next scan +} diff --git a/examples/NEO-M8P-2/Example1_EnableRTCM/Example1_EnableRTCM.ino b/examples/NEO-M8P-2/Example1_EnableRTCM/Example1_EnableRTCM.ino new file mode 100644 index 0000000..308b434 --- /dev/null +++ b/examples/NEO-M8P-2/Example1_EnableRTCM/Example1_EnableRTCM.ino @@ -0,0 +1,77 @@ +/* + Send UBX binary commands to enable RTCM sentences on u-blox NEO-M8P module + By: Nathan Seidle + SparkFun Electronics + Date: September 7th, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example sends the command to enable the four RTCM messages needed for RTK. This + is the first part of a larger tutorial and example to setup an RTK base station. + These commands are only accepted by the NEO-M8P module. + + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while(!Serial); //Wait for user to open terminal + Serial.println(F("u-blox RTCM Enable Example")); + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.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. Freezing.")); + while (1); + } + + while(Serial.available()) Serial.read(); //Clear any latent chars in serial buffer + Serial.println(F("Press any key to send commands to enable RTCM 3.x")); + while(Serial.available() == 0) ; //Wait for user to press a key + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + + boolean response = true; + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1077, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1087, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_I2C, 10); //Enable message every 10 seconds + + if (response == true) + { + Serial.println(F("RTCM messages enabled")); + } + else + { + Serial.println(F("RTCM failed to enable. Are you sure you have an NEO-M8P?")); + while(1); //Freeze + } + + //RTCM is now enabled but we haven't done a 'survey-in' + //See example 4 for the full Base RTK setup +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + delay(250); //Don't pound too hard on the I2C bus +} diff --git a/examples/NEO-M8P-2/Example2_StartRTCMBase/Example2_StartRTCMBase.ino b/examples/NEO-M8P-2/Example2_StartRTCMBase/Example2_StartRTCMBase.ino new file mode 100644 index 0000000..bdccc45 --- /dev/null +++ b/examples/NEO-M8P-2/Example2_StartRTCMBase/Example2_StartRTCMBase.ino @@ -0,0 +1,164 @@ +/* + Note: compiles OK with v2.0 but is currently untested + + Send UBX binary commands to enable RTCM sentences on u-blox NEO-M8P-2 module + By: Nathan Seidle + SparkFun Electronics + Date: September 7th, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example does all steps to configure and enable a NEO-M8P-2 as a base station: + Begin Survey-In + Once we've achieved 2m accuracy and 300s have passed, survey is complete + Enable four RTCM messages + Begin outputting RTCM bytes + + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("u-blox NEO-M8P-2 base station example")); + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.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. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR + + while (Serial.available()) Serial.read(); //Clear any latent chars in serial buffer + Serial.println(F("Press any key to send commands to begin Survey-In")); + while (Serial.available() == 0) ; //Wait for user to press a key + + boolean response; + + //Check if Survey is in Progress before initiating one + // From v2.0, the data from getSurveyStatus (UBX-NAV-SVIN) is returned in UBX_NAV_SVIN_t packetUBXNAVSVIN + // Please see u-blox_structs.h for the full definition of UBX_NAV_SVIN_t + // You can either read the data from packetUBXNAVSVIN directly + // or can use the helper functions: getSurveyInActive; getSurveyInValid; getSurveyInObservationTime; and getSurveyInMeanAccuracy + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (request can take a long time) + + if (response == false) // Check if fresh data was received + { + Serial.println(F("Failed to get Survey In status. Freezing...")); + while (1); //Freeze + } + + if (myGPS.getSurveyInActive() == true) // Use the helper function + { + Serial.print(F("Survey already in progress.")); + } + else + { + //Start survey + response = myGPS.enableSurveyMode(300, 2.000); //Enable Survey in, 300 seconds, 2.0m + if (response == false) + { + Serial.println(F("Survey start failed. Freezing...")); + while (1); + } + Serial.println(F("Survey started. This will run until 300s has passed and less than 2m accuracy is achieved.")); + } + + while(Serial.available()) Serial.read(); //Clear buffer + + //Begin waiting for survey to complete + while (myGPS.getSurveyInValid() == false) // Call the helper function + { + if(Serial.available()) + { + byte incoming = Serial.read(); + if(incoming == 'x') + { + //Stop survey mode + response = myGPS.disableSurveyMode(); //Disable survey + Serial.println(F("Survey stopped")); + break; + } + } + + // From v2.0, the data from getSurveyStatus (UBX-NAV-SVIN) is returned in UBX_NAV_SVIN_t packetUBXNAVSVIN + // Please see u-blox_structs.h for the full definition of UBX_NAV_SVIN_t + // You can either read the data from packetUBXNAVSVIN directly + // or can use the helper functions: getSurveyInActive; getSurveyInValid; getSurveyInObservationTime; and getSurveyInMeanAccuracy + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (req can take a long time) + if (response == true) // Check if fresh data was received + { + Serial.print(F("Press x to end survey - ")); + Serial.print(F("Time elapsed: ")); + Serial.print((String)myGPS.getSurveyInObservationTime()); + + Serial.print(F(" Accuracy: ")); + Serial.print((String)myGPS.getSurveyInMeanAccuracy()); + Serial.println(); + } + else + { + Serial.println(F("SVIN request failed")); + } + + delay(1000); + } + Serial.println(F("Survey valid!")); + + response = true; + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1077, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1087, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_I2C, 10); //Enable message every 10 seconds + + if (response == true) + { + Serial.println(F("RTCM messages enabled")); + } + else + { + Serial.println(F("RTCM failed to enable. Are you sure you have an NEO-M8P?")); + while (1); //Freeze + } + + Serial.println(F("Base survey complete! RTCM now broadcasting.")); +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + delay(250); //Don't pound too hard on the I2C bus +} + +//This function gets called from the SparkFun u-blox Arduino Library. +//As each RTCM byte comes in you can specify what to do with it +//Useful for passing the RTCM correction data to a radio, Ntrip broadcaster, etc. +void SFE_UBLOX_GPS::processRTCM(uint8_t incoming) +{ + //Let's just pretty-print the HEX values for now + if (myGPS.rtcmFrameCounter % 16 == 0) Serial.println(); + Serial.print(F(" ")); + if (incoming < 0x10) Serial.print(F("0")); + Serial.print(incoming, HEX); +} diff --git a/examples/NEO-M8P-2/Example3_BaseWithLCD/Example3_BaseWithLCD.ino b/examples/NEO-M8P-2/Example3_BaseWithLCD/Example3_BaseWithLCD.ino new file mode 100644 index 0000000..2eb9598 --- /dev/null +++ b/examples/NEO-M8P-2/Example3_BaseWithLCD/Example3_BaseWithLCD.ino @@ -0,0 +1,195 @@ +/* + Note: compiles OK with v2.0 but is currently untested + + Send UBX binary commands to enable RTCM sentences on u-blox NEO-M8P-2 module + By: Nathan Seidle + SparkFun Electronics + Date: September 7th, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example does all steps to configure and enable a NEO-M8P-2 as a base station: + Begin Survey-In + Once we've achieved 2m accuracy and 300s have passed, survey is complete + Enable four RTCM messages + Begin outputting RTCM bytes + + 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 + Plug a SerLCD onto the Qwiic bus + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Watch the output on the LCD or open the serial monitor at 115200 baud to see the output +*/ + +#define STAT_LED 13 + +#include //Needed for I2C to GNSS + +#include "SparkFun_Ublox_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +#include //Click here to get the library: http://librarymanager/All#SparkFun_SerLCD +SerLCD lcd; // Initialize the library with default I2C address 0x72 + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("u-blox GNSS I2C Test")); + + Wire.begin(); + + pinMode(STAT_LED, OUTPUT); + digitalWrite(STAT_LED, LOW); + + lcd.begin(Wire); //Set up the LCD for Serial communication at 9600bps + lcd.setBacklight(0x4B0082); //indigo, a kind of dark purplish blue + lcd.clear(); + lcd.print(F("LCD Ready")); + + myGPS.begin(Wire); + if (myGPS.isConnected() == false) + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing.")); + lcd.setCursor(0, 1); + lcd.print(F("No GNSS detected")); + while (1); + } + + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + lcd.setCursor(0, 1); + lcd.print("GNSS Detected"); + + //Check if Survey is in Progress before initiating one + // From v2.0, the data from getSurveyStatus (UBX-NAV-SVIN) is returned in UBX_NAV_SVIN_t packetUBXNAVSVIN + // Please see u-blox_structs.h for the full definition of UBX_NAV_SVIN_t + // You can either read the data from packetUBXNAVSVIN directly + // or can use the helper functions: getSurveyInActive; getSurveyInValid; getSurveyInObservationTime; and getSurveyInMeanAccuracy + boolean response; + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (request can take a long time) + if (response == false) + { + Serial.println(F("Failed to get Survey In status")); + while (1); //Freeze + } + + if (myGPS.getSurveyInActive() == true) // Use the helper function + { + Serial.print(F("Survey already in progress.")); + lcd.setCursor(0, 2); + lcd.print(F("Survey already going")); + } + else + { + //Start survey + response = myGPS.enableSurveyMode(300, 2.000); //Enable Survey in, 300 seconds, 2.0m + if (response == false) + { + Serial.println(F("Survey start failed")); + lcd.setCursor(0, 3); + lcd.print(F("Survey start failed")); + while (1); + } + Serial.println(F("Survey started. This will run until 300s has passed and less than 2m accuracy is achieved.")); + } + + while (Serial.available()) Serial.read(); //Clear buffer + + lcd.clear(); + lcd.print(F("Survey in progress")); + + //Begin waiting for survey to complete + while (myGPS.getSurveyInValid() == false) // Call the helper function + { + if (Serial.available()) + { + byte incoming = Serial.read(); + if (incoming == 'x') + { + //Stop survey mode + response = myGPS.disableSurveyMode(); //Disable survey + Serial.println(F("Survey stopped")); + break; + } + } + + // From v2.0, the data from getSurveyStatus (UBX-NAV-SVIN) is returned in UBX_NAV_SVIN_t packetUBXNAVSVIN + // Please see u-blox_structs.h for the full definition of UBX_NAV_SVIN_t + // You can either read the data from packetUBXNAVSVIN directly + // or can use the helper functions: getSurveyInActive; getSurveyInValid; getSurveyInObservationTime; and getSurveyInMeanAccuracy + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (req can take a long time) + if (response == true) + { + Serial.print(F("Press x to end survey - ")); + Serial.print(F("Time elapsed: ")); + Serial.print((String)myGPS.getSurveyInObservationTime()); + + lcd.setCursor(0, 1); + lcd.print(F("Elapsed: ")); + lcd.print((String)myGPS.getSurveyInObservationTime()); + + Serial.print(F(" Accuracy: ")); + Serial.print((String)myGPS.getSurveyInMeanAccuracy()); + Serial.println(); + + lcd.setCursor(0, 2); + lcd.print(F("Accuracy: ")); + lcd.print((String)myGPS.getSurveyInMeanAccuracy()); + } + else + { + Serial.println(F("SVIN request failed")); + } + + delay(1000); + } + Serial.println(F("Survey valid!")); + + response = true; + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1077, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1087, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_I2C, 10); //Enable message every 10 seconds + + if (response == true) + { + Serial.println(F("RTCM messages enabled")); + } + else + { + Serial.println(F("RTCM failed to enable. Are you sure you have an NEO-M8P?")); + while (1); //Freeze + } + + Serial.println(F("Base survey complete! RTCM now broadcasting.")); + lcd.clear(); + lcd.print(F("Transmitting RTCM")); +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + //Do anything you want. Call checkUblox() every second. NEO-M8P-2 has TX buffer of 4k bytes. + + delay(250); //Don't pound too hard on the I2C bus +} + +//This function gets called from the SparkFun u-blox Arduino Library. +//As each RTCM byte comes in you can specify what to do with it +//Useful for passing the RTCM correction data to a radio, Ntrip broadcaster, etc. +void SFE_UBLOX_GPS::processRTCM(uint8_t incoming) +{ + //Let's just pretty-print the HEX values for now + if (myGPS.rtcmFrameCounter % 16 == 0) Serial.println(); + Serial.print(" "); + if (incoming < 0x10) Serial.print("0"); + Serial.print(incoming, HEX); +} diff --git a/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/Example1_GetPositionAndTime_Series_6_7.ino b/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/Example1_GetPositionAndTime_Series_6_7.ino new file mode 100644 index 0000000..bf7ffb2 --- /dev/null +++ b/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/Example1_GetPositionAndTime_Series_6_7.ino @@ -0,0 +1,109 @@ +/* + Reading lat, long and UTC time via UBX binary commands - no more NMEA parsing! + By: Paul Clark and Nathan Seidle + Using the library modifications provided by @blazczak and @geeksville + + SparkFun Electronics + Date: June 16th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a u-blox module for its lat/long/altitude. We also + turn off the NMEA output on the I2C port. This decreases the amount of I2C traffic + dramatically. + + Note: Long/lat are large numbers because they are * 10^7. To convert lat/long + to something google maps understands simply divide the numbers by 10,000,000. We + do this so that we don't have to use floating point numbers. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + SAM-M8Q: https://www.sparkfun.com/products/15106 + + Hardware Connections: + Plug a Qwiic cable into the GPS 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 GPS + +#include "SparkFun_Ublox_Arduino_Library_Series_6_7.h" +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.begin() == false) //Connect to the u-blox module using Wire port + { + Serial.println(F("u-blox GPS not detected at default I2C address. Please check wiring. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfiguration(); //Save the current settings to flash and BBR +} + +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 > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + Serial.print(F(" Time: ")); + + byte Hour = myGPS.getHour(); + if (Hour < 10) + { + Serial.print(F("0")); + } + Serial.print(Hour); + Serial.print(F(":")); + + byte Minute = myGPS.getMinute(); + if (Minute < 10) + { + Serial.print(F("0")); + } + Serial.print(Minute); + Serial.print(F(":")); + + byte Second = myGPS.getSecond(); + if (Second < 10) + { + Serial.print(F("0")); + } + Serial.print(Second); + + Serial.println(); + } +} diff --git a/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.cpp b/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.cpp new file mode 100644 index 0000000..bb3c7b0 --- /dev/null +++ b/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.cpp @@ -0,0 +1,3477 @@ +/* + This is a library written for the u-blox ZED-F9P and NEO-M8P-2 + + Updated: June 16th, 2020 + + This copy includes changes by @blazczak and @geeksville to + provide support for the older series 6 and 7 modules. + + Disclaimer: SparkFun has not verified this copy of the library on either series 6 or 7. + It should work, it looks like it will work, but we have no way of confirming this. + We cannot guarantee that it will work reliably in your application. + + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Original library written by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + + This library handles configuring and handling the responses + from a u-blox GPS module. Works with most modules from u-blox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + Arduino IDE 1.8.5 + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "SparkFun_Ublox_Arduino_Library_Series_6_7.h" + +SFE_UBLOX_GPS::SFE_UBLOX_GPS(void) +{ + // Constructor + currentGeofenceParams.numFences = 0; // Zero the number of geofences currently in use + moduleQueried.versionNumber = false; + + if (checksumFailurePin >= 0) + { + pinMode((uint8_t)checksumFailurePin, OUTPUT); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } +} + +//Initialize the Serial port +boolean SFE_UBLOX_GPS::begin(TwoWire &wirePort, uint8_t deviceAddress) +{ + commType = COMM_TYPE_I2C; + _i2cPort = &wirePort; //Grab which port the user wants us to use + + //We expect caller to begin their I2C port, with the speed of their choice external to the library + //But if they forget, we start the hardware here. + + //We're moving away from the practice of starting Wire hardware in a library. This is to avoid cross platform issues. + //ie, there are some platforms that don't handle multiple starts to the wire hardware. Also, every time you start the wire + //hardware the clock speed reverts back to 100kHz regardless of previous Wire.setClocks(). + //_i2cPort->begin(); + + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + + return (isConnected()); +} + +//Initialize the Serial port +boolean SFE_UBLOX_GPS::begin(Stream &serialPort) +{ + commType = COMM_TYPE_SERIAL; + _serialPort = &serialPort; //Grab which port the user wants us to use + + return (isConnected()); +} + +//Enable or disable the printing of sent/response HEX values. +//Use this in conjunction with 'Transport Logging' from the Universal Reader Assistant to see what they're doing that we're not +void SFE_UBLOX_GPS::enableDebugging(Stream &debugPort, boolean printLimitedDebug) +{ + _debugSerial = &debugPort; //Grab which port the user wants us to use for debugging + if (printLimitedDebug == false) + { + _printDebug = true; //Should we print the commands we send? Good for debugging + } + else + { + _printLimitedDebug = true; //Should we print limited debug messages? Good for debugging high navigation rates + } +} +void SFE_UBLOX_GPS::disableDebugging(void) +{ + _printDebug = false; //Turn off extra print statements + _printLimitedDebug = false; +} + +//Safely print messages +void SFE_UBLOX_GPS::debugPrint(char *message) +{ + if (_printDebug == true) + { + _debugSerial->print(message); + } +} +//Safely print messages +void SFE_UBLOX_GPS::debugPrintln(char *message) +{ + if (_printDebug == true) + { + _debugSerial->println(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_NACK: + return "Command not acknowledged (NACK)"; + 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; + case SFE_UBLOX_STATUS_DATA_OVERWRITTEN: + return "Data Packet Overwritten"; + break; + default: + return "Unknown Status"; + break; + } + return "None"; +} + +void SFE_UBLOX_GPS::factoryReset() +{ + // Copy default settings to permanent + // Note: this does not load the permanent configuration into the current configuration. Calling factoryDefault() will do that. + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 13; + packetCfg.startingSpot = 0; + for (uint8_t i = 0; i < 4; i++) + { + payloadCfg[0 + i] = 0xff; // clear mask: copy default config to permanent config + payloadCfg[4 + i] = 0x00; // save mask: don't save current to permanent + payloadCfg[8 + i] = 0x00; // load mask: don't copy permanent config to current + } + payloadCfg[12] = 0xff; // all forms of permanent memory + sendCommand(&packetCfg, 0); // don't expect ACK + hardReset(); // cause factory default config to actually be loaded and used cleanly +} + +void SFE_UBLOX_GPS::hardReset() +{ + // Issue hard reset + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RST; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + payloadCfg[0] = 0xff; // cold start + payloadCfg[1] = 0xff; // cold start + payloadCfg[2] = 0; // 0=HW reset + payloadCfg[3] = 0; // reserved + sendCommand(&packetCfg, 0); // don't expect ACK +} + +//Changes the serial baud rate of the u-blox module, can't return success/fail 'cause ACK from modem +//is lost due to baud rate change +void SFE_UBLOX_GPS::setSerialRate(uint32_t baudrate, uint8_t uartPort, uint16_t maxWait) +{ + //Get the current config values for the UART port + getPortSettings(uartPort, maxWait); //This will load the payloadCfg array with current port settings + + if (_printDebug == true) + { + _debugSerial->print(F("Current baud rate: ")); + _debugSerial->println(((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[8] = baudrate; + payloadCfg[9] = baudrate >> 8; + payloadCfg[10] = baudrate >> 16; + payloadCfg[11] = baudrate >> 24; + + if (_printDebug == true) + { + _debugSerial->print(F("New baud rate:")); + _debugSerial->println(((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + _debugSerial->print(F("setSerialRate: sendCommand returned: ")); + _debugSerial->println(statusString(retVal)); + } +} + +//Changes the I2C address that the u-blox module responds to +//0x42 is the default but can be changed with this command +boolean SFE_UBLOX_GPS::setI2CAddress(uint8_t deviceAddress, uint16_t maxWait) +{ + //Get the current config values for the I2C port + getPortSettings(COM_PORT_I2C, maxWait); //This will load the payloadCfg array with current port settings + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[4] = deviceAddress << 1; //DDC mode LSB + + if (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT) // We are only expecting an ACK + { + //Success! Now change our internal global. + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + return (true); + } + return (false); +} + +//Want to see the NMEA messages on the Serial port? Here's how +void SFE_UBLOX_GPS::setNMEAOutputPort(Stream &nmeaOutputPort) +{ + _nmeaOutputPort = &nmeaOutputPort; //Store the port from user +} + +//Called regularly to check for available bytes on the user' specified port +boolean SFE_UBLOX_GPS::checkUblox(uint8_t requestedClass, uint8_t requestedID) +{ + return checkUbloxInternal(&packetCfg, requestedClass, requestedID); +} + +//Called regularly to check for available bytes on the user' specified port +boolean SFE_UBLOX_GPS::checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (commType == COMM_TYPE_I2C) + return (checkUbloxI2C(incomingUBX, requestedClass, requestedID)); + else if (commType == COMM_TYPE_SERIAL) + return (checkUbloxSerial(incomingUBX, requestedClass, requestedID)); + return false; +} + +//Polls I2C for data, passing any new bytes to process() +//Returns true if new bytes are available +boolean SFE_UBLOX_GPS::checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (millis() - lastCheck >= i2cPollingWait) + { + //Get the number of bytes available from the module + uint16_t bytesAvailable = 0; + _i2cPort->beginTransmission(_gpsI2Caddress); + _i2cPort->write(0xFD); //0xFD (MSB) and 0xFE (LSB) are the registers that contain number of bytes available + if (_i2cPort->endTransmission(false) != 0) //Send a restart command. Do not release bus. + return (false); //Sensor did not ACK + + _i2cPort->requestFrom((uint8_t)_gpsI2Caddress, (uint8_t)2); + if (_i2cPort->available()) + { + uint8_t msb = _i2cPort->read(); + uint8_t lsb = _i2cPort->read(); + if (lsb == 0xFF) + { + //I believe this is a u-blox bug. Device should never present an 0xFF. + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + _debugSerial->println(F("checkUbloxI2C: u-blox bug, length lsb is 0xFF")); + } + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + lastCheck = millis(); //Put off checking to avoid I2C bus traffic + return (false); + } + bytesAvailable = (uint16_t)msb << 8 | lsb; + } + + if (bytesAvailable == 0) + { + if (_printDebug == true) + { + _debugSerial->println(F("checkUbloxI2C: OK, zero bytes available")); + } + lastCheck = millis(); //Put off checking to avoid I2C bus traffic + return (false); + } + + //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. 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 + bytesAvailable &= ~((uint16_t)1 << 15); + + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + _debugSerial->print(F("checkUbloxI2C: Bytes available error:")); + _debugSerial->println(bytesAvailable); + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + } + } + + if (bytesAvailable > 100) + { + if (_printDebug == true) + { + _debugSerial->print(F("checkUbloxI2C: Large packet of ")); + _debugSerial->print(bytesAvailable); + _debugSerial->println(F(" bytes received")); + } + } + else + { + if (_printDebug == true) + { + _debugSerial->print(F("checkUbloxI2C: Reading ")); + _debugSerial->print(bytesAvailable); + _debugSerial->println(F(" bytes")); + } + } + + while (bytesAvailable) + { + _i2cPort->beginTransmission(_gpsI2Caddress); + _i2cPort->write(0xFF); //0xFF is the register to read data from + if (_i2cPort->endTransmission(false) != 0) //Send a restart command. Do not release bus. + return (false); //Sensor did not ACK + + //Limit to 32 bytes or whatever the buffer limit is for given platform + uint16_t bytesToRead = bytesAvailable; + if (bytesToRead > I2C_BUFFER_LENGTH) + bytesToRead = I2C_BUFFER_LENGTH; + + TRY_AGAIN: + + _i2cPort->requestFrom((uint8_t)_gpsI2Caddress, (uint8_t)bytesToRead); + if (_i2cPort->available()) + { + for (uint16_t x = 0; x < bytesToRead; x++) + { + uint8_t incoming = _i2cPort->read(); //Grab the actual character + + //Check to see if the first read is 0x7F. If it is, the module is not ready + //to respond. Stop, wait, and try again + if (x == 0) + { + if (incoming == 0x7F) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + _debugSerial->println(F("checkUbloxU2C: u-blox error, module not ready with data")); + } + delay(5); //In logic analyzation, the module starting responding after 1.48ms + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + goto TRY_AGAIN; + } + } + + process(incoming, incomingUBX, requestedClass, requestedID); //Process this valid character + } + } + else + return (false); //Sensor did not respond + + bytesAvailable -= bytesToRead; + } + } + + return (true); + +} //end checkUbloxI2C() + +//Checks Serial for data, passing any new bytes to process() +boolean SFE_UBLOX_GPS::checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + while (_serialPort->available()) + { + process(_serialPort->read(), incomingUBX, requestedClass, requestedID); + } + return (true); + +} //end checkUbloxSerial() + +//Processes NMEA and UBX binary sentences one byte at a time +//Take a given byte and file it into the proper array +void SFE_UBLOX_GPS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if ((currentSentence == NONE) || (currentSentence == NMEA)) + { + if (incoming == 0xB5) //UBX binary frames start with 0xB5, aka μ + { + //This is the start of a binary sentence. Reset flags. + //We still don't know the response class + ubxFrameCounter = 0; + currentSentence = UBX; + //Reset the packetBuf.counter even though we will need to reset it again when ubxFrameCounter == 2 + packetBuf.counter = 0; + ignoreThisPayload = false; //We should not ignore this payload - yet + //Store data in packetBuf until we know if we have a requested class and ID match + activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + } + else if (incoming == '$') + { + currentSentence = NMEA; + } + else if (incoming == 0xD3) //RTCM frames start with 0xD3 + { + rtcmFrameCounter = 0; + currentSentence = RTCM; + } + else + { + //This character is unknown or we missed the previous start of a sentence + } + } + + //Depending on the sentence, pass the character to the individual processor + if (currentSentence == UBX) + { + //Decide what type of response this is + if ((ubxFrameCounter == 0) && (incoming != 0xB5)) //ISO 'μ' + currentSentence = NONE; //Something went wrong. Reset. + else if ((ubxFrameCounter == 1) && (incoming != 0x62)) //ASCII 'b' + currentSentence = NONE; //Something went wrong. Reset. + // Note to future self: + // There may be some duplication / redundancy in the next few lines as processUBX will also + // load information into packetBuf, but we'll do it here too for clarity + else if (ubxFrameCounter == 2) //Class + { + // Record the class in packetBuf until we know what to do with it + packetBuf.cls = incoming; // (Duplication) + rollingChecksumA = 0; //Reset our rolling checksums here (not when we receive the 0xB5) + rollingChecksumB = 0; + packetBuf.counter = 0; //Reset the packetBuf.counter (again) + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // Reset the packet validity (redundant?) + packetBuf.startingSpot = incomingUBX->startingSpot; //Copy the startingSpot + } + else if (ubxFrameCounter == 3) //ID + { + // Record the ID in packetBuf until we know what to do with it + packetBuf.id = incoming; // (Duplication) + //We can now identify the type of response + //If the packet we are receiving is not an ACK then check for a class and ID match + if (packetBuf.cls != UBX_CLASS_ACK) + { + //This is not an ACK so check for a class and ID match + if ((packetBuf.cls == requestedClass) && (packetBuf.id == requestedID)) + { + //This is not an ACK and we have a class and ID match + //So start diverting data into incomingUBX (usually packetCfg) + activePacketBuffer = SFE_UBLOX_PACKET_PACKETCFG; + incomingUBX->cls = packetBuf.cls; //Copy the class and ID into incomingUBX (usually packetCfg) + incomingUBX->id = packetBuf.id; + incomingUBX->counter = packetBuf.counter; //Copy over the .counter too + } + else + { + //This is not an ACK and we do not have a class and ID match + //so we should keep diverting data into packetBuf and ignore the payload + ignoreThisPayload = true; + } + } + else + { + // This is an ACK so it is to early to do anything with it + // We need to wait until we have received the length and data bytes + // So we should keep diverting data into packetBuf + } + } + else if (ubxFrameCounter == 4) //Length LSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len = incoming; // (Duplication) + } + else if (ubxFrameCounter == 5) //Length MSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len |= incoming << 8; // (Duplication) + } + else if (ubxFrameCounter == 6) //This should be the first byte of the payload unless .len is zero + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + if (_printDebug == true) + { + _debugSerial->print(F("process: ZERO LENGTH packet received: Class: 0x")); + _debugSerial->print(packetBuf.cls, HEX); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->println(packetBuf.id, HEX); + } + //If length is zero (!) this will be the first byte of the checksum so record it + packetBuf.checksumA = incoming; + } + else + { + //The length is not zero so record this byte in the payload + packetBuf.payload[0] = incoming; + } + } + else if (ubxFrameCounter == 7) //This should be the second byte of the payload unless .len is zero or one + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + //If length is zero (!) this will be the second byte of the checksum so record it + packetBuf.checksumB = incoming; + } + else if (packetBuf.len == 1) // Check if length is one + { + //The length is one so this is the first byte of the checksum + packetBuf.checksumA = incoming; + } + else // Length is >= 2 so this must be a payload byte + { + packetBuf.payload[1] = incoming; + } + // Now that we have received two payload bytes, we can check for a matching ACK/NACK + if ((activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) // If we are not already processing a data packet + && (packetBuf.cls == UBX_CLASS_ACK) // and if this is an ACK/NACK + && (packetBuf.payload[0] == requestedClass) // and if the class matches + && (packetBuf.payload[1] == requestedID)) // and if the ID matches + { + if (packetBuf.len == 2) // Check if .len is 2 + { + // Then this is a matching ACK so copy it into packetAck + activePacketBuffer = SFE_UBLOX_PACKET_PACKETACK; + packetAck.cls = packetBuf.cls; + packetAck.id = packetBuf.id; + packetAck.len = packetBuf.len; + packetAck.counter = packetBuf.counter; + packetAck.payload[0] = packetBuf.payload[0]; + packetAck.payload[1] = packetBuf.payload[1]; + } + else // Length is not 2 (hopefully this is impossible!) + { + if (_printDebug == true) + { + _debugSerial->print(F("process: ACK received with .len != 2: Class: 0x")); + _debugSerial->print(packetBuf.payload[0], HEX); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->print(packetBuf.payload[1], HEX); + _debugSerial->print(F(" len: ")); + _debugSerial->println(packetBuf.len); + } + } + } + } + + //Divert incoming into the correct buffer + if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETACK) + processUBX(incoming, &packetAck, requestedClass, requestedID); + else if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG) + processUBX(incoming, incomingUBX, requestedClass, requestedID); + else // if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) + processUBX(incoming, &packetBuf, requestedClass, requestedID); + + //Finally, increment the frame counter + ubxFrameCounter++; + } + else if (currentSentence == NMEA) + { + processNMEA(incoming); //Process each NMEA character + } + else if (currentSentence == RTCM) + { + processRTCMframe(incoming); //Deal with RTCM bytes + } +} + +//This is the default or generic NMEA processor. We're only going to pipe the data to serial port so we can see it. +//User could overwrite this function to pipe characters to nmea.process(c) of tinyGPS or MicroNMEA +//Or user could pipe each character to a buffer, radio, etc. +void SFE_UBLOX_GPS::processNMEA(char incoming) +{ + //If user has assigned an output port then pipe the characters there + if (_nmeaOutputPort != NULL) + _nmeaOutputPort->write(incoming); //Echo this byte to the serial port +} + +//We need to be able to identify an RTCM packet and then the length +//so that we know when the RTCM message is completely received and we then start +//listening for other sentences (like NMEA or UBX) +//RTCM packet structure is very odd. I never found RTCM STANDARD 10403.2 but +//http://d1.amobbs.com/bbs_upload782111/files_39/ourdev_635123CK0HJT.pdf is good +//https://dspace.cvut.cz/bitstream/handle/10467/65205/F3-BP-2016-Shkalikava-Anastasiya-Prenos%20polohove%20informace%20prostrednictvim%20datove%20site.pdf?sequence=-1 +//Lead me to: https://forum.u-blox.com/index.php/4348/how-to-read-rtcm-messages-from-neo-m8p +//RTCM 3.2 bytes look like this: +//Byte 0: Always 0xD3 +//Byte 1: 6-bits of zero +//Byte 2: 10-bits of length of this packet including the first two-ish header bytes, + 6. +//byte 3 + 4 bits: Msg type 12 bits +//Example: D3 00 7C 43 F0 ... / 0x7C = 124+6 = 130 bytes in this packet, 0x43F = Msg type 1087 +void SFE_UBLOX_GPS::processRTCMframe(uint8_t incoming) +{ + if (rtcmFrameCounter == 1) + { + rtcmLen = (incoming & 0x03) << 8; //Get the last two bits of this byte. Bits 8&9 of 10-bit length + } + else if (rtcmFrameCounter == 2) + { + rtcmLen |= incoming; //Bits 0-7 of packet length + rtcmLen += 6; //There are 6 additional bytes of what we presume is header, msgType, CRC, and stuff + } + /*else if (rtcmFrameCounter == 3) + { + rtcmMsgType = incoming << 4; //Message Type, MS 4 bits + } + else if (rtcmFrameCounter == 4) + { + rtcmMsgType |= (incoming >> 4); //Message Type, bits 0-7 + }*/ + + rtcmFrameCounter++; + + processRTCM(incoming); //Here is where we expose this byte to the user + + if (rtcmFrameCounter == rtcmLen) + { + //We're done! + currentSentence = NONE; //Reset and start looking for next sentence type + } +} + +//This function is called for each byte of an RTCM frame +//Ths user can overwrite this function and process the RTCM frame as they please +//Bytes can be piped to Serial or other interface. The consumer could be a radio or the internet (Ntrip broadcaster) +void SFE_UBLOX_GPS::processRTCM(uint8_t incoming) +{ + //Radio.sendReliable((String)incoming); //An example of passing this byte to a radio + + //_debugSerial->write(incoming); //An example of passing this byte out the serial port + + //Debug printing + // _debugSerial->print(F(" ")); + // if(incoming < 0x10) _debugSerial->print(F("0")); + // if(incoming < 0x10) _debugSerial->print(F("0")); + // _debugSerial->print(incoming, HEX); + // if(rtcmFrameCounter % 16 == 0) _debugSerial->println(); +} + +//Given a character, file it away into the uxb packet structure +//Set valid to VALID or NOT_VALID once sentence is completely received and passes or fails CRC +//The payload portion of the packet can be 100s of bytes but the max array +//size is MAX_PAYLOAD_SIZE bytes. startingSpot can be set so we only record +//a subset of bytes within a larger packet. +void SFE_UBLOX_GPS::processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + //Add all incoming bytes to the rolling checksum + //Stop at len+4 as this is the checksum bytes to that should not be added to the rolling checksum + if (incomingUBX->counter < incomingUBX->len + 4) + addToChecksum(incoming); + + if (incomingUBX->counter == 0) + { + incomingUBX->cls = incoming; + } + else if (incomingUBX->counter == 1) + { + incomingUBX->id = incoming; + } + else if (incomingUBX->counter == 2) //Len LSB + { + incomingUBX->len = incoming; + } + else if (incomingUBX->counter == 3) //Len MSB + { + incomingUBX->len |= incoming << 8; + } + else if (incomingUBX->counter == incomingUBX->len + 4) //ChecksumA + { + incomingUBX->checksumA = incoming; + } + else if (incomingUBX->counter == incomingUBX->len + 5) //ChecksumB + { + incomingUBX->checksumB = incoming; + + currentSentence = NONE; //We're done! Reset the sentence to being looking for a new start char + + //Validate this sentence + if ((incomingUBX->checksumA == rollingChecksumA) && (incomingUBX->checksumB == rollingChecksumB)) + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_VALID; // Flag the packet as valid + + // Let's check if the class and ID match the requestedClass and requestedID + // Remember - this could be a data packet or an ACK packet + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is a NACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_NACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_NOTACKNOWLEDGED; // If we have a match, set the classAndIDmatch flag to NOTACKNOWLEDGED + if (_printDebug == true) + { + _debugSerial->print(F("processUBX: NACK received: Requested Class: 0x")); + _debugSerial->print(incomingUBX->payload[0], HEX); + _debugSerial->print(F(" Requested ID: 0x")); + _debugSerial->println(incomingUBX->payload[1], HEX); + } + } + + if (_printDebug == true) + { + _debugSerial->print(F("Incoming: Size: ")); + _debugSerial->print(incomingUBX->len); + _debugSerial->print(F(" Received: ")); + printPacket(incomingUBX); + + if (incomingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetCfg now valid")); + } + if (packetAck.valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetAck now valid")); + } + if (incomingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetCfg classAndIDmatch")); + } + if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetAck classAndIDmatch")); + } + } + + //We've got a valid packet, now do something with it but only if ignoreThisPayload is false + if (ignoreThisPayload == false) + { + processUBXpacket(incomingUBX); + } + } + else // Checksum failure + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; + + // Let's check if the class and ID match the requestedClass and requestedID. + // This is potentially risky as we are saying that we saw the requested Class and ID + // but that the packet checksum failed. Potentially it could be the class or ID bytes + // that caused the checksum error! + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + //Drive an external pin to allow for easier logic analyzation + if (checksumFailurePin >= 0) + { + digitalWrite((uint8_t)checksumFailurePin, LOW); + delay(10); + digitalWrite((uint8_t)checksumFailurePin, HIGH); + } + + _debugSerial->print(F("Checksum failed:")); + _debugSerial->print(F(" checksumA: ")); + _debugSerial->print(incomingUBX->checksumA); + _debugSerial->print(F(" checksumB: ")); + _debugSerial->print(incomingUBX->checksumB); + + _debugSerial->print(F(" rollingChecksumA: ")); + _debugSerial->print(rollingChecksumA); + _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); + } + } + } + else //Load this byte into the payload array + { + //If a UBX_NAV_PVT packet comes in asynchronously, we need to fudge the startingSpot + uint16_t startingSpot = incomingUBX->startingSpot; + if (incomingUBX->cls == UBX_CLASS_NAV && incomingUBX->id == UBX_NAV_PVT) + startingSpot = 0; + //Begin recording if counter goes past startingSpot + if ((incomingUBX->counter - 4) >= startingSpot) + { + //Check to see if we have room for this byte + if (((incomingUBX->counter - 4) - startingSpot) < MAX_PAYLOAD_SIZE) //If counter = 208, starting spot = 200, we're good to record. + { + // Check if this is payload data which should be ignored + if (ignoreThisPayload == false) + { + incomingUBX->payload[incomingUBX->counter - 4 - startingSpot] = incoming; //Store this byte into payload array + } + } + } + } + + //Increment the counter + incomingUBX->counter++; + + if (incomingUBX->counter == MAX_PAYLOAD_SIZE) + { + //Something has gone very wrong + currentSentence = NONE; //Reset the sentence to being looking for a new start char + if (_printDebug == true) + { + _debugSerial->println(F("processUBX: counter hit MAX_PAYLOAD_SIZE")); + } + } +} + +//Once a packet has been received and validated, identify this packet's class/id and update internal flags +//Note: if the user requests a PVT or a HPPOSLLH message using a custom packet, the data extraction will +// not work as expected beacuse extractLong etc are hardwired to packetCfg payloadCfg. Ideally +// extractLong etc should be updated so they receive a pointer to the packet buffer. +void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) +{ + switch (msg->cls) + { + case UBX_CLASS_NAV: + //u-blox8 length == 92 + //u-blox7 length == 84 + if ((msg->id == UBX_NAV_PVT) && ((msg->len == 92) || (msg->len == 84))) + { + //Parse various byte fields into global vars + constexpr int startingSpot = 0; //fixed value used in processUBX + + timeOfWeek = extractLong(0); + gpsMillisecond = extractLong(0) % 1000; //Get last three digits of iTOW + gpsYear = extractInt(4); + gpsMonth = extractByte(6); + gpsDay = extractByte(7); + gpsHour = extractByte(8); + gpsMinute = extractByte(9); + gpsSecond = extractByte(10); + gpsNanosecond = extractLong(16); //Includes milliseconds + + fixType = extractByte(20 - startingSpot); + //Note: the u-blox7 does not support carrSoln. carrierSolution will be zero. + carrierSolution = extractByte(21 - startingSpot) >> 6; //Get 6th&7th bits of this byte + SIV = extractByte(23 - startingSpot); + longitude = extractLong(24 - startingSpot); + latitude = extractLong(28 - startingSpot); + altitude = extractLong(32 - startingSpot); + altitudeMSL = extractLong(36 - startingSpot); + groundSpeed = extractLong(60 - startingSpot); + headingOfMotion = extractLong(64 - startingSpot); + pDOP = extractInt(76 - startingSpot); + + //Mark all datums as fresh (not read before) + moduleQueried.gpsiTOW = true; + moduleQueried.gpsYear = true; + moduleQueried.gpsMonth = true; + moduleQueried.gpsDay = true; + moduleQueried.gpsHour = true; + moduleQueried.gpsMinute = true; + moduleQueried.gpsSecond = true; + moduleQueried.gpsNanosecond = true; + + moduleQueried.all = true; + moduleQueried.longitude = true; + moduleQueried.latitude = true; + moduleQueried.altitude = true; + moduleQueried.altitudeMSL = true; + moduleQueried.SIV = true; + moduleQueried.fixType = true; + moduleQueried.carrierSolution = true; + moduleQueried.groundSpeed = true; + moduleQueried.headingOfMotion = true; + moduleQueried.pDOP = true; + } + else if (msg->id == UBX_NAV_TIMEUTC && msg->len == 20) + { + timeOfWeek = extractLong(0); + gpsMillisecond = extractLong(0) % 1000; //Get last three digits of iTOW + // extractLong(4); // time accuracy estimate + gpsYear = extractInt(12); + gpsMonth = extractByte(14); + gpsDay = extractByte(15); + gpsHour = extractByte(16); + gpsMinute = extractByte(17); + gpsSecond = extractByte(18); + gpsNanosecond = extractLong(8); //Includes milliseconds + uint8_t valid = extractByte(19); + bool gotTime = (valid & 4) ? true : false; // assume all other fields filled once we have TUTC + + //Mark all datums as fresh (not read before) + moduleQueried.gpsiTOW = gotTime; // valid tow + moduleQueried.gpsYear = gotTime; // valid week num + moduleQueried.gpsMonth = gotTime; + moduleQueried.gpsDay = gotTime; // valid UTC + moduleQueried.gpsHour = gotTime; + moduleQueried.gpsMinute = gotTime; + moduleQueried.gpsSecond = gotTime; + moduleQueried.gpsNanosecond = gotTime; + } + else if (msg->id == UBX_NAV_POSLLH && msg->len == 28) + { + timeOfWeek = extractLong(0); + longitude = extractLong(4); + latitude = extractLong(8); + altitude = extractLong(12); + altitudeMSL = extractLong(16); + horizontalAccuracy = extractLong(20); + verticalAccuracy = extractLong(24); + + moduleQueried.gpsiTOW = true; + moduleQueried.longitude = true; + moduleQueried.latitude = true; + moduleQueried.altitude = true; + moduleQueried.altitudeMSL = true; + highResModuleQueried.horizontalAccuracy = true; + highResModuleQueried.verticalAccuracy = true; + } + else if (msg->id == UBX_NAV_HPPOSLLH && msg->len == 36) + { + timeOfWeek = extractLong(4); + highResLongitude = extractLong(8); + highResLatitude = extractLong(12); + elipsoid = extractLong(16); + meanSeaLevel = extractLong(20); + highResLongitudeHp = extractSignedChar(24); + highResLatitudeHp = extractSignedChar(25); + elipsoidHp = extractSignedChar(26); + meanSeaLevelHp = extractSignedChar(27); + horizontalAccuracy = extractLong(28); + verticalAccuracy = extractLong(32); + + highResModuleQueried.all = true; + highResModuleQueried.highResLatitude = true; + highResModuleQueried.highResLatitudeHp = true; + highResModuleQueried.highResLongitude = true; + highResModuleQueried.highResLongitudeHp = true; + highResModuleQueried.elipsoid = true; + highResModuleQueried.elipsoidHp = true; + highResModuleQueried.meanSeaLevel = true; + highResModuleQueried.meanSeaLevelHp = true; + highResModuleQueried.geoidSeparation = true; + highResModuleQueried.horizontalAccuracy = true; + highResModuleQueried.verticalAccuracy = true; + moduleQueried.gpsiTOW = true; // this can arrive via HPPOS too. + + if (_printDebug == true) + { + _debugSerial->print(F("Sec: ")); + _debugSerial->print(((float)extractLong(4)) / 1000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LON: ")); + _debugSerial->print(((float)(int32_t)extractLong(8)) / 10000000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LAT: ")); + _debugSerial->print(((float)(int32_t)extractLong(12)) / 10000000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("ELI M: ")); + _debugSerial->print(((float)(int32_t)extractLong(16)) / 1000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("MSL M: ")); + _debugSerial->print(((float)(int32_t)extractLong(20)) / 1000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LON HP: ")); + _debugSerial->print(extractSignedChar(24)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("LAT HP: ")); + _debugSerial->print(extractSignedChar(25)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("ELI HP: ")); + _debugSerial->print(extractSignedChar(26)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("MSL HP: ")); + _debugSerial->print(extractSignedChar(27)); + _debugSerial->print(F(" ")); + _debugSerial->print(F("HA 2D M: ")); + _debugSerial->print(((float)(int32_t)extractLong(28)) / 10000.0f); + _debugSerial->print(F(" ")); + _debugSerial->print(F("VERT M: ")); + _debugSerial->println(((float)(int32_t)extractLong(32)) / 10000.0f); + } + } + else { + if (_printDebug == true) + { + _debugSerial->print(F("Unexpected nav packet ")); + _debugSerial->println(msg->id); + } + } + break; + } +} + +//Given a packet and payload, send everything including CRC bytes via I2C port +sfe_ublox_status_e SFE_UBLOX_GPS::sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait) +{ + 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("\nSending: ")); + printPacket(outgoingUBX); + } + + if (commType == COMM_TYPE_I2C) + { + retVal = sendI2cCommand(outgoingUBX, maxWait); + if (retVal != SFE_UBLOX_STATUS_SUCCESS) + { + if (_printDebug == true) + { + _debugSerial->println(F("Send I2C Command failed")); + } + return retVal; + } + } + else if (commType == COMM_TYPE_SERIAL) + { + sendSerialCommand(outgoingUBX); + } + + if (maxWait > 0) + { + //Depending on what we just sent, either we need to look for an ACK or not + if (outgoingUBX->cls == UBX_CLASS_CFG) + { + if (_printDebug == true) + { + _debugSerial->println(F("sendCommand: Waiting for ACK response")); + } + retVal = waitForACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("sendCommand: Waiting for No ACK response")); + } + retVal = waitForNoACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + } + return retVal; +} + +//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 (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 + _i2cPort->write(UBX_SYNCH_1); //μ - oh ublox, you're funny. I will call you micro-blox from now on. + _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 (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 + uint16_t bytesToSend = outgoingUBX->len; + + //"The number of data bytes must be at least 2 to properly distinguish + //from the write access to set the address counter in random read accesses." + uint16_t startSpot = 0; + while (bytesToSend > 1) + { + uint8_t len = bytesToSend; + if (len > I2C_BUFFER_LENGTH) + len = I2C_BUFFER_LENGTH; + + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + //_i2cPort->write(outgoingUBX->payload, len); //Write a portion of the payload to the bus + + 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 (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + + //*outgoingUBX->payload += len; //Move the pointer forward + startSpot += len; //Move the pointer forward + bytesToSend -= len; + } + + //Write checksum + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + if (bytesToSend == 1) + _i2cPort->write(outgoingUBX->payload, 1); + _i2cPort->write(outgoingUBX->checksumA); + _i2cPort->write(outgoingUBX->checksumB); + + //All done transmitting bytes. Release bus. + if (_i2cPort->endTransmission() != 0) + 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 +void SFE_UBLOX_GPS::sendSerialCommand(ubxPacket *outgoingUBX) +{ + //Write header bytes + _serialPort->write(UBX_SYNCH_1); //μ - oh ublox, you're funny. I will call you micro-blox from now on. + _serialPort->write(UBX_SYNCH_2); //b + _serialPort->write(outgoingUBX->cls); + _serialPort->write(outgoingUBX->id); + _serialPort->write(outgoingUBX->len & 0xFF); //LSB + _serialPort->write(outgoingUBX->len >> 8); //MSB + + //Write payload. + for (int i = 0; i < outgoingUBX->len; i++) + { + _serialPort->write(outgoingUBX->payload[i]); + } + + //Write checksum + _serialPort->write(outgoingUBX->checksumA); + _serialPort->write(outgoingUBX->checksumB); +} + +//Returns true if I2C device ack's +boolean SFE_UBLOX_GPS::isConnected(uint16_t maxWait) +{ + if (commType == COMM_TYPE_I2C) + { + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + if (_i2cPort->endTransmission() != 0) + return false; //Sensor did not ack + } + + // Query navigation rate to see whether we get a meaningful response + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are polling the RATE so we expect data and an ACK +} + +//Given a message, calc and store the two byte "8-Bit Fletcher" checksum over the entirety of the message +//This is called before we send a command message +void SFE_UBLOX_GPS::calcChecksum(ubxPacket *msg) +{ + msg->checksumA = 0; + msg->checksumB = 0; + + msg->checksumA += msg->cls; + msg->checksumB += msg->checksumA; + + msg->checksumA += msg->id; + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len & 0xFF); + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len >> 8); + msg->checksumB += msg->checksumA; + + for (uint16_t i = 0; i < msg->len; i++) + { + msg->checksumA += msg->payload[i]; + msg->checksumB += msg->checksumA; + } +} + +//Given a message and a byte, add to rolling "8-Bit Fletcher" checksum +//This is used when receiving messages from module +void SFE_UBLOX_GPS::addToChecksum(uint8_t incoming) +{ + rollingChecksumA += incoming; + rollingChecksumB += rollingChecksumA; +} + +//Pretty prints the current ubxPacket +void SFE_UBLOX_GPS::printPacket(ubxPacket *packet) +{ + if (_printDebug == true) + { + _debugSerial->print(F("CLS:")); + if (packet->cls == UBX_CLASS_NAV) //1 + _debugSerial->print(F("NAV")); + else if (packet->cls == UBX_CLASS_ACK) //5 + _debugSerial->print(F("ACK")); + else if (packet->cls == UBX_CLASS_CFG) //6 + _debugSerial->print(F("CFG")); + else if (packet->cls == UBX_CLASS_MON) //0x0A + _debugSerial->print(F("MON")); + else + { + _debugSerial->print(F("0x")); + _debugSerial->print(packet->cls, HEX); + } + + _debugSerial->print(F(" ID:")); + if (packet->cls == UBX_CLASS_NAV && packet->id == UBX_NAV_PVT) + _debugSerial->print(F("PVT")); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_RATE) + _debugSerial->print(F("RATE")); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_CFG) + _debugSerial->print(F("SAVE")); + else + { + _debugSerial->print(F("0x")); + _debugSerial->print(packet->id, HEX); + } + + _debugSerial->print(F(" Len: 0x")); + _debugSerial->print(packet->len, HEX); + + // Only print the payload is ignoreThisPayload is false otherwise + // we could be printing gibberish from beyond the end of packetBuf + if (ignoreThisPayload == false) + { + _debugSerial->print(F(" Payload:")); + + for (int x = 0; x < packet->len; x++) + { + _debugSerial->print(F(" ")); + _debugSerial->print(packet->payload[x], HEX); + } + } + else + { + _debugSerial->print(F(" Payload: IGNORED")); + } + _debugSerial->println(); + } +} + +//=-=-=-=-=-=-=-= Specific commands =-=-=-=-=-=-=-==-=-=-=-=-=-=-= +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//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. + +//When we poll or get a setting, we will receive _both_ a config packet and an ACK +//If the poll or get request is not valid, we will receive _only_ a NACK + +//If we are trying to get or poll a setting, then packetCfg.len will be 0 or 1 when the packetCfg is _sent_. +//If we poll the setting for a particular port using UBX-CFG-PRT then .len will be 1 initially +//For all other gets or polls, .len will be 0 initially +//(It would be possible for .len to be 2 _if_ we were using UBX-CFG-MSG to poll the settings for a particular message - but we don't use that (currently)) + +//If the get or poll _fails_, i.e. is NACK'd, then packetCfg.len could still be 0 or 1 after the NACK is received +//But if the get or poll is ACK'd, then packetCfg.len will have been updated by the incoming data and will always be at least 2 + +//If we are going to set the value for a setting, then packetCfg.len will be at least 3 when the packetCfg is _sent_. +//(UBX-CFG-MSG appears to have the shortest set length of 3 bytes) + +//We need to think carefully about how interleaved PVT packets affect things. +//It is entirely possible that our packetCfg and packetAck were received successfully +//but while we are still in the "if (checkUblox() == true)" loop a PVT packet is processed +//or _starts_ to arrive (remember that Serial data can arrive very slowly). + +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got an ACK and a valid packetCfg (module is responding with register content) +//Returns SFE_UBLOX_STATUS_DATA_SENT if we got an ACK and no packetCfg (no valid packetCfg needed, module absorbs new register data) +//Returns SFE_UBLOX_STATUS_FAIL if something very bad happens (e.g. a double checksum failure) +//Returns SFE_UBLOX_STATUS_COMMAND_NACK if the packet was not-acknowledged (NACK) +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we had a checksum failure +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an ACK and a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = millis(); + while (millis() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: valid data and valid ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and a correct ACK! + } + + // We can be confident that the data packet (if we are going to get one) will always arrive + // before the matching ACK. So if we sent a config packet which only produces an ACK + // then outgoingUBX->classAndIDmatch will be NOT_DEFINED and the packetAck.classAndIDmatch will VALID. + // We should not check outgoingUBX->valid, outgoingUBX->cls or outgoingUBX->id + // as these may have been changed by a PVT packet. + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: no data and valid ACK after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_SENT); //We got an ACK but no data... + } + + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by another packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && !((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: data being OVERWRITTEN after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If packetAck.classAndIDmatch is VALID but both outgoingUBX->valid and outgoingUBX->classAndIDmatch + // are NOT_VALID then we can be confident we have had a checksum failure on the data packet + else if ((packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: CRC failed after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //Checksum fail + } + + // If our packet was not-acknowledged (NACK) we do not receive a data packet - we only get the NACK. + // So you would expect outgoingUBX->valid and outgoingUBX->classAndIDmatch to still be NOT_DEFINED + // But if a full PVT packet arrives afterwards outgoingUBX->valid could be VALID (or just possibly NOT_VALID) + // but outgoingUBX->cls and outgoingUBX->id would not match... + // So I think this is telling us we need a special state for packetAck.classAndIDmatch to tell us + // the packet was definitely NACK'd otherwise we are possibly just guessing... + // Note: the addition of packetBuf changes the logic of this, but we'll leave the code as is for now. + else if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_NOTACKNOWLEDGED) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: data was NOTACKNOWLEDGED (NACK) after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_COMMAND_NACK); //We received a NACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID but the packetAck.classAndIDmatch is NOT_VALID + // then the ack probably had a checksum error. We will take a gamble and return DATA_RECEIVED. + // If we were playing safe, we should return FAIL instead + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: VALID data and INVALID ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID and the packetAck.classAndIDmatch is NOT_VALID + // then we return a FAIL. This must be a double checksum failure? + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: INVALID data and INVALID ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_FAIL); //We received invalid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID and the packetAck.classAndIDmatch is NOT_DEFINED + // then the ACK has not yet been received and we should keep waiting for it + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: valid data after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. Waiting for ACK.")); + } + } + + } //checkUbloxInternal == true + + delayMicroseconds(500); + } //while (millis() - startTime < maxTime) + + // We have timed out... + // If the outgoingUBX->classAndIDmatch is VALID then we can take a gamble and return DATA_RECEIVED + // even though we did not get an ACK + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: TIMEOUT with valid data after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. ")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data... But no ACK! + } + + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: TIMEOUT after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec.")); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +//For non-CFG queries no ACK is sent so we use this function +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got a config packet full of response data that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we got a corrupt config packet that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = millis(); + while (millis() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + + // If outgoingUBX->classAndIDmatch is VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: valid data with CLS/ID match after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data! + } + + // If the outgoingUBX->classAndIDmatch is VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by another packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && !((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: data being OVERWRITTEN after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If outgoingUBX->classAndIDmatch is NOT_DEFINED + // and outgoingUBX->valid is VALID then this must be (e.g.) a PVT packet + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: valid but UNWANTED data after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->print(F(" msec. Class: ")); + _debugSerial->print(outgoingUBX->cls); + _debugSerial->print(F(" ID: ")); + _debugSerial->print(outgoingUBX->id); + } + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID then we return CRC failure + else if (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: CLS/ID match but failed CRC after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //We received invalid data + } + } + + delayMicroseconds(500); + } + + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: TIMEOUT after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. No packet received.")); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +//Save current configuration to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::saveConfiguration(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[4] = 0xFF; //Set any bit in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Save the selected configuration sub-sections to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::saveConfigSelective(uint32_t configMask, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[4] = configMask & 0xFF; //Set the appropriate bits in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = (configMask >> 8) & 0xFF; + packetCfg.payload[6] = (configMask >> 16) & 0xFF; + packetCfg.payload[7] = (configMask >> 24) & 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Reset module to factory defaults +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::factoryDefault(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[0] = 0xFF; //Set any bit in the clearMask field to clear saved config + packetCfg.payload[1] = 0xFF; + packetCfg.payload[8] = 0xFF; //Set any bit in the loadMask field to discard current config and rebuild from lower non-volatile memory layers + packetCfg.payload[9] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a group, ID and size, return the value of this config spot +//The 32-bit key is put together from group/ID/size. See other getVal to send key directly. +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer, uint16_t maxWait) +{ + //Create key + uint32_t key = 0; + key |= (uint32_t)id; + key |= (uint32_t)group << 16; + key |= (uint32_t)size << 28; + + if (_printDebug == true) + { + _debugSerial->print(F("key: 0x")); + _debugSerial->print(key, HEX); + _debugSerial->println(); + } + + return getVal8(key, layer, maxWait); +} + +//Given a key, return its value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALGET; + packetCfg.len = 4 + 4 * 1; //While multiple keys are allowed, we will send only one key at a time + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + //VALGET uses different memory layer definitions to VALSET + //because it can only return the value for one layer. + //So we need to fiddle the layer here. + //And just to complicate things further, the ZED-F9P only responds + //correctly to layer 0 (RAM) and layer 7 (Default)! + uint8_t getLayer = 7; // 7 is the "Default Layer" + if ((layer & VAL_LAYER_RAM) == VAL_LAYER_RAM) // Did the user request the RAM layer? + { + getLayer = 0; // Layer 0 is RAM + } + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = getLayer; //Layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + if (_printDebug == true) + { + _debugSerial->print(F("key: 0x")); + _debugSerial->print(key, HEX); + _debugSerial->println(); + } + + //Send VALGET command with this key + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + _debugSerial->print(F("getVal8: sendCommand returned: ")); + _debugSerial->println(statusString(retVal)); + } + if (retVal != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (0); //If command send fails then bail + + //Verify the response is the correct length as compared to what the user called (did the module respond with 8-bits but the user called getVal32?) + //Response is 8 bytes plus cfg data + //if(packet->len > 8+1) + + //Pull the requested value from the response + //Response starts at 4+1*N with the 32-bit key so the actual data we're looking for is at 8+1*N + return (extractByte(8)); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + return setVal16(key, value, layer, maxWait); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal16(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set an 8-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal8(uint32_t key, uint8_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set a 32-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal32(uint32_t key, uint32_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset32(uint32_t key, uint32_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset16(uint32_t key, uint16_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset8(uint32_t key, uint8_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset32(uint32_t key, uint32_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + payloadCfg[packetCfg.len + 6] = value >> 8 * 2; + payloadCfg[packetCfg.len + 7] = value >> 8 * 3; + + //Update packet length: 4 byte key ID, 4 bytes of value + packetCfg.len = packetCfg.len + 4 + 4; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset16(uint32_t key, uint16_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + + //Update packet length: 4 byte key ID, 2 bytes of value + packetCfg.len = packetCfg.len + 4 + 2; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset8(uint32_t key, uint8_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value; //Value + + //Update packet length: 4 byte key ID, 1 byte value + packetCfg.len = packetCfg.len + 4 + 1; + + //All done + return (true); +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset32(uint32_t key, uint32_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset32(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset16(uint32_t key, uint16_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset16(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset8(uint32_t key, uint8_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset8(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the current TimeMode3 settings - these contain survey in statuses +boolean SFE_UBLOX_GPS::getSurveyMode(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Control Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + if (getSurveyMode(maxWait) == false) //Ask module for the current TimeMode3 settings. Loads into payloadCfg. + return (false); + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 40; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + //payloadCfg should be loaded with poll response. Now modify only the bits we care about + payloadCfg[2] = mode; //Set mode. Survey-In and Disabled are most common. Use ECEF (not LAT/LON/ALT). + + //svinMinDur is U4 (uint32_t) but we'll only use a uint16_t (waiting more than 65535 seconds seems excessive!) + payloadCfg[24] = observationTime & 0xFF; //svinMinDur in seconds + payloadCfg[25] = observationTime >> 8; //svinMinDur in seconds + payloadCfg[26] = 0; //Truncate to 16 bits + payloadCfg[27] = 0; //Truncate to 16 bits + + //svinAccLimit is U4 (uint32_t) in 0.1mm. + uint32_t svinAccLimit = (uint32_t)(requiredAccuracy * 10000.0); //Convert m to 0.1mm + payloadCfg[28] = svinAccLimit & 0xFF; //svinAccLimit in 0.1mm increments + payloadCfg[29] = svinAccLimit >> 8; + payloadCfg[30] = svinAccLimit >> 16; + payloadCfg[31] = svinAccLimit >> 24; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Begin Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_ENABLE, observationTime, requiredAccuracy, maxWait)); +} + +//Stop Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::disableSurveyMode(uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_DISABLE, 0, 0, maxWait)); +} + +//Reads survey in status and sets the global variables +//for status, position valid, observation time, and mean 3D StdDev +//Returns true if commands was successful +boolean SFE_UBLOX_GPS::getSurveyStatus(uint16_t maxWait) +{ + //Reset variables + svin.active = false; + svin.valid = false; + svin.observationTime = 0; + svin.meanAccuracy = 0; + + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_SVIN; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if ((sendCommand(&packetCfg, maxWait)) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //We got a response, now parse the bits into the svin structure + + //dur (Passed survey-in observation time) is U4 (uint32_t) seconds. We truncate to 16 bits + //(waiting more than 65535 seconds (18.2 hours) seems excessive!) + uint32_t tmpObsTime = extractLong(8); + if (tmpObsTime <= 0xFFFF) + { + svin.observationTime = (uint16_t)tmpObsTime; + } + else + { + svin.observationTime = 0xFFFF; + } + + // meanAcc is U4 (uint32_t) in 0.1mm. We convert this to float. + uint32_t tempFloat = extractLong(28); + svin.meanAccuracy = ((float)tempFloat) / 10000.0; //Convert 0.1mm to m + + svin.valid = payloadCfg[36]; //1 if survey-in position is valid, 0 otherwise + svin.active = payloadCfg[37]; //1 if survey-in in progress, 0 otherwise + + return (true); +} + +//Loads the payloadCfg array with the current protocol bits located the UBX-CFG-PRT register for a given port +boolean SFE_UBLOX_GPS::getPortSettings(uint8_t portID, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 1; + packetCfg.startingSpot = 0; + + payloadCfg[0] = portID; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +boolean SFE_UBLOX_GPS::setPortOutput(uint8_t portID, uint8_t outStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[14] = outStreamSettings; //OutProtocolMask LSB - Set outStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +boolean SFE_UBLOX_GPS::setPortInput(uint8_t portID, uint8_t inStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + //This will load the payloadCfg array with current port settings + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[12] = inStreamSettings; //InProtocolMask LSB - Set inStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a port to output UBX, NMEA, RTCM3 or a combination thereof +boolean SFE_UBLOX_GPS::setI2COutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_I2C, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUART1Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART1, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUART2Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART2, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUSBOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_USB, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setSPIOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_SPI, comSettings, maxWait)); +} + +//Set the rate at which the module will give us an updated navigation solution +//Expects a number that is the updates per second. For example 1 = 1Hz, 2 = 2Hz, etc. +//Max is 40Hz(?!) +boolean SFE_UBLOX_GPS::setNavigationFrequency(uint8_t navFreq, uint16_t maxWait) +{ + //if(updateRate > 40) updateRate = 40; //Not needed: module will correct out of bounds values + + //Adjust the I2C polling timeout based on update rate + i2cPollingWait = 1000 / (navFreq * 4); //This is the number of ms to wait between checks for new I2C data + + //Query the module for the latest lat/long + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + uint16_t measurementRate = 1000 / navFreq; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[0] = measurementRate & 0xFF; //measRate LSB + payloadCfg[1] = measurementRate >> 8; //measRate MSB + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the rate at which the module is outputting nav solutions +uint8_t SFE_UBLOX_GPS::getNavigationFrequency(uint16_t maxWait) +{ + //Query the module for the latest lat/long + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + uint16_t measurementRate = 0; + + //payloadCfg is now loaded with current bytes. Get what we need + measurementRate = extractInt(0); //Pull from payloadCfg at measRate LSB + + measurementRate = 1000 / measurementRate; //This may return an int when it's a float, but I'd rather not return 4 bytes + return (measurementRate); +} + +//In case no config access to the GPS is possible and PVT is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoPVT(boolean enabled, boolean implicitUpdate) +{ + boolean changes = autoPVT != enabled || autoPVTImplicitUpdate != implicitUpdate; + if (changes) + { + autoPVT = enabled; + autoPVTImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getPVT +//works. +boolean SFE_UBLOX_GPS::setAutoPVT(boolean enable, uint16_t maxWait) +{ + return setAutoPVT(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getPVT +//works. +boolean SFE_UBLOX_GPS::setAutoPVT(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_PVT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoPVT = enable; + autoPVTImplicitUpdate = implicitUpdate; + } + moduleQueried.all = false; + return ok; +} + +//Configure a given message type for a given port (UART1, I2C, SPI, etc) +boolean SFE_UBLOX_GPS::configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + //Poll for the current settings for a given message + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + payloadCfg[0] = msgClass; + payloadCfg[1] = msgID; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //Now send it back with new mods + packetCfg.len = 8; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[2 + portID] = sendRate; //Send rate is relative to the event a message is registered on. For example, if the rate of a navigation message is set to 2, the message is sent every 2nd navigation solution. + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Enable a given message type, default of 1 per update rate (usually 1 per second) +boolean SFE_UBLOX_GPS::enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, rate, maxWait)); +} +//Disable a given message type on a given port +boolean SFE_UBLOX_GPS::disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, 0, maxWait)); +} + +boolean SFE_UBLOX_GPS::enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(UBX_CLASS_NMEA, msgID, portID, rate, maxWait)); +} +boolean SFE_UBLOX_GPS::disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (enableNMEAMessage(msgID, portID, 0, maxWait)); +} + +//Given a message number turns on a message ID for output over a given portID (UART, I2C, SPI, USB, etc) +//To disable a message, set secondsBetween messages to 0 +//Note: This function will return false if the message is already enabled +//For base station RTK output we need to enable various sentences + +//NEO-M8P has four: +//1005 = 0xF5 0x05 - Stationary RTK reference ARP +//1077 = 0xF5 0x4D - GPS MSM7 +//1087 = 0xF5 0x57 - GLONASS MSM7 +//1230 = 0xF5 0xE6 - GLONASS code-phase biases, set to once every 10 seconds + +//ZED-F9P has six: +//1005, 1074, 1084, 1094, 1124, 1230 + +//Much of this configuration is not documented and instead discerned from u-center binary console +boolean SFE_UBLOX_GPS::enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + return (configureMessage(UBX_RTCM_MSB, messageNumber, portID, sendRate, maxWait)); +} + +//Disable a given message on a given port by setting secondsBetweenMessages to zero +boolean SFE_UBLOX_GPS::disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait) +{ + return (enableRTCMmessage(messageNumber, portID, 0, maxWait)); +} + +//Add a new geofence using UBX-CFG-GEOFENCE +boolean SFE_UBLOX_GPS::addGeofence(int32_t latitude, int32_t longitude, uint32_t radius, byte confidence, byte pinPolarity, byte pin, uint16_t maxWait) +{ + if (currentGeofenceParams.numFences >= 4) + return (false); // Quit if we already have four geofences defined + + // Store the new geofence parameters + currentGeofenceParams.lats[currentGeofenceParams.numFences] = latitude; + currentGeofenceParams.longs[currentGeofenceParams.numFences] = longitude; + currentGeofenceParams.rads[currentGeofenceParams.numFences] = radius; + currentGeofenceParams.numFences = currentGeofenceParams.numFences + 1; // Increment the number of fences + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = (currentGeofenceParams.numFences * 12) + 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = currentGeofenceParams.numFences; // numFences + payloadCfg[2] = confidence; // confLvl = Confidence level 0-4 (none, 68%, 95%, 99.7%, 99.99%) + payloadCfg[3] = 0; // reserved1 + if (pin > 0) + { + payloadCfg[4] = 1; // enable PIO combined fence state + } + else + { + payloadCfg[4] = 0; // disable PIO combined fence state + } + payloadCfg[5] = pinPolarity; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = pin; // PIO pin + payloadCfg[7] = 0; //reserved2 + payloadCfg[8] = currentGeofenceParams.lats[0] & 0xFF; + payloadCfg[9] = currentGeofenceParams.lats[0] >> 8; + payloadCfg[10] = currentGeofenceParams.lats[0] >> 16; + payloadCfg[11] = currentGeofenceParams.lats[0] >> 24; + payloadCfg[12] = currentGeofenceParams.longs[0] & 0xFF; + payloadCfg[13] = currentGeofenceParams.longs[0] >> 8; + payloadCfg[14] = currentGeofenceParams.longs[0] >> 16; + payloadCfg[15] = currentGeofenceParams.longs[0] >> 24; + payloadCfg[16] = currentGeofenceParams.rads[0] & 0xFF; + payloadCfg[17] = currentGeofenceParams.rads[0] >> 8; + payloadCfg[18] = currentGeofenceParams.rads[0] >> 16; + payloadCfg[19] = currentGeofenceParams.rads[0] >> 24; + if (currentGeofenceParams.numFences >= 2) + { + payloadCfg[20] = currentGeofenceParams.lats[1] & 0xFF; + payloadCfg[21] = currentGeofenceParams.lats[1] >> 8; + payloadCfg[22] = currentGeofenceParams.lats[1] >> 16; + payloadCfg[23] = currentGeofenceParams.lats[1] >> 24; + payloadCfg[24] = currentGeofenceParams.longs[1] & 0xFF; + payloadCfg[25] = currentGeofenceParams.longs[1] >> 8; + payloadCfg[26] = currentGeofenceParams.longs[1] >> 16; + payloadCfg[27] = currentGeofenceParams.longs[1] >> 24; + payloadCfg[28] = currentGeofenceParams.rads[1] & 0xFF; + payloadCfg[29] = currentGeofenceParams.rads[1] >> 8; + payloadCfg[30] = currentGeofenceParams.rads[1] >> 16; + payloadCfg[31] = currentGeofenceParams.rads[1] >> 24; + } + if (currentGeofenceParams.numFences >= 3) + { + payloadCfg[32] = currentGeofenceParams.lats[2] & 0xFF; + payloadCfg[33] = currentGeofenceParams.lats[2] >> 8; + payloadCfg[34] = currentGeofenceParams.lats[2] >> 16; + payloadCfg[35] = currentGeofenceParams.lats[2] >> 24; + payloadCfg[36] = currentGeofenceParams.longs[2] & 0xFF; + payloadCfg[37] = currentGeofenceParams.longs[2] >> 8; + payloadCfg[38] = currentGeofenceParams.longs[2] >> 16; + payloadCfg[39] = currentGeofenceParams.longs[2] >> 24; + payloadCfg[40] = currentGeofenceParams.rads[2] & 0xFF; + payloadCfg[41] = currentGeofenceParams.rads[2] >> 8; + payloadCfg[42] = currentGeofenceParams.rads[2] >> 16; + payloadCfg[43] = currentGeofenceParams.rads[2] >> 24; + } + if (currentGeofenceParams.numFences >= 4) + { + payloadCfg[44] = currentGeofenceParams.lats[3] & 0xFF; + payloadCfg[45] = currentGeofenceParams.lats[3] >> 8; + payloadCfg[46] = currentGeofenceParams.lats[3] >> 16; + payloadCfg[47] = currentGeofenceParams.lats[3] >> 24; + payloadCfg[48] = currentGeofenceParams.longs[3] & 0xFF; + payloadCfg[49] = currentGeofenceParams.longs[3] >> 8; + payloadCfg[50] = currentGeofenceParams.longs[3] >> 16; + payloadCfg[51] = currentGeofenceParams.longs[3] >> 24; + payloadCfg[52] = currentGeofenceParams.rads[3] & 0xFF; + payloadCfg[53] = currentGeofenceParams.rads[3] >> 8; + payloadCfg[54] = currentGeofenceParams.rads[3] >> 16; + payloadCfg[55] = currentGeofenceParams.rads[3] >> 24; + } + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear all geofences using UBX-CFG-GEOFENCE +boolean SFE_UBLOX_GPS::clearGeofences(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = 0; // numFences + payloadCfg[2] = 0; // confLvl + payloadCfg[3] = 0; // reserved1 + payloadCfg[4] = 0; // disable PIO combined fence state + payloadCfg[5] = 0; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = 0; // PIO pin + payloadCfg[7] = 0; //reserved2 + + currentGeofenceParams.numFences = 0; // Zero the number of geofences currently in use + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear the antenna control settings using UBX-CFG-ANT +//This function is hopefully redundant but may be needed to release +//any PIO pins pre-allocated for antenna functions +boolean SFE_UBLOX_GPS::clearAntPIO(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_ANT; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0x10; // Antenna flag mask: set the recovery bit + payloadCfg[1] = 0; + payloadCfg[2] = 0xFF; // Antenna pin configuration: set pinSwitch and pinSCD to 31 + payloadCfg[3] = 0xFF; // Antenna pin configuration: set pinOCD to 31, set reconfig bit + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Returns the combined geofence state using UBX-NAV-GEOFENCE +boolean SFE_UBLOX_GPS::getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_GEOFENCE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the geofence status. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + currentGeofenceState.status = payloadCfg[5]; // Extract the status + currentGeofenceState.numFences = payloadCfg[6]; // Extract the number of geofences + currentGeofenceState.combState = payloadCfg[7]; // Extract the combined state of all geofences + if (currentGeofenceState.numFences > 0) + currentGeofenceState.states[0] = payloadCfg[8]; // Extract geofence 1 state + if (currentGeofenceState.numFences > 1) + currentGeofenceState.states[1] = payloadCfg[10]; // Extract geofence 2 state + if (currentGeofenceState.numFences > 2) + currentGeofenceState.states[2] = payloadCfg[12]; // Extract geofence 3 state + if (currentGeofenceState.numFences > 3) + currentGeofenceState.states[3] = payloadCfg[14]; // Extract geofence 4 state + + return (true); +} + +//Power Save Mode +//Enables/Disables Low Power Mode using UBX-CFG-RXM +boolean SFE_UBLOX_GPS::powerSaveMode(bool power_save, uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version is ")); + _debugSerial->println(protVer); + } + */ + if (protVer >= 27) + { + if (_printDebug == true) + { + _debugSerial->println(F("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version")); + } + return (false); + } + + // Now let's change the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + if (power_save) + { + payloadCfg[1] = 1; // Power Save Mode + } + else + { + payloadCfg[1] = 0; // Continuous Mode + } + + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +// Get Power Save Mode +// Returns the current Low Power Mode using UBX-CFG-RXM +// Returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getPowerSaveMode(uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version is ")); + _debugSerial->println(protVer); + } + */ + if (protVer >= 27) + { + if (_printDebug == true) + { + _debugSerial->println(F("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version")); + } + return (255); + } + + // Now let's read the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + return (payloadCfg[1]); // Return the low power mode +} + +//Change the dynamic platform model using UBX-CFG-NAV5 +//Possible values are: +//PORTABLE,STATIONARY,PEDESTRIAN,AUTOMOTIVE,SEA, +//AIRBORNE1g,AIRBORNE2g,AIRBORNE4g,WRIST,BIKE +//WRIST is not supported in protocol versions less than 18 +//BIKE is supported in protocol versions 19.2 +boolean SFE_UBLOX_GPS::setDynamicModel(dynModel newDynamicModel, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + payloadCfg[0] = 0x01; // mask: set only the dyn bit (0) + payloadCfg[1] = 0x00; // mask + payloadCfg[2] = newDynamicModel; // dynModel + + packetCfg.len = 36; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the dynamic platform model using UBX-CFG-NAV5 +//Returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getDynamicModel(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + return (payloadCfg[2]); // Return the dynamic model +} + +//Given a spot in the payload array, extract four bytes and build a long +uint32_t SFE_UBLOX_GPS::extractLong(uint8_t spotToStart) +{ + uint32_t val = 0; + val |= (uint32_t)payloadCfg[spotToStart + 0] << 8 * 0; + val |= (uint32_t)payloadCfg[spotToStart + 1] << 8 * 1; + val |= (uint32_t)payloadCfg[spotToStart + 2] << 8 * 2; + val |= (uint32_t)payloadCfg[spotToStart + 3] << 8 * 3; + return (val); +} + +//Given a spot in the payload array, extract two bytes and build an int +uint16_t SFE_UBLOX_GPS::extractInt(uint8_t spotToStart) +{ + uint16_t val = 0; + val |= (uint16_t)payloadCfg[spotToStart + 0] << 8 * 0; + val |= (uint16_t)payloadCfg[spotToStart + 1] << 8 * 1; + return (val); +} + +//Given a spot, extract a byte from the payload +uint8_t SFE_UBLOX_GPS::extractByte(uint8_t spotToStart) +{ + return (payloadCfg[spotToStart]); +} + +//Given a spot, extract a signed 8-bit value from the payload +int8_t SFE_UBLOX_GPS::extractSignedChar(uint8_t spotToStart) +{ + return ((int8_t)payloadCfg[spotToStart]); +} + +//Get the current year +uint16_t SFE_UBLOX_GPS::getYear(uint16_t maxWait) +{ + if (moduleQueried.gpsYear == false) + getTimeData(maxWait); + moduleQueried.gpsYear = false; //Since we are about to give this to user, mark this data as stale + return (gpsYear); +} + +//Get the current month +uint8_t SFE_UBLOX_GPS::getMonth(uint16_t maxWait) +{ + if (moduleQueried.gpsMonth == false) + getTimeData(maxWait); + moduleQueried.gpsMonth = false; //Since we are about to give this to user, mark this data as stale + return (gpsMonth); +} + +//Get the current day +uint8_t SFE_UBLOX_GPS::getDay(uint16_t maxWait) +{ + if (moduleQueried.gpsDay == false) + getTimeData(maxWait); + moduleQueried.gpsDay = false; //Since we are about to give this to user, mark this data as stale + return (gpsDay); +} + +//Get the current hour +uint8_t SFE_UBLOX_GPS::getHour(uint16_t maxWait) +{ + if (moduleQueried.gpsHour == false) + getTimeData(maxWait); + moduleQueried.gpsHour = false; //Since we are about to give this to user, mark this data as stale + return (gpsHour); +} + +//Get the current minute +uint8_t SFE_UBLOX_GPS::getMinute(uint16_t maxWait) +{ + if (moduleQueried.gpsMinute == false) + getTimeData(maxWait); + moduleQueried.gpsMinute = false; //Since we are about to give this to user, mark this data as stale + return (gpsMinute); +} + +//Get the current second +uint8_t SFE_UBLOX_GPS::getSecond(uint16_t maxWait) +{ + if (moduleQueried.gpsSecond == false) + getTimeData(maxWait); + moduleQueried.gpsSecond = false; //Since we are about to give this to user, mark this data as stale + return (gpsSecond); +} + +//Get the current millisecond +uint16_t SFE_UBLOX_GPS::getMillisecond(uint16_t maxWait) +{ + if (moduleQueried.gpsiTOW == false) + getTimeData(maxWait); + moduleQueried.gpsiTOW = false; //Since we are about to give this to user, mark this data as stale + return (gpsMillisecond); +} + +//Get the current nanoseconds - includes milliseconds +int32_t SFE_UBLOX_GPS::getNanosecond(uint16_t maxWait) +{ + if (moduleQueried.gpsNanosecond == false) + getTimeData(maxWait); + moduleQueried.gpsNanosecond = false; //Since we are about to give this to user, mark this data as stale + return (gpsNanosecond); +} + +//Get the latest Position/Velocity/Time solution and fill all global variables +boolean SFE_UBLOX_GPS::getPVT(uint16_t maxWait) +{ + if (autoPVT && autoPVTImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + _debugSerial->println(F("getPVT: Autoreporting")); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_PVT); + return moduleQueried.all; + } + else if (autoPVT && !autoPVTImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + _debugSerial->println(F("getPVT: Exit immediately")); + } + return (false); + } + else + { + if (_printDebug == true) + { + _debugSerial->println(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; + packetCfg.len = 0; + //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 + 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); + } +} + +// Update time info (using appropriate call for the chip series) +boolean SFE_UBLOX_GPS::getTimeData(uint16_t maxWait) +{ + return getProtocolVersionHigh(maxWait) < 15 ? getTIMEUTC(maxWait) : getPVT(maxWait); +} + +// Update position info (using appropriate call for the chip series) +boolean SFE_UBLOX_GPS::getPositionData(uint16_t maxWait) +{ + return getProtocolVersionHigh(maxWait) < 20 ? getPOSLLH(maxWait) : getPVT(maxWait); +} + +//Get time (for use on chips with protocol version 14 and earlier) +boolean SFE_UBLOX_GPS::getTIMEUTC(uint16_t maxWait) +{ + debugPrintln((char *)F("getTIMEUTC: Polling")); + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_TIMEUTC; + packetCfg.len = 0; + //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 + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if (_printDebug == true) + { + _debugSerial->print(F("getTIMEUTC retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); +} + +//Get posllh (fos use on chips with protocol version 19 and earlier) +boolean SFE_UBLOX_GPS::getPOSLLH(uint16_t maxWait) +{ + debugPrintln((char *)F("getPOSLLH: Polling")); + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_POSLLH; + packetCfg.len = 0; + //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 + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if (_printDebug == true) + { + _debugSerial->print(F("getPOSLLH retVal: ")); + _debugSerial->println(statusString(retVal)); + } + return (false); +} + +uint32_t SFE_UBLOX_GPS::getTimeOfWeek(uint16_t maxWait /* = 250*/) +{ + if (moduleQueried.gpsiTOW == false) + getTimeData(maxWait); + moduleQueried.gpsiTOW = false; //Since we are about to give this to user, mark this data as stale + return (timeOfWeek); +} + +int32_t SFE_UBLOX_GPS::getHighResLatitude(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLatitude == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLatitude = false; //Since we are about to give this to user, mark this data as stale + return (highResLatitude); +} + +int8_t SFE_UBLOX_GPS::getHighResLatitudeHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLatitudeHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLatitudeHp = false; //Since we are about to give this to user, mark this data as stale + return (highResLatitudeHp); +} + +int32_t SFE_UBLOX_GPS::getHighResLongitude(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLongitude == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLongitude = false; //Since we are about to give this to user, mark this data as stale + return (highResLongitude); +} + +int8_t SFE_UBLOX_GPS::getHighResLongitudeHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLongitudeHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLongitudeHp = false; //Since we are about to give this to user, mark this data as stale + return (highResLongitudeHp); +} + +int32_t SFE_UBLOX_GPS::getElipsoid(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.elipsoid == false) + getHPPOSLLH(maxWait); + highResModuleQueried.elipsoid = false; //Since we are about to give this to user, mark this data as stale + return (elipsoid); +} + +int8_t SFE_UBLOX_GPS::getElipsoidHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.elipsoidHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.elipsoidHp = false; //Since we are about to give this to user, mark this data as stale + return (elipsoidHp); +} + +int32_t SFE_UBLOX_GPS::getMeanSeaLevel(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.meanSeaLevel == false) + getHPPOSLLH(maxWait); + highResModuleQueried.meanSeaLevel = false; //Since we are about to give this to user, mark this data as stale + return (meanSeaLevel); +} + +int8_t SFE_UBLOX_GPS::getMeanSeaLevelHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.meanSeaLevelHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.meanSeaLevelHp = false; //Since we are about to give this to user, mark this data as stale + return (meanSeaLevelHp); +} + +// getGeoidSeparation is currently redundant. The geoid separation seems to only be provided in NMEA GGA and GNS messages. +int32_t SFE_UBLOX_GPS::getGeoidSeparation(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.geoidSeparation == false) + getHPPOSLLH(maxWait); + highResModuleQueried.geoidSeparation = false; //Since we are about to give this to user, mark this data as stale + return (geoidSeparation); +} + +uint32_t SFE_UBLOX_GPS::getHorizontalAccuracy(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.horizontalAccuracy == false) + getHPPOSLLH(maxWait); + highResModuleQueried.horizontalAccuracy = false; //Since we are about to give this to user, mark this data as stale + return (horizontalAccuracy); +} + +uint32_t SFE_UBLOX_GPS::getVerticalAccuracy(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.verticalAccuracy == false) + getHPPOSLLH(maxWait); + highResModuleQueried.verticalAccuracy = false; //Since we are about to give this to user, mark this data as stale + return (verticalAccuracy); +} + +boolean SFE_UBLOX_GPS::getHPPOSLLH(uint16_t maxWait) +{ + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_HPPOSLLH; + packetCfg.len = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are only expecting data (no ACK) +} + +//Get the current 3D high precision positional accuracy - a fun thing to watch +//Returns a long representing the 3D accuracy in millimeters +uint32_t SFE_UBLOX_GPS::getPositionAccuracy(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_HPPOSECEF; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (0); //If command send fails then bail + + uint32_t tempAccuracy = extractLong(24); //We got a response, now extract a long beginning at a given position + + if ((tempAccuracy % 10) >= 5) + tempAccuracy += 5; //Round fraction of mm up to next mm if .5 or above + tempAccuracy /= 10; //Convert 0.1mm units to mm + + return (tempAccuracy); +} + +//Get the current latitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLatitude(uint16_t maxWait) +{ + if (moduleQueried.latitude == false) + getPositionData(maxWait); + moduleQueried.latitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (latitude); +} + +//Get the current longitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLongitude(uint16_t maxWait) +{ + if (moduleQueried.longitude == false) + getPositionData(maxWait); + moduleQueried.longitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (longitude); +} + +//Get the current altitude in mm according to ellipsoid model +int32_t SFE_UBLOX_GPS::getAltitude(uint16_t maxWait) +{ + if (moduleQueried.altitude == false) + getPositionData(maxWait); + moduleQueried.altitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (altitude); +} + +//Get the current altitude in mm according to mean sea level +//Ellipsoid model: https://www.esri.com/news/arcuser/0703/geoid1of3.html +//Difference between Ellipsoid Model and Mean Sea Level: https://eos-gnss.com/elevation-for-beginners/ +int32_t SFE_UBLOX_GPS::getAltitudeMSL(uint16_t maxWait) +{ + if (moduleQueried.altitudeMSL == false) + getPositionData(maxWait); + moduleQueried.altitudeMSL = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (altitudeMSL); +} + +//Get the number of satellites used in fix +uint8_t SFE_UBLOX_GPS::getSIV(uint16_t maxWait) +{ + if (moduleQueried.SIV == false) + getPVT(maxWait); + moduleQueried.SIV = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (SIV); +} + +//Get the current fix type +//0=no fix, 1=dead reckoning, 2=2D, 3=3D, 4=GNSS, 5=Time fix +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; + + return (fixType); +} + +//Get the carrier phase range solution status +//Useful when querying module to see if it has high-precision RTK fix +//0=No solution, 1=Float solution, 2=Fixed solution +uint8_t SFE_UBLOX_GPS::getCarrierSolutionType(uint16_t maxWait) +{ + if (moduleQueried.carrierSolution == false) + getPVT(maxWait); + moduleQueried.carrierSolution = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (carrierSolution); +} + +//Get the ground speed in mm/s +int32_t SFE_UBLOX_GPS::getGroundSpeed(uint16_t maxWait) +{ + if (moduleQueried.groundSpeed == false) + getPVT(maxWait); + moduleQueried.groundSpeed = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (groundSpeed); +} + +//Get the heading of motion (as opposed to heading of car) in degrees * 10^-5 +int32_t SFE_UBLOX_GPS::getHeading(uint16_t maxWait) +{ + if (moduleQueried.headingOfMotion == false) + getPVT(maxWait); + moduleQueried.headingOfMotion = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (headingOfMotion); +} + +//Get the positional dillution of precision * 10^-2 (dimensionless) +uint16_t SFE_UBLOX_GPS::getPDOP(uint16_t maxWait) +{ + if (moduleQueried.pDOP == false) + getPVT(maxWait); + moduleQueried.pDOP = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (pDOP); +} + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionHigh(uint16_t maxWait) +{ + if (moduleQueried.versionNumber == false) + getProtocolVersion(maxWait); + return (versionHigh); +} + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionLow(uint16_t maxWait) +{ + if (moduleQueried.versionNumber == false) + getProtocolVersion(maxWait); + return (versionLow); +} + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +boolean SFE_UBLOX_GPS::getProtocolVersion(uint16_t maxWait) +{ + //Send packet with only CLS and ID, length of zero. This will cause the module to respond with the contents of that CLS/ID. + packetCfg.cls = UBX_CLASS_MON; + packetCfg.id = UBX_MON_VER; + + packetCfg.len = 0; + packetCfg.startingSpot = 40; //Start at first "extended software information" string + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (false); //If command send fails then bail + + //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(); + // } + + //We will step through the payload looking at each extension field of 30 bytes + for (uint8_t extensionNumber = 0; extensionNumber < 10; extensionNumber++) + { + //Now we need to find "PROTVER=18.00" in the incoming byte stream + if (payloadCfg[(30 * extensionNumber) + 0] == 'P' && payloadCfg[(30 * extensionNumber) + 6] == 'R') + { + 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 + + 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) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_RELPOSNED; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (false); //If command send fails then bail + + //We got a response, now parse the bits + + uint16_t refStationID = extractInt(2); + //_debugSerial->print(F("refStationID: ")); + //_debugSerial->println(refStationID)); + + int32_t tempRelPos; + + tempRelPos = extractLong(8); + relPosInfo.relPosN = tempRelPos / 100.0; //Convert cm to m + + tempRelPos = extractLong(12); + relPosInfo.relPosE = tempRelPos / 100.0; //Convert cm to m + + tempRelPos = extractLong(16); + relPosInfo.relPosD = tempRelPos / 100.0; //Convert cm to m + + relPosInfo.relPosLength = extractLong(20); + relPosInfo.relPosHeading = extractLong(24); + + relPosInfo.relPosHPN = payloadCfg[32]; + relPosInfo.relPosHPE = payloadCfg[33]; + relPosInfo.relPosHPD = payloadCfg[34]; + relPosInfo.relPosHPLength = payloadCfg[35]; + + uint32_t tempAcc; + + tempAcc = extractLong(36); + relPosInfo.accN = tempAcc / 10000.0; //Convert 0.1 mm to m + + tempAcc = extractLong(40); + relPosInfo.accE = tempAcc / 10000.0; //Convert 0.1 mm to m + + tempAcc = extractLong(44); + relPosInfo.accD = tempAcc / 10000.0; //Convert 0.1 mm to m + + uint8_t flags = payloadCfg[60]; + + relPosInfo.gnssFixOk = flags & (1 << 0); + relPosInfo.diffSoln = flags & (1 << 1); + relPosInfo.relPosValid = flags & (1 << 2); + relPosInfo.carrSoln = (flags & (0b11 << 3)) >> 3; + relPosInfo.isMoving = flags & (1 << 5); + relPosInfo.refPosMiss = flags & (1 << 6); + relPosInfo.refObsMiss = flags & (1 << 7); + + return (true); +} +boolean SFE_UBLOX_GPS::getEsfInfo(uint16_t maxWait) +{ + // Requesting Data from the receiver + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_STATUS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + // payload should be loaded. + imuMeas.version = extractByte(4); + imuMeas.fusionMode = extractByte(12); + ubloxSen.numSens = extractByte(15); + + // Individual Status Sensor in different function + return (true); +} + +// +boolean SFE_UBLOX_GPS::getEsfIns(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_INS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + // Validity of each sensor value below + uint32_t validity = extractLong(0); + + imuMeas.xAngRateVald = (validity && 0x0080) >> 8; + imuMeas.yAngRateVald = (validity && 0x0100) >> 9; + imuMeas.zAngRateVald = (validity && 0x0200) >> 10; + imuMeas.xAccelVald = (validity && 0x0400) >> 11; + imuMeas.yAccelVald = (validity && 0x0800) >> 12; + imuMeas.zAccelVald = (validity && 0x1000) >> 13; + + imuMeas.xAngRate = extractLong(12); // deg/s + imuMeas.yAngRate = extractLong(16); // deg/s + imuMeas.zAngRate = extractLong(20); // deg/s + + imuMeas.xAccel = extractLong(24); // m/s + imuMeas.yAccel = extractLong(28); // m/s + imuMeas.zAccel = extractLong(32); // m/s + + return (true); +} + +// +boolean SFE_UBLOX_GPS::getEsfDataInfo(uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_MEAS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + uint32_t timeStamp = extractLong(0); + uint32_t flags = extractInt(4); + + uint8_t timeSent = (flags && 0x01) >> 1; + uint8_t timeEdge = (flags && 0x02) >> 2; + uint8_t tagValid = (flags && 0x04) >> 3; + uint8_t numMeas = (flags && 0x1000) >> 15; + + if (numMeas > DEF_NUM_SENS) + numMeas = DEF_NUM_SENS; + + uint8_t byteOffset = 4; + + for (uint8_t i = 0; i < numMeas; i++) + { + + uint32_t bitField = extractLong(4 + byteOffset * i); + imuMeas.dataType[i] = (bitField && 0xFF000000) >> 23; + imuMeas.data[i] = (bitField && 0xFFFFFF); + imuMeas.dataTStamp[i] = extractLong(8 + byteOffset * i); + } + + return (true); +} + +boolean SFE_UBLOX_GPS::getEsfRawDataInfo(uint16_t maxWait) +{ + + // Need to know the number of sensor to get the correct data + // Rate selected in UBX-CFG-MSG is not respected + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_RAW; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + uint32_t bitField = extractLong(4); + imuMeas.rawDataType = (bitField && 0xFF000000) >> 23; + imuMeas.rawData = (bitField && 0xFFFFFF); + imuMeas.rawTStamp = extractLong(8); + + return (true); +} + +sfe_ublox_status_e SFE_UBLOX_GPS::getSensState(uint8_t sensor, uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_STATUS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (SFE_UBLOX_STATUS_FAIL); //If command send fails then bail + + ubloxSen.numSens = extractByte(15); + + if (sensor > ubloxSen.numSens) + return (SFE_UBLOX_STATUS_OUT_OF_RANGE); + + checkUblox(); + + uint8_t offset = 4; + + // Only the last sensor value checked will remain. + for (uint8_t i = 0; i < sensor; i++) + { + + uint8_t sensorFieldOne = extractByte(16 + offset * i); + uint8_t sensorFieldTwo = extractByte(17 + offset * i); + ubloxSen.freq = extractByte(18 + offset * i); + uint8_t sensorFieldThr = extractByte(19 + offset * i); + + ubloxSen.senType = (sensorFieldOne && 0x10) >> 5; + ubloxSen.isUsed = (sensorFieldOne && 0x20) >> 6; + ubloxSen.isReady = (sensorFieldOne && 0x30) >> 7; + + ubloxSen.calibStatus = sensorFieldTwo && 0x03; + ubloxSen.timeStatus = (sensorFieldTwo && 0xC) >> 2; + + ubloxSen.badMeas = (sensorFieldThr && 0x01); + ubloxSen.badTag = (sensorFieldThr && 0x02) >> 1; + ubloxSen.missMeas = (sensorFieldThr && 0x04) >> 2; + ubloxSen.noisyMeas = (sensorFieldThr && 0x08) >> 3; + } + + return (SFE_UBLOX_STATUS_SUCCESS); +} + +boolean SFE_UBLOX_GPS::getVehAtt(uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_ATT; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (SFE_UBLOX_STATUS_FAIL); //If command send fails then bail + + checkUblox(); + + vehAtt.roll = extractLong(8); + vehAtt.pitch = extractLong(12); + vehAtt.heading = extractLong(16); + + vehAtt.accRoll = extractLong(20); + vehAtt.accPitch = extractLong(24); + vehAtt.accHeading = extractLong(28); + + return (true); +} diff --git a/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.h b/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.h new file mode 100644 index 0000000..a243865 --- /dev/null +++ b/examples/Series_6_7/Example1_GetPositionAndTime_Series_6_7/SparkFun_Ublox_Arduino_Library_Series_6_7.h @@ -0,0 +1,932 @@ +/* + This is a library written for the u-blox ZED-F9P and NEO-M8P-2 + + Updated: June 16th, 2020 + + This copy includes changes by @blazczak and @geeksville to + provide support for the older series 6 and 7 modules. + + Disclaimer: SparkFun has not verified this copy of the library on either series 6 or 7. + It should work, it looks like it will work, but we have no way of confirming this. + We cannot guarantee that it will work reliably in your application. + + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Original library written by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + + This library handles configuring and handling the responses + from a u-blox GPS module. Works with most modules from u-blox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + Arduino IDE 1.8.5 + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SPARKFUN_UBLOX_ARDUINO_LIBRARY_H +#define SPARKFUN_UBLOX_ARDUINO_LIBRARY_H + +#if (ARDUINO >= 100) +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#include + +//Platform specific configurations + +//Define the size of the I2C buffer based on the platform the user has +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) + +//I2C_BUFFER_LENGTH is defined in Wire.H +#define I2C_BUFFER_LENGTH BUFFER_LENGTH + +#elif defined(__SAMD21G18A__) + +//SAMD21 uses RingBuffer.h +#define I2C_BUFFER_LENGTH SERIAL_BUFFER_SIZE + +//#elif __MK20DX256__ +//Teensy + +#endif + +#ifndef I2C_BUFFER_LENGTH + +//The catch-all default is 32 +#define I2C_BUFFER_LENGTH 32 +//#define I2C_BUFFER_LENGTH 16 //For testing on Artemis + +#endif + +// Define Serial for SparkFun SAMD based boards. +// Boards like the RedBoard Turbo use SerialUSB (not Serial). +// But other boards like the SAMD51 Thing Plus use Serial (not SerialUSB). +// The next nine 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) // If it is not a SAMD51 Thing Plus + #define Serial SerialUSB // Define Serial as SerialUSB + #endif + #endif + #endif +#endif +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//Define a digital pin to aid checksum failure capture and analysis +//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_NACK, // Indicates that the command was unrecognised, invalid or that the module is too busy to respond + 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, // This indicates that a 'set' was successful + SFE_UBLOX_STATUS_DATA_RECEIVED, // This indicates that a 'get' (poll) was successful + SFE_UBLOX_STATUS_I2C_COMM_FAILURE, + SFE_UBLOX_STATUS_DATA_OVERWRITTEN // This is an error - the data was valid but has been or _is being_ overwritten by another packet +} sfe_ublox_status_e; + +// ubxPacket validity +typedef enum +{ + SFE_UBLOX_PACKET_VALIDITY_NOT_VALID, + SFE_UBLOX_PACKET_VALIDITY_VALID, + SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, + SFE_UBLOX_PACKET_NOTACKNOWLEDGED // This indicates that we received a NACK +} sfe_ublox_packet_validity_e; + +// Identify which packet buffer is in use: +// packetCfg (or a custom packet), packetAck or packetBuf +typedef enum +{ + SFE_UBLOX_PACKET_PACKETCFG, + SFE_UBLOX_PACKET_PACKETACK, + SFE_UBLOX_PACKET_PACKETBUF +} sfe_ublox_packet_buffer_e; + +//Registers +const uint8_t UBX_SYNCH_1 = 0xB5; +const uint8_t UBX_SYNCH_2 = 0x62; + +//The following are UBX Class IDs. Descriptions taken from ZED-F9P Interface Description Document page 32, NEO-M8P Interface Description page 145 +const uint8_t UBX_CLASS_NAV = 0x01; //Navigation Results Messages: Position, Speed, Time, Acceleration, Heading, DOP, SVs used +const uint8_t UBX_CLASS_RXM = 0x02; //Receiver Manager Messages: Satellite Status, RTC Status +const uint8_t UBX_CLASS_INF = 0x04; //Information Messages: Printf-Style Messages, with IDs such as Error, Warning, Notice +const uint8_t UBX_CLASS_ACK = 0x05; //Ack/Nak Messages: Acknowledge or Reject messages to UBX-CFG input messages +const uint8_t UBX_CLASS_CFG = 0x06; //Configuration Input Messages: Configure the receiver. +const uint8_t UBX_CLASS_UPD = 0x09; //Firmware Update Messages: Memory/Flash erase/write, Reboot, Flash identification, etc. +const uint8_t UBX_CLASS_MON = 0x0A; //Monitoring Messages: Communication Status, CPU Load, Stack Usage, Task Status +const uint8_t UBX_CLASS_AID = 0x0B; //(NEO-M8P ONLY!!!) AssistNow Aiding Messages: Ephemeris, Almanac, other A-GPS data input +const uint8_t UBX_CLASS_TIM = 0x0D; //Timing Messages: Time Pulse Output, Time Mark Results +const uint8_t UBX_CLASS_ESF = 0x10; //(NEO-M8P ONLY!!!) External Sensor Fusion Messages: External Sensor Measurements and Status Information +const uint8_t UBX_CLASS_MGA = 0x13; //Multiple GNSS Assistance Messages: Assistance data for various GNSS +const uint8_t UBX_CLASS_LOG = 0x21; //Logging Messages: Log creation, deletion, info and retrieval +const uint8_t UBX_CLASS_SEC = 0x27; //Security Feature Messages +const uint8_t UBX_CLASS_HNR = 0x28; //(NEO-M8P ONLY!!!) High Rate Navigation Results Messages: High rate time, position speed, heading +const uint8_t UBX_CLASS_NMEA = 0xF0; //NMEA Strings: standard NMEA strings + +//The following are used for configuration. Descriptions are from the ZED-F9P Interface Description pg 33-34 and NEO-M9N Interface Description pg 47-48 +const uint8_t UBX_CFG_ANT = 0x13; //Antenna Control Settings. Used to configure the antenna control settings +const uint8_t UBX_CFG_BATCH = 0x93; //Get/set data batching configuration. +const uint8_t UBX_CFG_CFG = 0x09; //Clear, Save, and Load Configurations. Used to save current configuration +const uint8_t UBX_CFG_DAT = 0x06; //Set User-defined Datum or The currently defined Datum +const uint8_t UBX_CFG_DGNSS = 0x70; //DGNSS configuration +const uint8_t UBX_CFG_GEOFENCE = 0x69; //Geofencing configuration. Used to configure a geofence +const uint8_t UBX_CFG_GNSS = 0x3E; //GNSS system configuration +const uint8_t UBX_CFG_INF = 0x02; //Depending on packet length, either: poll configuration for one protocol, or information message configuration +const uint8_t UBX_CFG_ITFM = 0x39; //Jamming/Interference Monitor configuration +const uint8_t UBX_CFG_LOGFILTER = 0x47; //Data Logger Configuration +const uint8_t UBX_CFG_MSG = 0x01; //Poll a message configuration, or Set Message Rate(s), or Set Message Rate +const uint8_t UBX_CFG_NAV5 = 0x24; //Navigation Engine Settings. Used to configure the navigation engine including the dynamic model. +const uint8_t UBX_CFG_NAVX5 = 0x23; //Navigation Engine Expert Settings +const uint8_t UBX_CFG_NMEA = 0x17; //Extended NMEA protocol configuration V1 +const uint8_t UBX_CFG_ODO = 0x1E; //Odometer, Low-speed COG Engine Settings +const uint8_t UBX_CFG_PM2 = 0x3B; //Extended power management configuration +const uint8_t UBX_CFG_PMS = 0x86; //Power mode setup +const uint8_t UBX_CFG_PRT = 0x00; //Used to configure port specifics. Polls the configuration for one I/O Port, or Port configuration for UART ports, or Port configuration for USB port, or Port configuration for SPI port, or Port configuration for DDC port +const uint8_t UBX_CFG_PWR = 0x57; //Put receiver in a defined power state +const uint8_t UBX_CFG_RATE = 0x08; //Navigation/Measurement Rate Settings. Used to set port baud rates. +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_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 +const uint8_t UBX_CFG_VALDEL = 0x8C; //Used for config of higher version u-blox modules (ie protocol v27 and above). Deletes values corresponding to provided keys/ provided keys with a transaction +const uint8_t UBX_CFG_VALGET = 0x8B; //Used for config of higher version u-blox modules (ie protocol v27 and above). Configuration Items +const uint8_t UBX_CFG_VALSET = 0x8A; //Used for config of higher version u-blox modules (ie protocol v27 and above). Sets values corresponding to provided key-value pairs/ provided key-value pairs within a transaction. + +//The following are used to enable NMEA messages. Descriptions come from the NMEA messages overview in the ZED-F9P Interface Description +const uint8_t UBX_NMEA_MSB = 0xF0; //All NMEA enable commands have 0xF0 as MSB +const uint8_t UBX_NMEA_DTM = 0x0A; //GxDTM (datum reference) +const uint8_t UBX_NMEA_GAQ = 0x45; //GxGAQ (poll a standard message (if the current talker ID is GA)) +const uint8_t UBX_NMEA_GBQ = 0x44; //GxGBQ (poll a standard message (if the current Talker ID is GB)) +const uint8_t UBX_NMEA_GBS = 0x09; //GxGBS (GNSS satellite fault detection) +const uint8_t UBX_NMEA_GGA = 0x00; //GxGGA (Global positioning system fix data) +const uint8_t UBX_NMEA_GLL = 0x01; //GxGLL (latitude and long, whith time of position fix and status) +const uint8_t UBX_NMEA_GLQ = 0x43; //GxGLQ (poll a standard message (if the current Talker ID is GL)) +const uint8_t UBX_NMEA_GNQ = 0x42; //GxGNQ (poll a standard message (if the current Talker ID is GN)) +const uint8_t UBX_NMEA_GNS = 0x0D; //GxGNS (GNSS fix data) +const uint8_t UBX_NMEA_GPQ = 0x040; //GxGPQ (poll a standard message (if the current Talker ID is GP)) +const uint8_t UBX_NMEA_GRS = 0x06; //GxGRS (GNSS range residuals) +const uint8_t UBX_NMEA_GSA = 0x02; //GxGSA (GNSS DOP and Active satellites) +const uint8_t UBX_NMEA_GST = 0x07; //GxGST (GNSS Pseudo Range Error Statistics) +const uint8_t UBX_NMEA_GSV = 0x03; //GxGSV (GNSS satellites in view) +const uint8_t UBX_NMEA_RMC = 0x04; //GxRMC (Recommended minimum data) +const uint8_t UBX_NMEA_TXT = 0x41; //GxTXT (text transmission) +const uint8_t UBX_NMEA_VLW = 0x0F; //GxVLW (dual ground/water distance) +const uint8_t UBX_NMEA_VTG = 0x05; //GxVTG (course over ground and Ground speed) +const uint8_t UBX_NMEA_ZDA = 0x08; //GxZDA (Time and Date) + +//The following are used to configure the NMEA protocol main talker ID and GSV talker ID +const uint8_t UBX_NMEA_MAINTALKERID_NOTOVERRIDDEN = 0x00; //main talker ID is system dependent +const uint8_t UBX_NMEA_MAINTALKERID_GP = 0x01; //main talker ID is GPS +const uint8_t UBX_NMEA_MAINTALKERID_GL = 0x02; //main talker ID is GLONASS +const uint8_t UBX_NMEA_MAINTALKERID_GN = 0x03; //main talker ID is combined receiver +const uint8_t UBX_NMEA_MAINTALKERID_GA = 0x04; //main talker ID is Galileo +const uint8_t UBX_NMEA_MAINTALKERID_GB = 0x05; //main talker ID is BeiDou +const uint8_t UBX_NMEA_GSVTALKERID_GNSS = 0x00; //GNSS specific Talker ID (as defined by NMEA) +const uint8_t UBX_NMEA_GSVTALKERID_MAIN = 0x01; //use the main Talker ID + +//The following are used to configure INF UBX messages (information messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_INF_CLASS = 0x04; //All INF messages have 0x04 as the class +const uint8_t UBX_INF_DEBUG = 0x04; //ASCII output with debug contents +const uint8_t UBX_INF_ERROR = 0x00; //ASCII output with error contents +const uint8_t UBX_INF_NOTICE = 0x02; //ASCII output with informational contents +const uint8_t UBX_INF_TEST = 0x03; //ASCII output with test contents +const uint8_t UBX_INF_WARNING = 0x01; //ASCII output with warning contents + +//The following are used to configure LOG UBX messages (loggings messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_LOG_CREATE = 0x07; //Create Log File +const uint8_t UBX_LOG_ERASE = 0x03; //Erase Logged Data +const uint8_t UBX_LOG_FINDTIME = 0x0E; //Find index of a log entry based on a given time, or response to FINDTIME requested +const uint8_t UBX_LOG_INFO = 0x08; //Poll for log information, or Log information +const uint8_t UBX_LOG_RETRIEVEPOSEXTRA = 0x0F; //Odometer log entry +const uint8_t UBX_LOG_RETRIEVEPOS = 0x0B; //Position fix log entry +const uint8_t UBX_LOG_RETRIEVESTRING = 0x0D; //Byte string log entry +const uint8_t UBX_LOG_RETRIEVE = 0x09; //Request log data +const uint8_t UBX_LOG_STRING = 0x04; //Store arbitrary string on on-board flash + +//The following are used to configure MGA UBX messages (Multiple GNSS Assistance Messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_MGA_ACK_DATA0 = 0x60; //Multiple GNSS Acknowledge message +const uint8_t UBX_MGA_BDS_EPH = 0x03; //BDS Ephemeris Assistance +const uint8_t UBX_MGA_BDS_ALM = 0x03; //BDS Almanac Assistance +const uint8_t UBX_MGA_BDS_HEALTH = 0x03; //BDS Health Assistance +const uint8_t UBX_MGA_BDS_UTC = 0x03; //BDS UTC Assistance +const uint8_t UBX_MGA_BDS_IONO = 0x03; //BDS Ionospheric Assistance +const uint8_t UBX_MGA_DBD = 0x80; //Either: Poll the Navigation Database, or Navigation Database Dump Entry +const uint8_t UBX_MGA_GAL_EPH = 0x02; //Galileo Ephemeris Assistance +const uint8_t UBX_MGA_GAL_ALM = 0x02; //Galileo Almanac Assitance +const uint8_t UBX_MGA_GAL_TIMOFFSET = 0x02; //Galileo GPS time offset assistance +const uint8_t UBX_MGA_GAL_UTC = 0x02; //Galileo UTC Assistance +const uint8_t UBX_MGA_GLO_EPH = 0x06; //GLONASS Ephemeris Assistance +const uint8_t UBX_MGA_GLO_ALM = 0x06; //GLONASS Almanac Assistance +const uint8_t UBX_MGA_GLO_TIMEOFFSET = 0x06; //GLONASS Auxiliary Time Offset Assistance +const uint8_t UBX_MGA_GPS_EPH = 0x00; //GPS Ephemeris Assistance +const uint8_t UBX_MGA_GPS_ALM = 0x00; //GPS Almanac Assistance +const uint8_t UBX_MGA_GPS_HEALTH = 0x00; //GPS Health Assistance +const uint8_t UBX_MGA_GPS_UTC = 0x00; //GPS UTC Assistance +const uint8_t UBX_MGA_GPS_IONO = 0x00; //GPS Ionosphere Assistance +const uint8_t UBX_MGA_INI_POS_XYZ = 0x40; //Initial Position Assistance +const uint8_t UBX_MGA_INI_POS_LLH = 0x40; //Initial Position Assitance +const uint8_t UBX_MGA_INI_TIME_UTC = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_TIME_GNSS = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_CLKD = 0x40; //Initial Clock Drift Assitance +const uint8_t UBX_MGA_INI_FREQ = 0x40; //Initial Frequency Assistance +const uint8_t UBX_MGA_INI_EOP = 0x40; //Earth Orientation Parameters Assistance +const uint8_t UBX_MGA_QZSS_EPH = 0x05; //QZSS Ephemeris Assistance +const uint8_t UBX_MGA_QZSS_ALM = 0x05; //QZSS Almanac Assistance +const uint8_t UBX_MGA_QZAA_HEALTH = 0x05; //QZSS Health Assistance + +//The following are used to configure the MON UBX messages (monitoring messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35) +const uint8_t UBX_MON_COMMS = 0x36; //Comm port information +const uint8_t UBX_MON_GNSS = 0x28; //Information message major GNSS selection +const uint8_t UBX_MON_HW2 = 0x0B; //Extended Hardware Status +const uint8_t UBX_MON_HW3 = 0x37; //HW I/O pin information +const uint8_t UBX_MON_HW = 0x09; //Hardware Status +const uint8_t UBX_MON_IO = 0x02; //I/O Subsystem Status +const uint8_t UBX_MON_MSGPP = 0x06; //Message Parse and Process Status +const uint8_t UBX_MON_PATCH = 0x27; //Output information about installed patches +const uint8_t UBX_MON_RF = 0x38; //RF information +const uint8_t UBX_MON_RXBUF = 0x07; //Receiver Buffer Status +const uint8_t UBX_MON_RXR = 0x21; //Receiver Status Information +const uint8_t UBX_MON_TXBUF = 0x08; //Transmitter Buffer Status. Used for query tx buffer size/state. +const uint8_t UBX_MON_VER = 0x04; //Receiver/Software Version. Used for obtaining Protocol Version. + +//The following are used to configure the NAV UBX messages (navigation results messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35-36) +const uint8_t UBX_NAV_ATT = 0x05; //Vehicle "Attitude" Solution +const uint8_t UBX_NAV_CLOCK = 0x22; //Clock Solution +const uint8_t UBX_NAV_DOP = 0x04; //Dilution of precision +const uint8_t UBX_NAV_EOE = 0x61; //End of Epoch +const uint8_t UBX_NAV_GEOFENCE = 0x39; //Geofencing status. Used to poll the geofence status +const uint8_t UBX_NAV_HPPOSECEF = 0x13; //High Precision Position Solution in ECEF. Used to find our positional accuracy (high precision). +const uint8_t UBX_NAV_HPPOSLLH = 0x14; //High Precision Geodetic Position Solution. Used for obtaining lat/long/alt in high precision +const uint8_t UBX_NAV_ODO = 0x09; //Odometer Solution +const uint8_t UBX_NAV_ORB = 0x34; //GNSS Orbit Database Info +const uint8_t UBX_NAV_POSECEF = 0x01; //Position Solution in ECEF +const uint8_t UBX_NAV_POSLLH = 0x02; //Geodetic Position Solution +const uint8_t UBX_NAV_PVT = 0x07; //All the things! Position, velocity, time, PDOP, height, h/v accuracies, number of satellites. Navigation Position Velocity Time Solution. +const uint8_t UBX_NAV_RELPOSNED = 0x3C; //Relative Positioning Information in NED frame +const uint8_t UBX_NAV_RESETODO = 0x10; //Reset odometer +const uint8_t UBX_NAV_SAT = 0x35; //Satellite Information +const uint8_t UBX_NAV_SIG = 0x43; //Signal Information +const uint8_t UBX_NAV_STATUS = 0x03; //Receiver Navigation Status +const uint8_t UBX_NAV_SVIN = 0x3B; //Survey-in data. Used for checking Survey In status +const uint8_t UBX_NAV_TIMEBDS = 0x24; //BDS Time Solution +const uint8_t UBX_NAV_TIMEGAL = 0x25; //Galileo Time Solution +const uint8_t UBX_NAV_TIMEGLO = 0x23; //GLO Time Solution +const uint8_t UBX_NAV_TIMEGPS = 0x20; //GPS Time Solution +const uint8_t UBX_NAV_TIMELS = 0x26; //Leap second event information +const uint8_t UBX_NAV_TIMEUTC = 0x21; //UTC Time Solution +const uint8_t UBX_NAV_VELECEF = 0x11; //Velocity Solution in ECEF +const uint8_t UBX_NAV_VELNED = 0x12; //Velocity Solution in NED + +//The following are used to configure the RXM UBX messages (receiver manager messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_RXM_MEASX = 0x14; //Satellite Measurements for RRLP +const uint8_t UBX_RXM_PMREQ = 0x41; //Requests a Power Management task (two differenent packet sizes) +const uint8_t UBX_RXM_RAWX = 0x15; //Multi-GNSS Raw Measurement Data +const uint8_t UBX_RXM_RLM = 0x59; //Galileo SAR Short-RLM report (two different packet sizes) +const uint8_t UBX_RXM_RTCM = 0x32; //RTCM input status +const uint8_t UBX_RXM_SFRBX = 0x13; //Boradcast Navigation Data Subframe + +//The following are used to configure the SEC UBX messages (security feature messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_SEC_UNIQID = 0x03; //Unique chip ID + +//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 UPD UBX messages (firmware update messages). Descriptions from UBX messages overview (ZED-F9P Interface Description Document page 36) +const uint8_t UBX_UPD_SOS = 0x14; //Poll Backup Fil Restore Status, Create Backup File in Flash, Clear Backup File in Flash, Backup File Creation Acknowledge, System Restored from Backup + +//The following are used to enable RTCM messages +const uint8_t UBX_RTCM_MSB = 0xF5; //All RTCM enable commands have 0xF5 as MSB +const uint8_t UBX_RTCM_1005 = 0x05; //Stationary RTK reference ARP +const uint8_t UBX_RTCM_1074 = 0x4A; //GPS MSM4 +const uint8_t UBX_RTCM_1077 = 0x4D; //GPS MSM7 +const uint8_t UBX_RTCM_1084 = 0x54; //GLONASS MSM4 +const uint8_t UBX_RTCM_1087 = 0x57; //GLONASS MSM7 +const uint8_t UBX_RTCM_1094 = 0x5E; //Galileo MSM4 +const uint8_t UBX_RTCM_1097 = 0x61; //Galileo MSM7 +const uint8_t UBX_RTCM_1124 = 0x7C; //BeiDou MSM4 +const uint8_t UBX_RTCM_1127 = 0x7F; //BeiDou MSM7 +const uint8_t UBX_RTCM_1230 = 0xE6; //GLONASS code-phase biases, set to once every 10 seconds +const uint8_t UBX_RTCM_4072_0 = 0xFE; //Reference station PVT (ublox proprietary RTCM message) +const uint8_t UBX_RTCM_4072_1 = 0xFD; //Additional reference station information (ublox proprietary RTCM message) + +const uint8_t UBX_ACK_NACK = 0x00; +const uint8_t UBX_ACK_ACK = 0x01; +const uint8_t UBX_ACK_NONE = 0x02; //Not a real value + +// The following constants are used to get External Sensor Measurements and Status +// Information. +const uint8_t UBX_ESF_MEAS = 0x02; +const uint8_t UBX_ESF_RAW = 0x03; +const uint8_t UBX_ESF_STATUS = 0x10; +const uint8_t UBX_ESF_INS = 0x15; //36 bytes + +const uint8_t SVIN_MODE_DISABLE = 0x00; +const uint8_t SVIN_MODE_ENABLE = 0x01; + +//The following consts are used to configure the various ports and streams for those ports. See -CFG-PRT. +const uint8_t COM_PORT_I2C = 0; +const uint8_t COM_PORT_UART1 = 1; +const uint8_t COM_PORT_UART2 = 2; +const uint8_t COM_PORT_USB = 3; +const uint8_t COM_PORT_SPI = 4; + +const uint8_t COM_TYPE_UBX = (1 << 0); +const uint8_t COM_TYPE_NMEA = (1 << 1); +const uint8_t COM_TYPE_RTCM3 = (1 << 5); + +//The following consts are used to generate KEY values for the advanced protocol functions of VELGET/SET/DEL +const uint8_t VAL_SIZE_1 = 0x01; //One bit +const uint8_t VAL_SIZE_8 = 0x02; //One byte +const uint8_t VAL_SIZE_16 = 0x03; //Two bytes +const uint8_t VAL_SIZE_32 = 0x04; //Four bytes +const uint8_t VAL_SIZE_64 = 0x05; //Eight bytes + +//These are the Bitfield layers definitions for the UBX-CFG-VALSET message (not to be confused with Bitfield deviceMask in UBX-CFG-CFG) +const uint8_t VAL_LAYER_RAM = (1 << 0); +const uint8_t VAL_LAYER_BBR = (1 << 1); +const uint8_t VAL_LAYER_FLASH = (1 << 2); + +//Below are various Groups, IDs, and sizes for various settings +//These can be used to call getVal/setVal/delVal +const uint8_t VAL_GROUP_I2COUTPROT = 0x72; +const uint8_t VAL_GROUP_I2COUTPROT_SIZE = VAL_SIZE_1; //All fields in I2C group are currently 1 bit + +const uint8_t VAL_ID_I2COUTPROT_UBX = 0x01; +const uint8_t VAL_ID_I2COUTPROT_NMEA = 0x02; +const uint8_t VAL_ID_I2COUTPROT_RTCM3 = 0x03; + +const uint8_t VAL_GROUP_I2C = 0x51; +const uint8_t VAL_GROUP_I2C_SIZE = VAL_SIZE_8; //All fields in I2C group are currently 1 byte + +const uint8_t VAL_ID_I2C_ADDRESS = 0x01; + +// Configuration Sub-Section mask definitions for saveConfigSelective (UBX-CFG-CFG) +const uint32_t VAL_CFG_SUBSEC_IOPORT = 0x00000001; // ioPort - communications port settings (causes IO system reset!) +const uint32_t VAL_CFG_SUBSEC_MSGCONF = 0x00000002; // msgConf - message configuration +const uint32_t VAL_CFG_SUBSEC_INFMSG = 0x00000004; // infMsg - INF message configuration +const uint32_t VAL_CFG_SUBSEC_NAVCONF = 0x00000008; // navConf - navigation configuration +const uint32_t VAL_CFG_SUBSEC_RXMCONF = 0x00000010; // rxmConf - receiver manager configuration +const uint32_t VAL_CFG_SUBSEC_SENCONF = 0x00000100; // senConf - sensor interface configuration (requires protocol 19+) +const uint32_t VAL_CFG_SUBSEC_RINVCONF = 0x00000200; // rinvConf - remove inventory configuration +const uint32_t VAL_CFG_SUBSEC_ANTCONF = 0x00000400; // antConf - antenna configuration +const uint32_t VAL_CFG_SUBSEC_LOGCONF = 0x00000800; // logConf - logging configuration +const uint32_t VAL_CFG_SUBSEC_FTSCONF = 0x00001000; // ftsConf - FTS configuration (FTS products only) + +enum dynModel // Possible values for the dynamic platform model, which provide more accuract position output for the situation. Description extracted from ZED-F9P Integration Manual +{ + DYN_MODEL_PORTABLE = 0, //Applications with low acceleration, e.g. portable devices. Suitable for most situations. + // 1 is not defined + DYN_MODEL_STATIONARY = 2, //Used in timing applications (antenna must be stationary) or other stationary applications. Velocity restricted to 0 m/s. Zero dynamics assumed. + DYN_MODEL_PEDESTRIAN, //Applications with low acceleration and speed, e.g. how a pedestrian would move. Low acceleration assumed. + DYN_MODEL_AUTOMOTIVE, //Used for applications with equivalent dynamics to those of a passenger car. Low vertical acceleration assumed + DYN_MODEL_SEA, //Recommended for applications at sea, with zero vertical velocity. Zero vertical velocity assumed. Sea level assumed. + DYN_MODEL_AIRBORNE1g, //Airborne <1g acceleration. Used for applications with a higher dynamic range and greater vertical acceleration than a passenger car. No 2D position fixes supported. + DYN_MODEL_AIRBORNE2g, //Airborne <2g acceleration. Recommended for typical airborne environments. No 2D position fixes supported. + DYN_MODEL_AIRBORNE4g, //Airborne <4g acceleration. Only recommended for extremely dynamic environments. No 2D position fixes supported. + DYN_MODEL_WRIST, // Not supported in protocol versions less than 18. Only recommended for wrist worn applications. Receiver will filter out arm motion. + DYN_MODEL_BIKE, // Supported in protocol versions 19.2 +}; + +#ifndef MAX_PAYLOAD_SIZE + +#define MAX_PAYLOAD_SIZE 256 //We need ~220 bytes for getProtocolVersion on most ublox modules +//#define MAX_PAYLOAD_SIZE 768 //Worst case: UBX_CFG_VALSET packet with 64 keyIDs each with 64 bit values + +#endif + +//-=-=-=-=- UBX binary specific variables +typedef struct +{ + uint8_t cls; + uint8_t 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; + uint8_t checksumA; //Given to us from 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 +} ubxPacket; + +// Struct to hold the results returned by getGeofenceState (returned by UBX-NAV-GEOFENCE) +typedef struct +{ + uint8_t status; // Geofencing status: 0 - Geofencing not available or not reliable; 1 - Geofencing active + uint8_t numFences; // Number of geofences + uint8_t combState; // Combined (logical OR) state of all geofences: 0 - Unknown; 1 - Inside; 2 - Outside + uint8_t states[4]; // Geofence states: 0 - Unknown; 1 - Inside; 2 - Outside +} geofenceState; + +// Struct to hold the current geofence parameters +typedef struct +{ + uint8_t numFences; // Number of active geofences + int32_t lats[4]; // Latitudes of geofences (in degrees * 10^-7) + int32_t longs[4]; // Longitudes of geofences (in degrees * 10^-7) + uint32_t rads[4]; // Radii of geofences (in m * 10^-2) +} geofenceParams; + +class SFE_UBLOX_GPS +{ +public: + SFE_UBLOX_GPS(void); + +// A default of 250ms for maxWait seems fine for I2C but is not enough for SerialUSB. +// If you know you are only going to be using I2C / Qwiic communication, you can +// safely reduce defaultMaxWait to 250. +#ifndef defaultMaxWait // Let's allow the user to define their own value if they want to +#define defaultMaxWait 1100 +#endif + + //By default use the default I2C address, and use Wire port + boolean begin(TwoWire &wirePort = Wire, uint8_t deviceAddress = 0x42); //Returns true if module is detected + //serialPort needs to be perviously initialized to correct baud rate + boolean begin(Stream &serialPort); //Returns true if module is detected + + //Returns true if device answers on _gpsI2Caddress address or via Serial + //maxWait is only used for Serial + boolean isConnected(uint16_t maxWait = 1100); + + //Changed in V1.8.1: provides backward compatibility for the examples that call checkUblox directly + //Will default to using packetCfg to look for explicit autoPVT packets so they get processed correctly by processUBX + boolean checkUblox(uint8_t requestedClass = UBX_CLASS_NAV, uint8_t requestedID = UBX_NAV_PVT); //Checks module with user selected commType + + boolean checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for I2C polling of data, passing any new bytes to process() + boolean checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for serial polling of data, passing any new bytes to process() + + void process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Processes NMEA and UBX binary sentences one byte at a time + void processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Given a character, file it away into the uxb packet structure + void processRTCMframe(uint8_t incoming); //Monitor the incoming bytes for start and length bytes + void processRTCM(uint8_t incoming) __attribute__((weak)); //Given rtcm byte, do something with it. User can overwrite if desired to pipe bytes to radio, internet, etc. + + 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 + sfe_ublox_status_e sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait = defaultMaxWait); //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 + + void factoryReset(); //Send factory reset sequence (i.e. load "default" configuration and perform hardReset) + void hardReset(); //Perform a reset leading to a cold start (zero info start-up) + + boolean setI2CAddress(uint8_t deviceAddress, uint16_t maxTime = 250); //Changes the I2C address of the u-blox module + void setSerialRate(uint32_t baudrate, uint8_t uartPort = COM_PORT_UART1, uint16_t maxTime = defaultMaxWait); //Changes the serial baud rate of the u-blox module, uartPort should be COM_PORT_UART1/2 + void setNMEAOutputPort(Stream &nmeaOutputPort); //Sets the internal variable for the port to direct NMEA characters to + + boolean setNavigationFrequency(uint8_t navFreq, uint16_t maxWait = defaultMaxWait); //Set the number of nav solutions sent per second + uint8_t getNavigationFrequency(uint16_t maxWait = defaultMaxWait); //Get the number of nav solutions sent per second currently being output by module + boolean saveConfiguration(uint16_t maxWait = defaultMaxWait); //Save current configuration to flash and BBR (battery backed RAM) + boolean factoryDefault(uint16_t maxWait = defaultMaxWait); //Reset module to factory defaults + boolean saveConfigSelective(uint32_t configMask, uint16_t maxWait = defaultMaxWait); //Save the selected configuration sub-sections to flash and BBR (battery backed RAM) + + sfe_ublox_status_e waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //Poll the module until a config packet and an ACK is received + sfe_ublox_status_e waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //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 + + boolean assumeAutoPVT(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and PVT is send cyclically already + boolean setAutoPVT(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency + boolean getPVT(uint16_t maxWait = getPVTmaxWait); //Query module for latest group of datums and load global vars: lat, long, alt, speed, SIV, accuracies, etc. If autoPVT is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new PVT is available. + boolean getTimeData(uint16_t maxWait = getPVTmaxWait); //Query module for latest time data. Calls getPVT or getTIMEUTC depending on which module is attached. + boolean getPositionData(uint16_t maxWait = getPVTmaxWait); //Query module for latest position data. Calls getPVT or getPOSLLH depending on which module is attached. + boolean getTIMEUTC(uint16_t maxWait = getPVTmaxWait); //Query module for current time (for use with older chip series). Returns true if new data is available. + boolean getPOSLLH(uint16_t maxWait = getPVTmaxWait); //Query module for current position (for use with older chip series). Returns true if new data is available. + + boolean setAutoPVT(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean 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. Returns 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 (dimensionless) + uint16_t getYear(uint16_t maxWait = getPVTmaxWait); + uint8_t getMonth(uint16_t maxWait = getPVTmaxWait); + uint8_t getDay(uint16_t maxWait = getPVTmaxWait); + uint8_t getHour(uint16_t maxWait = getPVTmaxWait); + uint8_t getMinute(uint16_t maxWait = getPVTmaxWait); + uint8_t getSecond(uint16_t maxWait = getPVTmaxWait); + uint16_t getMillisecond(uint16_t maxWait = getPVTmaxWait); + int32_t getNanosecond(uint16_t maxWait = getPVTmaxWait); + uint32_t getTimeOfWeek(uint16_t maxWait = getPVTmaxWait); + + int32_t getHighResLatitude(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getHighResLatitudeHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getHighResLongitude(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getHighResLongitudeHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getElipsoid(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getElipsoidHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getMeanSeaLevel(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getMeanSeaLevelHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getGeoidSeparation(uint16_t maxWait = getHPPOSLLHmaxWait); + uint32_t getHorizontalAccuracy(uint16_t maxWait = getHPPOSLLHmaxWait); + uint32_t getVerticalAccuracy(uint16_t maxWait = getHPPOSLLHmaxWait); + + //Port configurations + boolean setPortOutput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setPortInput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof + boolean getPortSettings(uint8_t portID, uint16_t maxWait = defaultMaxWait); //Returns the current protocol bits in the UBX-CFG-PRT command for a given port + + boolean setI2COutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure I2C port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUART1Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART1 port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUART2Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART2 port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUSBOutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure USB port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setSPIOutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure SPI port to output UBX, NMEA, RTCM3 or a combination thereof + + //Functions to turn on/off message types for a given port ID (see COM_PORT_I2C, etc above) + boolean configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); + boolean enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + boolean disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + boolean enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + boolean disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + boolean enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); //Given a message number turns on a message ID for output over given PortID + boolean disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait = defaultMaxWait); //Turn off given RTCM message from a given port + + //General configuration (used only on protocol v27 and higher - ie, ZED-F9P) + //It is probably safe to assume that users of the ZED-F9P will be using I2C / Qwiic. + //If they are using Serial then the higher baud rate will also help. So let's leave maxWait set to 250ms. + uint8_t getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint8_t getVal8(uint32_t keyID, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint8_t setVal(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 8-bit value at a given group/id/size location + uint8_t setVal16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 32-bit value at a given group/id/size location + uint8_t newCfgValset8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 8-bit value + uint8_t newCfgValset16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 16-bit value + uint8_t newCfgValset32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 32-bit value + uint8_t addCfgValset8(uint32_t keyID, uint8_t value); //Add a new KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset16(uint32_t keyID, uint16_t value); //Add a new KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset32(uint32_t keyID, uint32_t value); //Add a new KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t sendCfgValset8(uint32_t keyID, uint8_t value, uint16_t maxWait = 250); //Add the final KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset16(uint32_t keyID, uint16_t value, uint16_t maxWait = 250); //Add the final KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset32(uint32_t keyID, uint32_t value, uint16_t maxWait = 250); //Add the final KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + + //Functions used for RTK and base station setup + //It is probably safe to assume that users of the RTK will be using I2C / Qwiic. So let's leave maxWait set to 250ms. + boolean getSurveyMode(uint16_t maxWait = 250); //Get the current TimeMode3 settings + boolean setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); //Control survey in mode + boolean enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); //Begin Survey-In for NEO-M8P + boolean disableSurveyMode(uint16_t maxWait = 250); //Stop Survey-In mode + + boolean getSurveyStatus(uint16_t maxWait); //Reads survey in status and sets the global variables + + uint32_t getPositionAccuracy(uint16_t maxWait = 1100); //Returns the 3D accuracy of the current high-precision fix, in mm. Supported on NEO-M8P, ZED-F9P, + + uint8_t getProtocolVersionHigh(uint16_t maxWait = 500); //Returns the PROTVER XX.00 from UBX-MON-VER register + uint8_t getProtocolVersionLow(uint16_t maxWait = 500); //Returns the PROTVER 00.XX from UBX-MON-VER register + boolean getProtocolVersion(uint16_t maxWait = 500); //Queries module, loads low/high bytes + + boolean getRELPOSNED(uint16_t maxWait = 1100); //Get Relative Positioning Information of the NED frame + + void enableDebugging(Stream &debugPort = Serial, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + 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 powerSaveMode(bool power_save = true, uint16_t maxWait = 1100); + uint8_t getPowerSaveMode(uint16_t maxWait = 1100); // Returns 255 if the sendCommand fails + + //Change the dynamic platform model using UBX-CFG-NAV5 + boolean setDynamicModel(dynModel newDynamicModel = DYN_MODEL_PORTABLE, uint16_t maxWait = 1100); + uint8_t getDynamicModel(uint16_t maxWait = 1100); // Get the dynamic model - returns 255 if the sendCommand fails + + boolean getEsfInfo(uint16_t maxWait = 1100); + boolean getEsfIns(uint16_t maxWait = 1100); + boolean getEsfDataInfo(uint16_t maxWait = 1100); + boolean getEsfRawDataInfo(uint16_t maxWait = 1100); + sfe_ublox_status_e getSensState(uint8_t sensor, uint16_t maxWait = 1100); + boolean getVehAtt(uint16_t maxWait = 1100); + + //Survey-in specific controls + struct svinStructure + { + boolean active; + boolean valid; + uint16_t observationTime; + float meanAccuracy; + } svin; + + //Relative Positioning Info in NED frame specific controls + struct frelPosInfoStructure + { + uint16_t refStationID; + + float relPosN; + float relPosE; + float relPosD; + + long relPosLength; + long relPosHeading; + + int8_t relPosHPN; + int8_t relPosHPE; + int8_t relPosHPD; + int8_t relPosHPLength; + + float accN; + float accE; + float accD; + + bool gnssFixOk; + bool diffSoln; + bool relPosValid; + uint8_t carrSoln; + bool isMoving; + bool refPosMiss; + bool refObsMiss; + } relPosInfo; + + //The major datums we want to globally store + uint16_t gpsYear; + uint8_t gpsMonth; + uint8_t gpsDay; + uint8_t gpsHour; + uint8_t gpsMinute; + uint8_t gpsSecond; + uint16_t gpsMillisecond; + int32_t gpsNanosecond; + + int32_t latitude; //Degrees * 10^-7 (more accurate than floats) + int32_t longitude; //Degrees * 10^-7 (more accurate than floats) + int32_t altitude; //Number of mm above ellipsoid + int32_t altitudeMSL; //Number of mm above Mean Sea Level + uint8_t SIV; //Number of satellites used in position solution + uint8_t fixType; //Tells us when we have a solution aka lock + uint8_t carrierSolution; //Tells us when we have an RTK float/fixed solution + int32_t groundSpeed; //mm/s + int32_t headingOfMotion; //degrees * 10^-5 + uint16_t pDOP; //Positional dilution of precision * 10^-2 (dimensionless) + uint8_t versionLow; //Loaded from getProtocolVersion(). + uint8_t versionHigh; + + uint32_t timeOfWeek; // ms + int32_t highResLatitude; // Degrees * 10^-7 + int32_t highResLongitude; // Degrees * 10^-7 + int32_t elipsoid; // Height above ellipsoid in mm (Typo! Should be eLLipsoid! **Uncorrected for backward-compatibility.**) + int32_t meanSeaLevel; // Height above mean sea level in mm + int32_t geoidSeparation; // This seems to only be provided in NMEA GGA and GNS messages + uint32_t horizontalAccuracy; // mm * 10^-1 (i.e. 0.1mm) + uint32_t verticalAccuracy; // mm * 10^-1 (i.e. 0.1mm) + int8_t elipsoidHp; // High precision component of the height above ellipsoid in mm * 10^-1 (Deliberate typo! Should be eLLipsoidHp!) + int8_t meanSeaLevelHp; // High precision component of Height above mean sea level in mm * 10^-1 + int8_t highResLatitudeHp; // High precision component of latitude: Degrees * 10^-9 + int8_t highResLongitudeHp; // High precision component of longitude: Degrees * 10^-9 + + uint16_t rtcmFrameCounter = 0; //Tracks the type of incoming byte inside RTCM frame + +#define DEF_NUM_SENS 7 + struct deadReckData + { + uint8_t version; + uint8_t fusionMode; + + uint8_t xAngRateVald; + uint8_t yAngRateVald; + uint8_t zAngRateVald; + uint8_t xAccelVald; + uint8_t yAccelVald; + uint8_t zAccelVald; + + int32_t xAngRate; + int32_t yAngRate; + int32_t zAngRate; + + int32_t xAccel; + int32_t yAccel; + int32_t zAccel; + + // The array size is based on testing directly on M8U and F9R + uint32_t rawData; + uint32_t rawDataType; + uint32_t rawTStamp; + + uint32_t data[DEF_NUM_SENS]; + uint32_t dataType[DEF_NUM_SENS]; + uint32_t dataTStamp[DEF_NUM_SENS]; + } imuMeas; + + struct indivImuData + { + + uint8_t numSens; + + uint8_t senType; + boolean isUsed; + boolean isReady; + uint8_t calibStatus; + uint8_t timeStatus; + + uint8_t freq; // Hz + + boolean badMeas; + boolean badTag; + boolean missMeas; + boolean noisyMeas; + } ubloxSen; + + struct vehicleAttitude + { + // All values in degrees + int32_t roll; + int32_t pitch; + int32_t heading; + uint32_t accRoll; + uint32_t accPitch; + uint32_t accHeading; + } vehAtt; + +private: + //Depending on the sentence type the processor will load characters into different arrays + enum SentenceTypes + { + NONE = 0, + NMEA, + UBX, + RTCM + } currentSentence = NONE; + + //Depending on the ubx binary response class, store binary responses into different places + enum classTypes + { + CLASS_NONE = 0, + CLASS_ACK, + CLASS_NOT_AN_ACK + } ubxFrameClass = CLASS_NONE; + + enum commTypes + { + COMM_TYPE_I2C = 0, + COMM_TYPE_SERIAL, + COMM_TYPE_SPI + } commType = COMM_TYPE_I2C; //Controls which port we look to for incoming bytes + + //Functions + boolean checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass = 255, uint8_t requestedID = 255); //Checks module with user selected commType + uint32_t extractLong(uint8_t spotToStart); //Combine four bytes from payload into long + uint16_t extractInt(uint8_t spotToStart); //Combine two bytes from payload into int + uint8_t extractByte(uint8_t spotToStart); //Get byte from payload + int8_t extractSignedChar(uint8_t spotToStart); //Get signed 8-bit value from payload + void addToChecksum(uint8_t incoming); //Given an incoming byte, adjust rollingChecksumA/B + + //Variables + TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware + Stream *_serialPort; //The generic connection to user's chosen Serial hardware + Stream *_nmeaOutputPort = NULL; //The user can assign an output port to print NMEA sentences if they wish + Stream *_debugSerial; //The stream to send debug messages to if enabled + + uint8_t _gpsI2Caddress = 0x42; //Default 7-bit unshifted address of the ublox 6/7/8/M8/F9 series + //This can be changed using the ublox configuration software + + boolean _printDebug = false; //Flag to print the serial commands we are sending to the Serial port for debug + boolean _printLimitedDebug = false; //Flag to print limited debug messages. Useful for I2C debugging or high navigation rates + + //The packet buffers + //These are pointed at from within the ubxPacket + uint8_t payloadAck[2]; // Holds the requested ACK/NACK + uint8_t payloadCfg[MAX_PAYLOAD_SIZE]; // Holds the requested data packet + uint8_t payloadBuf[2]; // Temporary buffer used to screen incoming packets or dump unrequested packets + + //Init the packet structures and init them with pointers to the payloadAck, payloadCfg and payloadBuf arrays + ubxPacket packetAck = {0, 0, 0, 0, 0, payloadAck, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetCfg = {0, 0, 0, 0, 0, payloadCfg, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetBuf = {0, 0, 0, 0, 0, payloadBuf, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + + //Flag if this packet is unrequested (and so should be ignored and not copied into packetCfg or packetAck) + boolean ignoreThisPayload = false; + + //Identify which buffer is in use + //Data is stored in packetBuf until the requested class and ID can be validated + //If a match is seen, data is diverted into packetAck or packetCfg + sfe_ublox_packet_buffer_e activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + + //Limit checking of new data to every X ms + //If we are expecting an update every X Hz then we should check every half that amount of time + //Otherwise we may block ourselves from seeing new data + uint8_t i2cPollingWait = 100; //Default to 100ms. Adjusted when user calls setNavigationFrequency() + + unsigned long lastCheck = 0; + boolean autoPVT = false; //Whether autoPVT is enabled or not + boolean autoPVTImplicitUpdate = true; // Whether autoPVT is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + uint16_t ubxFrameCounter; //It counts all UBX frame. [Fixed header(2bytes), CLS(1byte), ID(1byte), length(2bytes), payload(x bytes), checksums(2bytes)] + + uint8_t rollingChecksumA; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + uint8_t rollingChecksumB; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + + //Create bit field for staleness of each datum in PVT we want to monitor + //moduleQueried.latitude goes true each time we call getPVT() + //This reduces the number of times we have to call getPVT as this can take up to ~1s per read + //depending on update rate + struct + { + uint32_t gpsiTOW : 1; + uint32_t gpsYear : 1; + uint32_t gpsMonth : 1; + uint32_t gpsDay : 1; + uint32_t gpsHour : 1; + uint32_t gpsMinute : 1; + uint32_t gpsSecond : 1; + uint32_t gpsNanosecond : 1; + + uint32_t all : 1; + uint32_t longitude : 1; + uint32_t latitude : 1; + uint32_t altitude : 1; + uint32_t altitudeMSL : 1; + uint32_t SIV : 1; + uint32_t fixType : 1; + uint32_t carrierSolution : 1; + uint32_t groundSpeed : 1; + uint32_t headingOfMotion : 1; + uint32_t pDOP : 1; + uint32_t versionNumber : 1; + } moduleQueried; + + struct + { + uint16_t all : 1; + uint16_t timeOfWeek : 1; + uint16_t highResLatitude : 1; + uint16_t highResLongitude : 1; + uint16_t elipsoid : 1; + uint16_t meanSeaLevel : 1; + uint16_t geoidSeparation : 1; // Redundant but kept for backward-compatibility + uint16_t horizontalAccuracy : 1; + uint16_t verticalAccuracy : 1; + uint16_t elipsoidHp : 1; + uint16_t meanSeaLevelHp : 1; + uint16_t highResLatitudeHp : 1; + uint16_t highResLongitudeHp : 1; + } highResModuleQueried; + + uint16_t rtcmLen = 0; +}; + +#endif diff --git a/examples/ZED-F9P/Example10_GetHighPrecisionPositionAndAccuracy/Example10_GetHighPrecisionPositionAndAccuracy.ino b/examples/ZED-F9P/Example10_GetHighPrecisionPositionAndAccuracy/Example10_GetHighPrecisionPositionAndAccuracy.ino new file mode 100644 index 0000000..2cf277a --- /dev/null +++ b/examples/ZED-F9P/Example10_GetHighPrecisionPositionAndAccuracy/Example10_GetHighPrecisionPositionAndAccuracy.ino @@ -0,0 +1,154 @@ +/* + Get the high precision geodetic solution for latitude and longitude + By: Nathan Seidle + Modified by: Steven Rowland and Paul Clark + SparkFun Electronics + Date: April 17th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to inspect the accuracy of the high-precision + positional solution. Please see below for information about the units. + + 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 + + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + + Wire.begin(); + + //myGPS.enableDebugging(Serial); + + if (myGPS.begin(Wire) == 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. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.setNavigationFrequency(20); //Set output to 20 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 +} + +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 > 1000) + { + lastTime = millis(); //Update the timer + + // getHighResLatitude: returns the latitude from HPPOSLLH as an int32_t in degrees * 10^-7 + // getHighResLatitudeHp: returns the high resolution component of latitude from HPPOSLLH as an int8_t in degrees * 10^-9 + // getHighResLongitude: returns the longitude from HPPOSLLH as an int32_t in degrees * 10^-7 + // getHighResLongitudeHp: returns the high resolution component of longitude from HPPOSLLH as an int8_t in degrees * 10^-9 + // getElipsoid: returns the height above ellipsoid as an int32_t in mm + // getElipsoidHp: returns the high resolution component of the height above ellipsoid as an int8_t in mm * 10^-1 + // getMeanSeaLevel: returns the height above mean sea level as an int32_t in mm + // getMeanSeaLevelHp: returns the high resolution component of the height above mean sea level as an int8_t in mm * 10^-1 + // getHorizontalAccuracy: returns the horizontal accuracy estimate from HPPOSLLH as an uint32_t in mm * 10^-1 + + // If you want to use the high precision latitude and longitude with the full 9 decimal places + // you will need to use a 64-bit double - which is not supported on all platforms + + // To allow this example to run on standard platforms, we cheat by converting lat and lon to integer and fractional degrees + + // The high resolution altitudes can be converted into standard 32-bit float + + // First, let's collect the position data + int32_t latitude = myGPS.getHighResLatitude(); + int8_t latitudeHp = myGPS.getHighResLatitudeHp(); + int32_t longitude = myGPS.getHighResLongitude(); + int8_t longitudeHp = myGPS.getHighResLongitudeHp(); + int32_t ellipsoid = myGPS.getElipsoid(); + int8_t ellipsoidHp = myGPS.getElipsoidHp(); + int32_t msl = myGPS.getMeanSeaLevel(); + int8_t mslHp = myGPS.getMeanSeaLevelHp(); + uint32_t accuracy = myGPS.getHorizontalAccuracy(); + + // Defines storage for the lat and lon units integer and fractional parts + int32_t lat_int; // Integer part of the latitude in degrees + int32_t lat_frac; // Fractional part of the latitude + int32_t lon_int; // Integer part of the longitude in degrees + int32_t lon_frac; // Fractional part of the longitude + + // Calculate the latitude and longitude integer and fractional parts + lat_int = latitude / 10000000; // Convert latitude from degrees * 10^-7 to Degrees + lat_frac = latitude - (lat_int * 10000000); // Calculate the fractional part of the latitude + lat_frac = (lat_frac * 100) + latitudeHp; // Now add the high resolution component + if (lat_frac < 0) // If the fractional part is negative, remove the minus sign + { + lat_frac = 0 - lat_frac; + } + lon_int = longitude / 10000000; // Convert latitude from degrees * 10^-7 to Degrees + lon_frac = longitude - (lon_int * 10000000); // Calculate the fractional part of the longitude + lon_frac = (lon_frac * 100) + longitudeHp; // Now add the high resolution component + if (lon_frac < 0) // If the fractional part is negative, remove the minus sign + { + lon_frac = 0 - lon_frac; + } + + // Print the lat and lon + Serial.print("Lat (deg): "); + Serial.print(lat_int); // Print the integer part of the latitude + Serial.print("."); + Serial.print(lat_frac); // Print the fractional part of the latitude + Serial.print(", Lon (deg): "); + Serial.print(lon_int); // Print the integer part of the latitude + Serial.print("."); + Serial.println(lon_frac); // Print the fractional part of the latitude + + // Now define float storage for the heights and accuracy + float f_ellipsoid; + float f_msl; + float f_accuracy; + + // Calculate the height above ellipsoid in mm * 10^-1 + f_ellipsoid = (ellipsoid * 10) + ellipsoidHp; + // Now convert to m + f_ellipsoid = f_ellipsoid / 10000.0; // Convert from mm * 10^-1 to m + + // Calculate the height above mean sea level in mm * 10^-1 + f_msl = (msl * 10) + mslHp; + // Now convert to m + f_msl = f_msl / 10000.0; // Convert from mm * 10^-1 to m + + // Convert the horizontal accuracy (mm * 10^-1) to a float + f_accuracy = accuracy; + // Now convert to m + f_accuracy = f_accuracy / 10000.0; // Convert from mm * 10^-1 to m + + // Finally, do the printing + Serial.print("Ellipsoid (m): "); + Serial.print(f_ellipsoid, 4); // Print the ellipsoid with 4 decimal places + + Serial.print(", Mean Sea Level(m): "); + Serial.print(f_msl, 4); // Print the mean sea level with 4 decimal places + + Serial.print(", Accuracy (m): "); + Serial.println(f_accuracy, 4); // Print the accuracy with 4 decimal places + } +} diff --git a/examples/ZED-F9P/Example11_GetHighPrecisionPositionUsingDouble/Example11_GetHighPrecisionPositionUsingDouble.ino b/examples/ZED-F9P/Example11_GetHighPrecisionPositionUsingDouble/Example11_GetHighPrecisionPositionUsingDouble.ino new file mode 100644 index 0000000..58eb295 --- /dev/null +++ b/examples/ZED-F9P/Example11_GetHighPrecisionPositionUsingDouble/Example11_GetHighPrecisionPositionUsingDouble.ino @@ -0,0 +1,146 @@ +/* + Get the high precision geodetic solution for latitude and longitude using double + By: Nathan Seidle + Modified by: Paul Clark (PaulZC) + SparkFun Electronics + Date: April 17th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to inspect the accuracy of the high-precision + positional solution. Please see below for information about the units. + + ** This example will only work correctly on platforms which support 64-bit double ** + + 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 + + Hardware Connections: + Plug a Qwiic cable into the GNSS and (e.g.) a Redboard Artemis https://www.sparkfun.com/products/15444 + or an Artemis Thing Plus https://www.sparkfun.com/products/15574 + 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 + +#define myWire Wire // This will work on the Redboard Artemis and the Artemis Thing Plus using Qwiic +//#define myWire Wire1 // Uncomment this line if you are using the extra SCL1/SDA1 pins (D17 and D16) on the Thing Plus + +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + + myWire.begin(); + + //myGPS.enableDebugging(Serial); // Uncomment this line to enable debug messages + + if (myGPS.begin(myWire) == 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. Freezing.")); + while (1) + ; + } + + // Check that this platform supports 64-bit (8 byte) double + if (sizeof(double) < 8) + { + Serial.println(F("Warning! Your platform does not support 64-bit double.")); + Serial.println(F("The latitude and longitude will be inaccurate.")); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + //myGPS.setNavigationFrequency(20); //Set output to 20 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 +} + +void loop() +{ + //Query module only every second. + //The module only responds when a new position is available. + if (millis() - lastTime > 1000) + { + lastTime = millis(); //Update the timer + + // getHighResLatitude: returns the latitude from HPPOSLLH as an int32_t in degrees * 10^-7 + // getHighResLatitudeHp: returns the high resolution component of latitude from HPPOSLLH as an int8_t in degrees * 10^-9 + // getHighResLongitude: returns the longitude from HPPOSLLH as an int32_t in degrees * 10^-7 + // getHighResLongitudeHp: returns the high resolution component of longitude from HPPOSLLH as an int8_t in degrees * 10^-9 + // getElipsoid: returns the height above ellipsoid as an int32_t in mm + // getElipsoidHp: returns the high resolution component of the height above ellipsoid as an int8_t in mm * 10^-1 + // getMeanSeaLevel: returns the height above mean sea level as an int32_t in mm + // getMeanSeaLevelHp: returns the high resolution component of the height above mean sea level as an int8_t in mm * 10^-1 + // getHorizontalAccuracy: returns the horizontal accuracy estimate from HPPOSLLH as an uint32_t in mm * 10^-1 + + // First, let's collect the position data + int32_t latitude = myGPS.getHighResLatitude(); + int8_t latitudeHp = myGPS.getHighResLatitudeHp(); + int32_t longitude = myGPS.getHighResLongitude(); + int8_t longitudeHp = myGPS.getHighResLongitudeHp(); + int32_t ellipsoid = myGPS.getElipsoid(); + int8_t ellipsoidHp = myGPS.getElipsoidHp(); + int32_t msl = myGPS.getMeanSeaLevel(); + int8_t mslHp = myGPS.getMeanSeaLevelHp(); + uint32_t accuracy = myGPS.getHorizontalAccuracy(); + + // Defines storage for the lat and lon as double + double d_lat; // latitude + double d_lon; // longitude + + // Assemble the high precision latitude and longitude + d_lat = ((double)latitude) / 10000000.0; // Convert latitude from degrees * 10^-7 to degrees + d_lat += ((double)latitudeHp) / 1000000000.0; // Now add the high resolution component (degrees * 10^-9 ) + d_lon = ((double)longitude) / 10000000.0; // Convert longitude from degrees * 10^-7 to degrees + d_lon += ((double)longitudeHp) / 1000000000.0; // Now add the high resolution component (degrees * 10^-9 ) + + // Print the lat and lon + Serial.print("Lat (deg): "); + Serial.print(d_lat, 9); + Serial.print(", Lon (deg): "); + Serial.print(d_lon, 9); + + // Now define float storage for the heights and accuracy + float f_ellipsoid; + float f_msl; + float f_accuracy; + + // Calculate the height above ellipsoid in mm * 10^-1 + f_ellipsoid = (ellipsoid * 10) + ellipsoidHp; + // Now convert to m + f_ellipsoid = f_ellipsoid / 10000.0; // Convert from mm * 10^-1 to m + + // Calculate the height above mean sea level in mm * 10^-1 + f_msl = (msl * 10) + mslHp; + // Now convert to m + f_msl = f_msl / 10000.0; // Convert from mm * 10^-1 to m + + // Convert the horizontal accuracy (mm * 10^-1) to a float + f_accuracy = accuracy; + // Now convert to m + f_accuracy = f_accuracy / 10000.0; // Convert from mm * 10^-1 to m + + // Finally, do the printing + Serial.print(", Ellipsoid (m): "); + Serial.print(f_ellipsoid, 4); // Print the ellipsoid with 4 decimal places + + Serial.print(", Mean Sea Level (m): "); + Serial.print(f_msl, 4); // Print the mean sea level with 4 decimal places + + Serial.print(", Accuracy (m): "); + Serial.println(f_accuracy, 4); // Print the accuracy with 4 decimal places + } +} diff --git a/examples/ZED-F9P/Example12_setStaticPosition/Example12_setStaticPosition.ino b/examples/ZED-F9P/Example12_setStaticPosition/Example12_setStaticPosition.ino new file mode 100644 index 0000000..12ed29d --- /dev/null +++ b/examples/ZED-F9P/Example12_setStaticPosition/Example12_setStaticPosition.ino @@ -0,0 +1,77 @@ +/* + Set the static position of the receiver. + By: SparkFun Electronics / Nathan Seidle + Date: September 26th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to set the static position of a receiver + using an Earth-Centered, Earth-Fixed (ECEF) location. This is the + output from a long (24 hour+) survey-in. Setting the static position + immediately causes the receiver to begin outputting RTCM data (if + enabled), perfect for setting up your own RTCM NTRIP caster or CORS. + + 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 + + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); // You may need to increase this for high navigation rates! + while (!Serial) + ; //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + + boolean success = true; + + //-1280208.308,-4716803.847,4086665.811 is SparkFun HQ so... + + //Units are cm so 1234 = 12.34m + //success &= myGPS.setStaticPosition(-128020831, -471680385, 408666581); + + //Units are cm with a high precision extension so -1234.5678 should be called: (-123456, -78) + success &= myGPS.setStaticPosition(-128020830, -80, -471680384, -70, 408666581, 10); //With high precision 0.1mm parts + + //We can also set via lat/long + //40.09029751,-105.18507900,1560.238 + //success &= myGPS.setStaticPosition(400902975, -1051850790, 156024, true); //True at end enables lat/long input + //success &= myGPS.setStaticPosition(400902975, 10, -1051850790, 0, 156023, 80, true); + + if (!success) Serial.println(F("At least one call to setStaticPosition failed!")); + + //Now let's use getVals to read back the data + //long ecefX = myGPS.getVal32(0x40030003); + //Serial.print("ecefX: "); + //Serial.println(ecefX); + + Serial.println(F("Done!")); +} + +void loop() +{ +} diff --git a/examples/ZED-F9P/Example13_autoHPPOSLLH/Example13_autoHPPOSLLH.ino b/examples/ZED-F9P/Example13_autoHPPOSLLH/Example13_autoHPPOSLLH.ino new file mode 100644 index 0000000..c56eeba --- /dev/null +++ b/examples/ZED-F9P/Example13_autoHPPOSLLH/Example13_autoHPPOSLLH.ino @@ -0,0 +1,119 @@ +/* + Configuring the GNSS to automatically send HPPOSLLH position reports over I2C + By: Paul Clark + Date: October 27th 2020 + + Based on an earlier example: + By: Nathan Seidle and Thorsten von Eicken + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the U-Blox GNSS the send navigation reports automatically + and retrieving the latest one via getHPPOSLLH. This eliminates the blocking in getHPPOSLLH while the GNSS + produces a fresh navigation solution at the expense of returning a slighly old solution. + + This can be used over serial or over I2C, this example shows the I2C use. With serial the GNSS + simply outputs the UBX_NAV_HPPOSLLH packet. With I2C it queues it into its internal I2C buffer (4KB in + size?) where it can be retrieved in the next I2C poll. + + 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 + + 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 +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable lots of helpful debug messages + //myGPS.enableDebugging(Serial, true); // Uncomment this line to enable the minimum of helpful debug messages + + if (myGPS.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. Freezing.")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + //myGPS.factoryDefault(); delay(5000); + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save the communications port settings to flash and BBR + + myGPS.setNavigationFrequency(1); //Produce one solution per second + + + // The acid test: all four of these combinations should work seamlessly :-) + + //myGPS.setAutoPVT(false); // Library will poll each reading + //myGPS.setAutoHPPOSLLH(false); // Library will poll each reading + + //myGPS.setAutoPVT(true); // Tell the GPS to "send" each solution automatically + //myGPS.setAutoHPPOSLLH(false); // Library will poll each reading + + //myGPS.setAutoPVT(false); // Library will poll each reading + //myGPS.setAutoHPPOSLLH(true); // Tell the GPS to "send" each hi res solution automatically + + myGPS.setAutoPVT(true); // Tell the GPS to "send" each solution automatically + myGPS.setAutoHPPOSLLH(true); // Tell the GPS to "send" each hi res solution automatically +} + +void loop() +{ + // Calling getHPPOSLLH returns true if there actually is a fresh navigation solution available. + // Calling getPVT returns true if there actually is a fresh navigation solution available. + if ((myGPS.getHPPOSLLH()) || (myGPS.getPVT())) + { + Serial.println(); + + long highResLatitude = myGPS.getHighResLatitude(); + Serial.print(F("Hi Res Lat: ")); + Serial.print(highResLatitude); + + int highResLatitudeHp = myGPS.getHighResLatitudeHp(); + Serial.print(F(" ")); + Serial.print(highResLatitudeHp); + + long highResLongitude = myGPS.getHighResLongitude(); + Serial.print(F(" Hi Res Long: ")); + Serial.print(highResLongitude); + + int highResLongitudeHp = myGPS.getHighResLongitudeHp(); + Serial.print(F(" ")); + Serial.print(highResLongitudeHp); + + unsigned long horizAccuracy = myGPS.getHorizontalAccuracy(); + Serial.print(F(" Horiz accuracy: ")); + Serial.print(horizAccuracy); + + long latitude = myGPS.getLatitude(); + Serial.print(F(" Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.println(longitude); + } + else + { + Serial.print("."); + delay(50); + } +} diff --git a/examples/ZED-F9P/Example13_autoHPPOSLLH_with_Callback/Example13_autoHPPOSLLH_with_Callback.ino b/examples/ZED-F9P/Example13_autoHPPOSLLH_with_Callback/Example13_autoHPPOSLLH_with_Callback.ino new file mode 100644 index 0000000..89611b7 --- /dev/null +++ b/examples/ZED-F9P/Example13_autoHPPOSLLH_with_Callback/Example13_autoHPPOSLLH_with_Callback.ino @@ -0,0 +1,150 @@ +/* + Configuring the GNSS to automatically send HPPOSLLH position reports over I2C + and uses callbacks to process and display the data automatically + By: Paul Clark + Date: October 27th 2020 + + Based on an earlier example: + By: Nathan Seidle and Thorsten von Eicken + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the U-Blox GNSS the send navigation reports automatically + and and uses callbacks to process and display the data automatically. No more polling! + + This can be used over serial or over I2C, this example shows the I2C use. With serial the GNSS + simply outputs the UBX_NAV_HPPOSLLH packet. With I2C it queues it into its internal I2C buffer (4KB in + size?) where it can be retrieved in the next I2C poll. + + 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 + + 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 +SFE_UBLOX_GPS myGPS; + +// Callback: printHPdata will be called when new NAV HPPOSLLH data arrives +// See u-blox_structs.h for the full definition of UBX_NAV_HPPOSLLH_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoHPPOSLLHcallback +// / _____ This _must_ be UBX_NAV_HPPOSLLH_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printHPdata(UBX_NAV_HPPOSLLH_data_t ubxDataStruct) +{ + Serial.println(); + + long highResLatitude = ubxDataStruct.lat; + Serial.print(F("Hi Res Lat: ")); + Serial.print(highResLatitude); + + int highResLatitudeHp = ubxDataStruct.latHp; + Serial.print(F(" ")); + Serial.print(highResLatitudeHp); + + long highResLongitude = ubxDataStruct.lon; + Serial.print(F(" Hi Res Long: ")); + Serial.print(highResLongitude); + + int highResLongitudeHp = ubxDataStruct.lonHp; + Serial.print(F(" ")); + Serial.print(highResLongitudeHp); + + unsigned long horizAccuracy = ubxDataStruct.hAcc; + Serial.print(F(" Horiz accuracy: ")); + Serial.println(horizAccuracy); +} + +// Callback: printPVTdata will be called when new NAV PVT data arrives +// See u-blox_structs.h for the full definition of UBX_NAV_PVT_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoPVTcallback +// / _____ This _must_ be UBX_NAV_PVT_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printPVTdata(UBX_NAV_PVT_data_t ubxDataStruct) +{ + Serial.println(); + + Serial.print(F("Time: ")); // Print the time + uint8_t hms = ubxDataStruct.hour; // Print the hours + if (hms < 10) Serial.print(F("0")); // Print a leading zero if required + Serial.print(hms); + Serial.print(F(":")); + hms = ubxDataStruct.min; // Print the minutes + if (hms < 10) Serial.print(F("0")); // Print a leading zero if required + Serial.print(hms); + Serial.print(F(":")); + hms = ubxDataStruct.sec; // Print the seconds + if (hms < 10) Serial.print(F("0")); // Print a leading zero if required + Serial.print(hms); + Serial.print(F(".")); + unsigned long millisecs = ubxDataStruct.iTOW % 1000; // Print the milliseconds + if (millisecs < 100) Serial.print(F("0")); // Print the trailing zeros correctly + if (millisecs < 10) Serial.print(F("0")); + Serial.print(millisecs); + + long latitude = ubxDataStruct.lat; // Print the latitude + Serial.print(F(" Lat: ")); + Serial.print(latitude); + + long longitude = ubxDataStruct.lon; // Print the longitude + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = ubxDataStruct.hMSL; // Print the height above mean sea level + Serial.print(F(" Height above MSL: ")); + Serial.print(altitude); + Serial.println(F(" (mm)")); +} + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable lots of helpful debug messages + //myGPS.enableDebugging(Serial, true); // Uncomment this line to enable the minimum of helpful debug messages + + if (myGPS.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. Freezing.")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + //myGPS.factoryDefault(); delay(5000); + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save the communications port settings to flash and BBR + + myGPS.setNavigationFrequency(2); //Produce two solutions per second + + myGPS.setAutoPVTcallback(&printPVTdata); // Enable automatic NAV PVT messages with callback to printPVTdata + + myGPS.setAutoHPPOSLLHcallback(&printHPdata); // Enable automatic NAV HPPOSLLH messages with callback to printHPdata +} + +void loop() +{ + myGPS.checkUblox(); // Check for the arrival of new data and process it. You could set up a timer interrupt to do this for you. + myGPS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + Serial.print("."); + delay(50); +} diff --git a/examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino b/examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino new file mode 100644 index 0000000..13ae4ae --- /dev/null +++ b/examples/ZED-F9P/Example14_NTRIPServer/Example14_NTRIPServer.ino @@ -0,0 +1,276 @@ +/* + Note: compiles OK with v2.0 but is currently untested + + Use ESP32 WiFi to push RTCM data to RTK2Go (caster) as a Server + By: SparkFun Electronics / Nathan Seidle + Date: December 14th, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to gather RTCM data over I2C and push it to a casting service over WiFi. + It's confusing, but the Arduino is acting as a 'server' to a 'caster'. In this case we will + use RTK2Go.com as our caster because it is free. A rover (car, surveyor stick, etc) can + then connect to RTK2Go as a 'client' and get the RTCM data it needs. + + You will need to register your mountpoint here: http://www.rtk2go.com/new-reservation/ + (They'll probably block the credentials we include in this example) + + To see if your mountpoint is active go here: http://rtk2go.com:2101/ + + This is a proof of concept. Serving RTCM to a caster over WiFi is useful when you need to + set up a high-precision base station. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/16481 + RTK Surveyor: https://www.sparkfun.com/products/17369 + + Hardware Connections: + Plug a Qwiic cable into the GNSS and a ESP32 Thing Plus + 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 +#include "secrets.h" +WiFiClient client; + +#include //Needed for I2C to GNSS +#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +//Basic Connection settings to RTK2Go NTRIP Caster - See secrets for mount specific credentials +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +const uint16_t casterPort = 2101; +const char * casterHost = "rtk2go.com"; +const char * ntrip_server_name = "SparkFun_RTK_Surveyor"; + +long lastSentRTCM_ms = 0; //Time of last data pushed to socket +int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster + +uint32_t serverBytesSent = 0; //Just a running total +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +long lastReport_ms = 0; //Time of last report of bytes sent + +void setup() +{ + Serial.begin(115200); // You may need to increase this for high navigation rates! + while (!Serial) + ; //Wait for user to open terminal + Serial.println(F("SparkFun u-blox Example")); + + Wire.begin(); + + //myGPS.enableDebugging(); // Uncomment this line to enable debug messages + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + Serial.print("Connecting to local WiFi"); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.print("\nWiFi connected with IP: "); + Serial.println(WiFi.localIP()); + + myGPS.setI2COutput(COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_RTCM3); //UBX+RTCM3 is not a valid option so we enable all three. + + myGPS.setNavigationFrequency(1); //Set output in Hz. RTCM rarely benefits from >1Hz. + + //Disable all NMEA sentences + bool response = true; + response &= myGPS.disableNMEAMessage(UBX_NMEA_GGA, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_GSA, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_GSV, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_RMC, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_GST, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_GLL, COM_PORT_I2C); + response &= myGPS.disableNMEAMessage(UBX_NMEA_VTG, COM_PORT_I2C); + + if (response == false) + { + Serial.println(F("Failed to disable NMEA. Freezing...")); + while (1); + } + else + Serial.println(F("NMEA disabled")); + + //Enable necessary RTCM sentences + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through UART2, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1074, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1084, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1094, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1124, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_I2C, 10); //Enable message every 10 seconds + + if (response == false) + { + Serial.println(F("Failed to enable RTCM. Freezing...")); + while (1); + } + else + Serial.println(F("RTCM sentences enabled")); + + //-1280208.308,-4716803.847,4086665.811 is SparkFun HQ so... + //Units are cm with a high precision extension so -1234.5678 should be called: (-123456, -78) + //For more infomation see Example12_setStaticPosition + //Note: If you leave these coordinates in place and setup your antenna *not* at SparkFun, your receiver + //will be very confused and fail to generate correction data because, well, you aren't at SparkFun... + //See this tutorial on getting PPP coordinates: https://learn.sparkfun.com/tutorials/how-to-build-a-diy-gnss-reference-station/all + response &= myGPS.setStaticPosition(-128020830, -80, -471680384, -70, 408666581, 10); //With high precision 0.1mm parts + if (response == false) + { + Serial.println(F("Failed to enter static position. Freezing...")); + while (1); + } + else + Serial.println(F("Static position set")); + + //You could instead do a survey-in but it takes much longer to start generating RTCM data. See Example4_BaseWithLCD + //myGPS.enableSurveyMode(60, 5.000); //Enable Survey in, 60 seconds, 5.0m + + if (myGPS.saveConfiguration() == false) //Save the current settings to flash and BBR + Serial.println(F("Module failed to save.")); + + Serial.println(F("Module configuration complete")); +} + +void loop() +{ + if (Serial.available()) beginServing(); + + Serial.println(F("Press any key to start serving.")); + + delay(1000); +} + +void beginServing() +{ + Serial.println("Xmit to RTK2Go. Press any key to stop"); + delay(10); //Wait for any serial to arrive + while (Serial.available()) Serial.read(); //Flush + + while (Serial.available() == 0) + { + //Connect if we are not already + if (client.connected() == false) + { + Serial.printf("Opening socket to %s\n", casterHost); + + if (client.connect(casterHost, casterPort) == true) //Attempt connection + { + Serial.printf("Connected to %s:%d\n", casterHost, casterPort); + + const int SERVER_BUFFER_SIZE = 512; + char serverBuffer[SERVER_BUFFER_SIZE]; + + snprintf(serverBuffer, SERVER_BUFFER_SIZE, "SOURCE %s /%s\r\nSource-Agent: NTRIP %s/%s\r\n\r\n", + mntpnt_pw, mntpnt, ntrip_server_name, "App Version 1.0"); + + Serial.printf("Sending credentials:\n%s\n", serverBuffer); + client.write(serverBuffer, strlen(serverBuffer)); + + //Wait for response + unsigned long timeout = millis(); + while (client.available() == 0) + { + if (millis() - timeout > 5000) + { + Serial.println(">>> Client Timeout !"); + client.stop(); + return; + } + delay(10); + } + + //Check reply + bool connectionSuccess = false; + char response[512]; + int responseSpot = 0; + while (client.available()) + { + response[responseSpot++] = client.read(); + if (strstr(response, "200") > 0) //Look for 'ICY 200 OK' + connectionSuccess = true; + if (responseSpot == 512 - 1) break; + } + response[responseSpot] = '\0'; + + if (connectionSuccess == false) + { + Serial.printf("Failed to connect to RTK2Go: %s", response); + } + } //End attempt to connect + else + { + Serial.println("Connection to host failed"); + } + } //End connected == false + + if (client.connected() == true) + { + delay(10); + while (Serial.available()) Serial.read(); //Flush any endlines or carriage returns + + lastReport_ms = millis(); + lastSentRTCM_ms = millis(); + + //This is the main sending loop. We scan for new ublox data but processRTCM() is where the data actually gets sent out. + while (1) + { + if (Serial.available()) break; + + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + //Close socket if we don't have new data for 10s + //RTK2Go will ban your IP address if you abuse it. See http://www.rtk2go.com/how-to-get-your-ip-banned/ + //So let's not leave the socket open/hanging without data + if (millis() - lastSentRTCM_ms > maxTimeBeforeHangup_ms) + { + Serial.println("RTCM timeout. Disconnecting..."); + client.stop(); + return; + } + + delay(10); + + //Report some statistics every 250 + if (millis() - lastReport_ms > 250) + { + lastReport_ms += 250; + Serial.printf("Total sent: %d\n", serverBytesSent); + } + } + } + + delay(10); + } + + Serial.println("User pressed a key"); + Serial.println("Disconnecting..."); + client.stop(); + + delay(10); + while (Serial.available()) Serial.read(); //Flush any endlines or carriage returns +} + +//This function gets called from the SparkFun u-blox Arduino Library. +//As each RTCM byte comes in you can specify what to do with it +//Useful for passing the RTCM correction data to a radio, Ntrip broadcaster, etc. +void SFE_UBLOX_GPS::processRTCM(uint8_t incoming) +{ + if (client.connected() == true) + { + client.write(incoming); //Send this byte to socket + serverBytesSent++; + lastSentRTCM_ms = millis(); + } +} diff --git a/examples/ZED-F9P/Example14_NTRIPServer/secrets.h b/examples/ZED-F9P/Example14_NTRIPServer/secrets.h new file mode 100644 index 0000000..5dcd50e --- /dev/null +++ b/examples/ZED-F9P/Example14_NTRIPServer/secrets.h @@ -0,0 +1,7 @@ +//Your WiFi credentials +const char* ssid = "TRex"; +const char* password = "hasBigTeeth"; + +//Your RTK2GO mount point credentials +const char* mntpnt_pw = "WR5wRo4H"; +const char* mntpnt = "bldr_dwntwn2"; diff --git a/examples/ZED-F9P/Example1_GetPositionAccuracy/Example1_GetPositionAccuracy.ino b/examples/ZED-F9P/Example1_GetPositionAccuracy/Example1_GetPositionAccuracy.ino new file mode 100644 index 0000000..54a7f04 --- /dev/null +++ b/examples/ZED-F9P/Example1_GetPositionAccuracy/Example1_GetPositionAccuracy.ino @@ -0,0 +1,76 @@ +/* + Get the high position accuracy of the RTK enhanced position from HPPOSECEF + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to inspect the accuracy of the high-precision + positional solution. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1); + } + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + //myGPS.saveConfiguration(); //Optional: Save the current settings to flash and BBR +} + +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 > 1000) + { + lastTime = millis(); //Update the timer + + long latitude = myGPS.getLatitude(); + Serial.print(F("Lat: ")); + Serial.print(latitude); + + long longitude = myGPS.getLongitude(); + Serial.print(F(" Long: ")); + Serial.print(longitude); + Serial.print(F(" (degrees * 10^-7)")); + + long altitude = myGPS.getAltitude(); + Serial.print(F(" Alt: ")); + Serial.print(altitude); + Serial.print(F(" (mm)")); + + long accuracy = myGPS.getPositionAccuracy(); + Serial.print(F(" 3D Positional Accuracy: ")); + Serial.print(accuracy); + Serial.println(F(" (mm)")); + } +} diff --git a/examples/ZED-F9P/Example2_ValConfigurationMethod/Example2_ValConfigurationMethod.ino b/examples/ZED-F9P/Example2_ValConfigurationMethod/Example2_ValConfigurationMethod.ino new file mode 100644 index 0000000..d388d15 --- /dev/null +++ b/examples/ZED-F9P/Example2_ValConfigurationMethod/Example2_ValConfigurationMethod.ino @@ -0,0 +1,63 @@ +/* + Configuring u-blox Module using new VALGET / VALSET / VALDEL methods + + Please see u-blox_config_keys.h for the definitions of _all_ of the configuration keys + + By: Nathan Seidle + SparkFun Electronics + Date: January 3rd, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + u-blox deprecated many -CFG messages and replaced them with new + VALGET, VALSET, VALDEL methods. This shows the basics of how to use + these methods. + + Leave NMEA parsing behind. Now you can simply ask the module for the datums you want! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1); + } + + byte response; + response = myGPS.getVal8(UBLOX_CFG_I2C_ADDRESS, VAL_LAYER_RAM); // Get the I2C address (see u-blox_config_keys.h for details) + Serial.print(F("I2C Address: 0x")); + Serial.println(response >> 1, HEX); //We have to shift by 1 to get the common '7-bit' I2C address format + + response = myGPS.getVal8(UBLOX_CFG_I2COUTPROT_NMEA, VAL_LAYER_RAM); // Get the flag indicating is NMEA should be output on I2C + Serial.print(F("Output NMEA over I2C port: 0x")); + Serial.print(response, HEX); +} + +void loop() +{ +} diff --git a/examples/ZED-F9P/Example3_StartRTCMBase/Example3_StartRTCMBase.ino b/examples/ZED-F9P/Example3_StartRTCMBase/Example3_StartRTCMBase.ino new file mode 100644 index 0000000..e9cf1b9 --- /dev/null +++ b/examples/ZED-F9P/Example3_StartRTCMBase/Example3_StartRTCMBase.ino @@ -0,0 +1,194 @@ +/* + Send UBX binary commands to enable RTCM sentences on u-blox ZED-F9P module + By: Nathan Seidle + SparkFun Electronics + Date: January 9th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example does all steps to configure and enable a ZED-F9P as a base station: + Begin Survey-In + Once we've achieved 2m accuracy and 300s have passed, survey is complete + Enable six RTCM messages + Begin outputting RTCM bytes + + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +//#define USE_SERIAL1 // Uncomment this line to push the RTCM data to Serial1 + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println(F("u-blox Base Station example")); + +#ifdef USE_SERIAL1 + // If our board supports it, we can output the RTCM data on Serial1 + Serial1.begin(115200); +#endif + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.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. Freezing.")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + //myGPS.factoryDefault(); delay(5000); + + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save the communications port settings to flash and BBR + + while (Serial.available()) Serial.read(); //Clear any latent chars in serial buffer + Serial.println(F("Press any key to send commands to begin Survey-In")); + while (Serial.available() == 0) ; //Wait for user to press a key + + boolean response = true; + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1074, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1084, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1094, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1124, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_I2C, 10); //Enable message every 10 seconds + + //Use COM_PORT_UART1 for the above six messages to direct RTCM messages out UART1 + //COM_PORT_UART2, COM_PORT_USB, COM_PORT_SPI are also available + //For example: response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_UART1, 10); + + if (response == true) + { + Serial.println(F("RTCM messages enabled")); + } + else + { + Serial.println(F("RTCM failed to enable. Are you sure you have an ZED-F9P?")); + while (1); //Freeze + } + + //Check if Survey is in Progress before initiating one + // From v2.0, the data from getSurveyStatus (UBX-NAV-SVIN) is returned in UBX_NAV_SVIN_t packetUBXNAVSVIN + // Please see u-blox_structs.h for the full definition of UBX_NAV_SVIN_t + // You can either read the data from packetUBXNAVSVIN directly + // or can use the helper functions: getSurveyInActive; getSurveyInValid; getSurveyInObservationTime; and getSurveyInMeanAccuracy + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (request can take a long time) + + if (response == false) // Check if fresh data was received + { + Serial.println(F("Failed to get Survey In status")); + while (1); //Freeze + } + + if (myGPS.getSurveyInActive() == true) // Use the helper function + //if (myGPS.packetUBXNAVSVIN->data.active > 0) // Or we could read active directly + { + Serial.print(F("Survey already in progress.")); + } + else + { + //Start survey + //The ZED-F9P is slightly different than the NEO-M8P. See the Integration manual 3.5.8 for more info. + //response = myGPS.enableSurveyMode(300, 2.000); //Enable Survey in on NEO-M8P, 300 seconds, 2.0m + response = myGPS.enableSurveyMode(60, 5.000); //Enable Survey in, 60 seconds, 5.0m + if (response == false) + { + Serial.println(F("Survey start failed. Freezing...")); + while (1); + } + Serial.println(F("Survey started. This will run until 60s has passed and less than 5m accuracy is achieved.")); + } + + while(Serial.available()) Serial.read(); //Clear buffer + + //Begin waiting for survey to complete + while (myGPS.getSurveyInValid() == false) // Call the helper function + //while (myGPS.packetUBXNAVSVIN->data.valid == 0) // Or we could read valid directly + { + if(Serial.available()) + { + byte incoming = Serial.read(); + if(incoming == 'x') + { + //Stop survey mode + response = myGPS.disableSurveyMode(); //Disable survey + Serial.println(F("Survey stopped")); + break; + } + } + + // From v2.0, the data from getSurveyStatus (UBX-NAV-SVIN) is returned in UBX_NAV_SVIN_t packetUBXNAVSVIN + // Please see u-blox_structs.h for the full definition of UBX_NAV_SVIN_t + // You can either read the data from packetUBXNAVSVIN directly + // or can use the helper functions: getSurveyInActive; getSurveyInValid; getSurveyInObservationTime; and getSurveyInMeanAccuracy + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (req can take a long time) + + if (response == true) // Check if fresh data was received + { + Serial.print(F("Press x to end survey - ")); + Serial.print(F("Time elapsed: ")); + Serial.print((String)myGPS.getSurveyInObservationTime()); // Call the helper function + Serial.print(F(" (")); + Serial.print((String)myGPS.packetUBXNAVSVIN->data.dur); // Read the survey-in duration directly from packetUBXNAVSVIN + + Serial.print(F(") Accuracy: ")); + Serial.print((String)myGPS.getSurveyInMeanAccuracy()); // Call the helper function + Serial.print(F(" (")); + // Read the mean accuracy directly from packetUBXNAVSVIN and manually convert from mm*0.1 to m + float meanAcc = ((float)myGPS.packetUBXNAVSVIN->data.meanAcc) / 10000.0; + Serial.print((String)meanAcc); + Serial.println(F(")")); + } + else + { + Serial.println(F("SVIN request failed")); + } + + delay(1000); + } + Serial.println(F("Survey valid!")); + + Serial.println(F("Base survey complete! RTCM now broadcasting.")); + + myGPS.setI2COutput(COM_TYPE_UBX | COM_TYPE_RTCM3); //Set the I2C port to output UBX and RTCM sentences (not really an option, turns on NMEA as well) +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + delay(250); //Don't pound too hard on the I2C bus +} + +//This function gets called from the SparkFun u-blox Arduino Library. +//As each RTCM byte comes in you can specify what to do with it +//Useful for passing the RTCM correction data to a radio, Ntrip broadcaster, etc. +void SFE_UBLOX_GPS::processRTCM(uint8_t incoming) +{ +#ifdef USE_SERIAL1 + //Push the RTCM data to Serial1 + Serial1.write(incoming); +#endif + + //Pretty-print the HEX values to Serial + if (myGPS.rtcmFrameCounter % 16 == 0) Serial.println(); + Serial.print(F(" ")); + if (incoming < 0x10) Serial.print(F("0")); + Serial.print(incoming, HEX); +} diff --git a/examples/ZED-F9P/Example4_BaseWithLCD/Example4_BaseWithLCD.ino b/examples/ZED-F9P/Example4_BaseWithLCD/Example4_BaseWithLCD.ino new file mode 100644 index 0000000..3286d64 --- /dev/null +++ b/examples/ZED-F9P/Example4_BaseWithLCD/Example4_BaseWithLCD.ino @@ -0,0 +1,210 @@ +/* + Note: compiles OK with v2.0 but is untested. The previous example works fine though. + + Send UBX binary commands to enable RTCM sentences on u-blox ZED-F9P module + By: Nathan Seidle + SparkFun Electronics + Date: January 9th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example does all steps to configure and enable a ZED-F9P as a base station: + Begin Survey-In + Once we've achieved 2m accuracy and 300s have passed, survey is complete + Enable six RTCM messages + Begin outputting RTCM bytes + + 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 + Plug a SerLCD onto the Qwiic bus + If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425) + Watch the output on the LCD or open the serial monitor at 115200 baud to see the output +*/ + +#define STAT_LED 13 + +#include //Needed for I2C to GNSS + +#include "SparkFun_Ublox_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +#include //Click here to get the library: http://librarymanager/All#SparkFun_SerLCD +SerLCD lcd; // Initialize the library with default I2C address 0x72 + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println(F("u-blox GNSS I2C Test")); + + Wire.begin(); + + pinMode(STAT_LED, OUTPUT); + digitalWrite(STAT_LED, LOW); + + lcd.begin(Wire); //Set up the LCD for Serial communication at 9600bps + lcd.setBacklight(0x4B0082); //indigo, a kind of dark purplish blue + lcd.clear(); + lcd.print(F("LCD Ready")); + + myGPS.begin(Wire); + if (myGPS.isConnected() == false) + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing.")); + lcd.setCursor(0, 1); + lcd.print(F("No GNSS detected")); + while (1) + ; + } + + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + lcd.setCursor(0, 1); + lcd.print("GNSS Detected"); + + //myGPS.setI2COutput(COM_TYPE_RTCM3); //Set the I2C port to output RTCM3 sentences (turn off NMEA noise) + myGPS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX sentences (turn off NMEA noise) + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save the communications port settings to flash and BBR + + boolean response = true; + response &= myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + response &= myGPS.enableRTCMmessage(UBX_RTCM_1074, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1084, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1094, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1124, COM_PORT_I2C, 1); + response &= myGPS.enableRTCMmessage(UBX_RTCM_1230, COM_PORT_I2C, 10); //Enable message every 10 seconds + if (response == true) + { + Serial.println(F("RTCM messages enabled")); + } + else + { + Serial.println(F("RTCM failed to enable. Are you sure you have an ZED-F9P? Freezing.")); + while (1) + ; //Freeze + } + + //Check if Survey is in Progress before initiating one + // From v2.0, the data from getSurveyStatus (UBX-NAV-SVIN) is returned in UBX_NAV_SVIN_t packetUBXNAVSVIN + // Please see u-blox_structs.h for the full definition of UBX_NAV_SVIN_t + // You can either read the data from packetUBXNAVSVIN directly + // or can use the helper functions: getSurveyInActive; getSurveyInValid; getSurveyInObservationTime; and getSurveyInMeanAccuracy + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (request can take a long time) + if (response == false) + { + Serial.println(F("Failed to get Survey In status. Freezing.")); + while (1) + ; //Freeze + } + + if (myGPS.getSurveyInActive() == true) // Use the helper function + { + Serial.print(F("Survey already in progress.")); + lcd.setCursor(0, 2); + lcd.print(F("Survey already going")); + } + else + { + //Start survey + response = myGPS.enableSurveyMode(60, 5.000); //Enable Survey in, 60 seconds, 5.0m + if (response == false) + { + Serial.println(F("Survey start failed")); + lcd.setCursor(0, 3); + lcd.print(F("Survey start failed. Freezing.")); + while (1) + ; + } + Serial.println(F("Survey started. This will run until 60s has passed and less than 5m accuracy is achieved.")); + } + + while (Serial.available()) + Serial.read(); //Clear buffer + + lcd.clear(); + lcd.print(F("Survey in progress")); + + //Begin waiting for survey to complete + while (myGPS.getSurveyInValid() == false) // Call the helper function + { + if (Serial.available()) + { + byte incoming = Serial.read(); + if (incoming == 'x') + { + //Stop survey mode + response = myGPS.disableSurveyMode(); //Disable survey + Serial.println(F("Survey stopped")); + break; + } + } + + // From v2.0, the data from getSurveyStatus (UBX-NAV-SVIN) is returned in UBX_NAV_SVIN_t packetUBXNAVSVIN + // Please see u-blox_structs.h for the full definition of UBX_NAV_SVIN_t + // You can either read the data from packetUBXNAVSVIN directly + // or can use the helper functions: getSurveyInActive; getSurveyInValid; getSurveyInObservationTime; and getSurveyInMeanAccuracy + response = myGPS.getSurveyStatus(2000); //Query module for SVIN status with 2000ms timeout (req can take a long time) + if (response == true) + { + Serial.print(F("Press x to end survey - ")); + Serial.print(F("Time elapsed: ")); + Serial.print((String)myGPS.getSurveyInObservationTime()); // Call the helper function + + lcd.setCursor(0, 1); + lcd.print(F("Elapsed: ")); + lcd.print((String)myGPS.getSurveyInObservationTime()); // Call the helper function + + Serial.print(F(" Accuracy: ")); + Serial.print((String)myGPS.getSurveyInMeanAccuracy()); // Call the helper function + Serial.println(); + + lcd.setCursor(0, 2); + lcd.print(F("Accuracy: ")); + lcd.print((String)myGPS.getSurveyInMeanAccuracy()); // Call the helper function + } + else + { + Serial.println(F("SVIN request failed")); + } + + delay(1000); + } + Serial.println(F("Survey valid!")); + + Serial.println(F("Base survey complete! RTCM now broadcasting.")); + lcd.clear(); + lcd.print(F("Transmitting RTCM")); + + myGPS.setI2COutput(COM_TYPE_UBX | COM_TYPE_RTCM3); //Set the I2C port to output UBX and RTCM sentences (not really an option, turns on NMEA as well) + +} + +void loop() +{ + myGPS.checkUblox(); //See if new data is available. Process bytes as they come in. + + //Do anything you want. Call checkUblox() every second. ZED-F9P has TX buffer of 4k bytes. + + delay(250); //Don't pound too hard on the I2C bus +} + +//This function gets called from the SparkFun u-blox Arduino Library. +//As each RTCM byte comes in you can specify what to do with it +//Useful for passing the RTCM correction data to a radio, Ntrip broadcaster, etc. +void SFE_UBLOX_GPS::processRTCM(uint8_t incoming) +{ + //Let's just pretty-print the HEX values for now + if (myGPS.rtcmFrameCounter % 16 == 0) + Serial.println(); + Serial.print(" "); + if (incoming < 0x10) + Serial.print("0"); + Serial.print(incoming, HEX); +} diff --git a/examples/ZED-F9P/Example5_RelativePositioningInformation/Example5_RelativePositioningInformation.ino b/examples/ZED-F9P/Example5_RelativePositioningInformation/Example5_RelativePositioningInformation.ino new file mode 100644 index 0000000..c433593 --- /dev/null +++ b/examples/ZED-F9P/Example5_RelativePositioningInformation/Example5_RelativePositioningInformation.ino @@ -0,0 +1,165 @@ +/* + Send UBX binary commands to enable RTCM sentences on u-blox ZED-F9P module + By: Nathan Seidle + SparkFun Electronics + Date: January 9th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query the module for RELPOS information in the NED frame. + It assumes you already have RTCM correction data being fed to the receiver. + + 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 RedBoard Qwiic or 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +//#define USE_SERIAL1 // Uncomment this line to push the RTCM data from Serial1 to the module via I2C + +size_t numBytes = 0; // Record the number os bytes received from Serial1 + +void setup() +{ + Serial.begin(115200); + while (!Serial); //Wait for user to open terminal + Serial.println("u-blox Base station example"); + +#ifdef USE_SERIAL1 + // If our board supports it, we can receive the RTCM data on Serial1 + Serial1.begin(115200); +#endif + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.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. Freezing.")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + //myGPS.factoryDefault(); delay(5000); + +#ifdef USE_SERIAL1 + Serial.print(F("Enabling UBX and RTCM input on I2C. Result: ")); + Serial.print(myGPS.setPortInput(COM_PORT_I2C, COM_TYPE_UBX | COM_TYPE_RTCM3)); //Enable UBX and RTCM input on I2C + myGPS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save the communications port settings to flash and BBR +#endif +} + +void loop() +{ + // From v2.0, the data from getRELPOSNED (UBX-NAV-RELPOSNED) is returned in UBX_NAV_RELPOSNED_t packetUBXNAVRELPOSNED + // Please see u-blox_structs.h for the full definition of UBX_NAV_RELPOSNED_t + // You can either read the data from packetUBXNAVRELPOSNED directly + // or can use the helper functions: getRelPosN/E/D; getRelPosAccN/E/D + if (myGPS.getRELPOSNED() == true) + { + Serial.print("relPosN: "); + Serial.println(myGPS.getRelPosN(), 4); // Use the helper functions to get the rel. pos. as m + Serial.print("relPosE: "); + Serial.println(myGPS.getRelPosE(), 4); + Serial.print("relPosD: "); + Serial.println(myGPS.getRelPosD(), 4); + + Serial.print("relPosLength: "); + Serial.println(myGPS.packetUBXNAVRELPOSNED->data.relPosLength); + Serial.print("relPosHeading: "); + Serial.println(myGPS.packetUBXNAVRELPOSNED->data.relPosHeading); + + Serial.print("relPosHPN: "); + Serial.println(myGPS.packetUBXNAVRELPOSNED->data.relPosHPN); + Serial.print("relPosHPE: "); + Serial.println(myGPS.packetUBXNAVRELPOSNED->data.relPosHPE); + Serial.print("relPosHPD: "); + Serial.println(myGPS.packetUBXNAVRELPOSNED->data.relPosHPD); + Serial.print("relPosHPLength: "); + Serial.println(myGPS.packetUBXNAVRELPOSNED->data.relPosHPLength); + + Serial.print("accN: "); + Serial.println(myGPS.getRelPosAccN(), 4); // Use the helper functions to get the rel. pos. accuracy as m + Serial.print("accE: "); + Serial.println(myGPS.getRelPosAccE(), 4); + Serial.print("accD: "); + Serial.println(myGPS.getRelPosAccD(), 4); + + Serial.print("gnssFixOk: "); + if (myGPS.packetUBXNAVRELPOSNED->data.flags.bits.gnssFixOK == true) + Serial.println("x"); + else + Serial.println(""); + + Serial.print("diffSolution: "); + if (myGPS.packetUBXNAVRELPOSNED->data.flags.bits.diffSoln == true) + Serial.println("x"); + else + Serial.println(""); + + Serial.print("relPosValid: "); + if (myGPS.packetUBXNAVRELPOSNED->data.flags.bits.relPosValid == true) + Serial.println("x"); + else + Serial.println(""); + + Serial.print("carrier Solution Type: "); + if (myGPS.packetUBXNAVRELPOSNED->data.flags.bits.carrSoln == 0) + Serial.println("None"); + else if (myGPS.packetUBXNAVRELPOSNED->data.flags.bits.carrSoln == 1) + Serial.println("Float"); + else if (myGPS.packetUBXNAVRELPOSNED->data.flags.bits.carrSoln == 2) + Serial.println("Fixed"); + + Serial.print("isMoving: "); + if (myGPS.packetUBXNAVRELPOSNED->data.flags.bits.isMoving == true) + Serial.println("x"); + else + Serial.println(""); + + Serial.print("refPosMiss: "); + if (myGPS.packetUBXNAVRELPOSNED->data.flags.bits.refPosMiss == true) + Serial.println("x"); + else + Serial.println(""); + + Serial.print("refObsMiss: "); + if (myGPS.packetUBXNAVRELPOSNED->data.flags.bits.refObsMiss == true) + Serial.println("x"); + else + Serial.println(""); + } + else + Serial.println("RELPOS request failed"); + + for (int i = 0; i < 500; i++) + { +#ifdef USE_SERIAL1 + uint8_t store[256]; + while ((Serial1.available()) && (numBytes < 256)) // Check if data has been received + { + store[numBytes++] = Serial1.read(); // Read a byte from Serial1 and store it + } + if (numBytes > 0) // Check if data was received + { + //Serial.print("Pushing "); + //Serial.print(numBytes); + //Serial.println(" bytes via I2C"); + myGPS.pushRawData(((uint8_t *)&store), numBytes); // Push the RTCM data via I2C + numBytes = 0; // Reset numBytes + } +#endif + delay(10); + } +} diff --git a/examples/ZED-F9P/Example6_GetVal/Example6_GetVal.ino b/examples/ZED-F9P/Example6_GetVal/Example6_GetVal.ino new file mode 100644 index 0000000..da42c32 --- /dev/null +++ b/examples/ZED-F9P/Example6_GetVal/Example6_GetVal.ino @@ -0,0 +1,60 @@ +/* + Get a device's I2C address using advanced getVal method + By: Nathan Seidle + SparkFun Electronics + Date: January 9th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + u-blox changed how to configure their modules in 2019. As of version 23 of the UBX protocol the + UBX-CFG commands are deprecated; they still work, they just recommend using VALSET, VALGET, and VALDEL + commands instead. This example shows how to use this new command structure. + + 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 RedBoard Qwiic or 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("u-blox getVal example"); + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + //myGPS.enableDebugging(); //Enable debug messages over Serial (default) + //myGPS.enableDebugging(SerialUSB); //Enable debug messages over Serial USB + + uint8_t currentI2Caddress = myGPS.getVal8(UBLOX_CFG_I2C_ADDRESS); + Serial.print("Current I2C address (should be 0x42): 0x"); + Serial.println(currentI2Caddress >> 1, HEX); //u-blox module returns a shifted 8-bit address. Make it 7-bit unshifted. +} + +void loop() +{ + // Nothing to do here +} diff --git a/examples/ZED-F9P/Example7_SetVal/Example7_SetVal.ino b/examples/ZED-F9P/Example7_SetVal/Example7_SetVal.ino new file mode 100644 index 0000000..cbe7d36 --- /dev/null +++ b/examples/ZED-F9P/Example7_SetVal/Example7_SetVal.ino @@ -0,0 +1,76 @@ +/* + Send UBX binary commands to enable RTCM sentences on u-blox ZED-F9P module + By: Nathan Seidle + SparkFun Electronics + Date: January 9th, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + u-blox changed how to configure their modules in 2019. As of version 23 of the UBX protocol the + UBX-CFG commands are deprecated; they still work, they just recommend using VALSET, VALGET, and VALDEL + commands instead. This example shows how to use this new command structure. + + 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 RedBoard Qwiic or 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +long lastTime = 0; //Simple local timer. Limits amount if I2C traffic to u-blox module. + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("u-blox getVal example"); + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + //myGPS.enableDebugging(); //Enable debug messages over Serial (default) + //myGPS.enableDebugging(SerialUSB); //Enable debug messages over Serial USB + + bool setValueSuccess; + + //These key values are hard coded and defined in u-blox_config_keys.h. + //You can obtain them from the ZED-F9P interface description doc + //or from u-center's Messages->CFG->VALSET window. Keys must be 32-bit. + //setValueSuccess = myGPS.setVal(UBLOX_CFG_NMEA_HIGHPREC, 0); //Enable high precision NMEA + //setValueSuccess = myGPS.setVal(UBLOX_CFG_RATE_MEAS, 1000); //Set measurement rate to 100ms (10Hz update rate) + setValueSuccess = myGPS.setVal(UBLOX_CFG_RATE_MEAS, 1000); //Set measurement rate to 1000ms (1Hz update rate) + + //Below is the original way we enabled the RTCM message on the I2C port. After that, we show how to do the same + //but with setVal(). + //Original: myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + //setValueSuccess = myGPS.setVal(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_I2C, 1); //Set output rate of msg 1005 over the I2C port to once per second + + if (setValueSuccess == true) + { + Serial.println("Value was successfully set"); + } + else + Serial.println("Value set failed"); +} + +void loop() +{ +} diff --git a/examples/ZED-F9P/Example8_GetSetPortSettings/Example8_GetSetPortSettings.ino b/examples/ZED-F9P/Example8_GetSetPortSettings/Example8_GetSetPortSettings.ino new file mode 100644 index 0000000..242cfb4 --- /dev/null +++ b/examples/ZED-F9P/Example8_GetSetPortSettings/Example8_GetSetPortSettings.ino @@ -0,0 +1,98 @@ +/* + Configuring port settings using the newer getVal/setVal methods + By: Nathan Seidle + SparkFun Electronics + Date: October 23rd, 2020 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to query a u-blox module for its UART1 settings and + then change them if the settings aren't what we want. + + Note: getVal/setVal/delVal are only support in u-blox protocol versions 27 and higher. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + + Hardware Connections: + Plug a Qwiic cable into the GNSS and a RedBoard + 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("SparkFun u-blox Example"); + + Wire.begin(); + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + bool response = true; + + //Read the settings from RAM (what the module is running right now, not BBR, Flash, or default) + uint8_t currentUART1Setting_ubx = myGPS.getVal8(UBLOX_CFG_UART1INPROT_UBX); + uint8_t currentUART1Setting_nmea = myGPS.getVal8(UBLOX_CFG_UART1INPROT_NMEA); + uint8_t currentUART1Setting_rtcm3 = myGPS.getVal8(UBLOX_CFG_UART1INPROT_RTCM3X); + + Serial.print("currentUART1Setting_ubx: "); + Serial.println(currentUART1Setting_ubx); + Serial.print("currentUART1Setting_nmea: "); + Serial.println(currentUART1Setting_nmea); + Serial.print("currentUART1Setting_rtcm3: "); + Serial.println(currentUART1Setting_rtcm3); + + //Check if NMEA and RTCM are enabled for UART1 + if (currentUART1Setting_ubx == 0 || currentUART1Setting_nmea == 0) + { + Serial.println("Updating UART1 configuration"); + + //setVal sets the values for RAM, BBR, and Flash automatically so no .saveConfiguration() is needed + response &= myGPS.setVal8(UBLOX_CFG_UART1INPROT_UBX, 1); //Enable UBX on UART1 Input + response &= myGPS.setVal8(UBLOX_CFG_UART1INPROT_NMEA, 1); //Enable NMEA on UART1 Input + response &= myGPS.setVal8(UBLOX_CFG_UART1INPROT_RTCM3X, 0); //Disable RTCM on UART1 Input + + if (response == false) + Serial.println("SetVal failed"); + else + Serial.println("SetVal succeeded"); + } + else + Serial.println("No port change needed"); + + //Change speed of UART2 + uint32_t currentUART2Baud = myGPS.getVal32(UBLOX_CFG_UART2_BAUDRATE); + Serial.print("currentUART2Baud: "); + Serial.println(currentUART2Baud); + + if (currentUART2Baud != 57600) + { + response &= myGPS.setVal32(UBLOX_CFG_UART2_BAUDRATE, 57600); + if (response == false) + Serial.println("SetVal failed"); + else + Serial.println("SetVal succeeded"); + } + else + Serial.println("No baud change needed"); + + Serial.println("Done"); +} + +void loop() +{ +} diff --git a/examples/ZED-F9P/Example9_multiSetVal/Example9_multiSetVal.ino b/examples/ZED-F9P/Example9_multiSetVal/Example9_multiSetVal.ino new file mode 100644 index 0000000..8a161a6 --- /dev/null +++ b/examples/ZED-F9P/Example9_multiSetVal/Example9_multiSetVal.ino @@ -0,0 +1,90 @@ +/* + Send UBX binary commands to enable RTCM sentences on u-blox ZED-F9P module + Based on Example7 By: Nathan Seidle + SparkFun Electronics + Updated by Paul Clark to demonstrate setVal8/16/32, newCfgValset8/16/32, addCfgValset8/16/32 and sendCfgValset8/16/32 + Date: July 1st, 2019 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + u-blox changed how to configure their modules in 2019. As of version 23 of the UBX protocol the + UBX-CFG commands are deprecated; they still work, they just recommend using VALSET, VALGET, and VALDEL + commands instead. This example shows how to use this new command structure. + + 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 RedBoard Qwiic or 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 "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GPS myGPS; + +void setup() +{ + Serial.begin(115200); + while (!Serial) + ; //Wait for user to open terminal + Serial.println("u-blox multi setVal example"); + + Wire.begin(); + Wire.setClock(400000); //Increase I2C clock speed to 400kHz + + if (myGPS.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. Freezing.")); + while (1) + ; + } + + //myGPS.enableDebugging(); //Enable debug messages over Serial (default) + //myGPS.enableDebugging(SerialUSB); //Enable debug messages over Serial USB + + bool setValueSuccess = true; + + //These key values are hard coded. You can obtain them from the ZED-F9P interface description doc + //or from u-center's Messages->CFG->VALSET window. Keys must be 32-bit. + //Choose setVal8, setVal16 or setVal32 depending on the required value data width (1, 2 or 4 bytes) + //L, U1, I1, E1 and X1 values are 8-bit + //U2, I2, E2 and X2 values are 16-bit + //U4, I4, R4, E4, X4 values are 32-bit + + setValueSuccess &= myGPS.setVal8(UBLOX_CFG_NMEA_HIGHPREC, 0); //Enable high precision NMEA (value is 8-bit (L / U1)) + //setValueSuccess &= myGPS.setVal16(UBLOX_CFG_RATE_MEAS, 200); //Set measurement rate to 100ms (10Hz update rate) (value is 16-bit (U2)) + //setValueSuccess &= myGPS.setVal16(UBLOX_CFG_RATE_MEAS, 200, 1); //Set rate setting in RAM instead of BBR + setValueSuccess &= myGPS.setVal16(UBLOX_CFG_RATE_MEAS, 1000); //Set measurement rate to 1000ms (1Hz update rate) (value is 16-bit (U2)) + + //Below is the original way we enabled a single RTCM message on the I2C port. After that, we show how to do the same + //but with multiple messages all in one go using newCfgValset, addCfgValset and sendCfgValset. + //Original: myGPS.enableRTCMmessage(UBX_RTCM_1005, COM_PORT_I2C, 1); //Enable message 1005 to output through I2C port, message every second + + //Begin with newCfgValset8/16/32 + setValueSuccess &= myGPS.newCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_I2C, 1); //Set output rate of msg 1005 over the I2C port to once per measurement (value is 8-bit (U1)) + //setValueSuccess &= myGPS.newCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_I2C, 1, VAL_LAYER_RAM); //Set this and the following settings in RAM only instead of Flash/RAM/BBR + //Add extra keyIDs and values using addCfgValset8/16/32 + setValueSuccess &= myGPS.addCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_I2C, 1); //Set output rate of msg 1077 over the I2C port to once per measurement (value is 8-bit (U1)) + setValueSuccess &= myGPS.addCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_I2C, 1); //Set output rate of msg 1087 over the I2C port to once per measurement (value is 8-bit (U1)) + setValueSuccess &= myGPS.addCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_I2C, 1); //Set output rate of msg 1127 over the I2C port to once per measurement (value is 8-bit (U1)) + setValueSuccess &= myGPS.addCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_I2C, 1); //Set output rate of msg 1097 over the I2C port to once per measurement (value is 8-bit (U1)) + // Add the final value and send the packet using sendCfgValset8/16/32 + setValueSuccess &= myGPS.sendCfgValset8(UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_I2C, 10); //Set output rate of msg 1230 over the I2C port to once every 10 measurements (value is 8-bit (U1)) + + if (setValueSuccess == true) + { + Serial.println("Values were successfully set"); + } + else + Serial.println("Value set failed"); +} + +void loop() +{ +} diff --git a/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/CMakeLists.txt b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/CMakeLists.txt new file mode 100644 index 0000000..3dd6c0a --- /dev/null +++ b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/CMakeLists.txt @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) +project(sparkfun_ublox_zephyr_library) + +zephyr_compile_options(-fdiagnostics-color=always) + +zephyr_include_directories(.) +target_sources(app PRIVATE src/SparkFun_Ublox_Zephyr_Library.cpp) +target_sources(app PRIVATE src/SparkFun_Ublox_Zephyr_Interface.cpp) + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c) diff --git a/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/nrf52840dk_nrf52840.overlay b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000..3c95572 --- /dev/null +++ b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/nrf52840dk_nrf52840.overlay @@ -0,0 +1,4 @@ +&i2c0 { + status = "okay"; + compatible = "nordic,nrf-twim"; +}; \ No newline at end of file diff --git a/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/prj.conf b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/prj.conf new file mode 100644 index 0000000..e239329 --- /dev/null +++ b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/prj.conf @@ -0,0 +1,7 @@ +#turn on c++ support +CONFIG_CPLUSPLUS=y + +# turn on peripherals +CONFIG_GPIO=y +CONFIG_I2C=y +CONFIG_I2C_0=y diff --git a/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.cpp b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.cpp new file mode 100644 index 0000000..ea96afe --- /dev/null +++ b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.cpp @@ -0,0 +1,100 @@ +/* + This is an interface that connects the CPP u-blox library with the main C code. + Added to make it possible to run u-blox lib on Zephyr (NCS) + + This port was made by Vid Rajtmajer , www.irnas.eu +*/ +#include "SparkFun_Ublox_Zephyr_Interface.h" + +#include +#include +#include +#include + +#include "SparkFun_Ublox_Zephyr_Library.h" + + +SFE_UBLOX_GPS myGPS; // driver class instance +long lastTime = 0; // Simple local timer. Limits amount if I2C traffic to u-blox module. + +// init GPIO checksumFailurePin and load GPIO device pointer to the driver +uint8_t set_gpio_dev(struct device *gpio_dev, uint8_t enable_debug) +{ + if (myGPS.init_gpio_pins(*gpio_dev) == false) + { + return -EIO; + } + // turn on debugging if enable_debug is set + if (enable_debug) + { + myGPS.enableDebugging(); + } + return 0; +} + +// initialize I2C and check if GPS device respons +uint8_t gps_begin(struct device *i2c_dev) +{ + if (myGPS.begin(*i2c_dev) == false) + { + return -EIO; + } + return 0; +} + +// This will pipe all NMEA sentences to UART so we can see them +void pipe_nmea_sentences(void) +{ + myGPS.setNMEAOutputPort(); +} + +// Check for available bytes from the device +void check_ublox(void) +{ + myGPS.checkUblox(); +} + +// Get position information when requested, also display number of satellites used in the fix +int get_position(void) +{ + //Query module only every second. Doing it more often will just cause I2C traffic. + //The module only responds when a new position is available, print it to console + if (k_uptime_get_32() - lastTime > 1000) + { + lastTime = k_uptime_get_32(); //Update the timer + + long latitude = myGPS.getLatitude(); + long longitude = myGPS.getLongitude(); + long altitude = myGPS.getAltitude(); + uint8_t SIV = myGPS.getSIV(); + + printk("Position: Lat: %ld, Lon: %ld, Alt: %ld, SIV: %d", latitude, longitude, altitude, SIV); + return 0; + } + return -EBUSY; +} + +// Get date and time information when requested, check if they are valid and print info to console, it returns UNIX time +void get_datetime(void) +{ + int year = myGPS.getYear(); + int month = myGPS.getMonth(); + int day = myGPS.getDay(); + int hour = myGPS.getHour(); + int minute = myGPS.getMinute(); + int second = myGPS.getSecond(); + + printk("DateTime: %d-%d-%d %d:%d:%d\n", year, month, day, hour, minute, second); + + printk("Time is "); + if (myGPS.getTimeValid() == false) + { + printk("not "); + } + printk("valid. Date is "); + if (myGPS.getDateValid() == false) + { + printk("not "); + } + printk("valid.\n"); +} diff --git a/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.h b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.h new file mode 100644 index 0000000..a4e23e4 --- /dev/null +++ b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Interface.h @@ -0,0 +1,29 @@ +/* + This is an interface that connects the CPP u-blox library with the main C code. + Added to make it possible to run u-blox lib on Zephyr (NCS) + + This port was made by Vid Rajtmajer , www.irnas.eu +*/ +#include +#include + +#ifndef _UBLOX_LIB_INTERFACE_H_ +#define _UBLOX_LIB_INTERFACE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + uint8_t set_gpio_dev(struct device *gpio_dev, uint8_t enable_debug); // init GPIO + uint8_t gps_begin(struct device *i2c_dev); // initialize I2C and check if GPS device respons + void pipe_nmea_sentences(void); // print NMEA sentences + + void check_ublox(void); // Check for available bytes from the device + int get_position(void); // Get position information + void get_datetime(void); // Get date and time information + +#ifdef __cplusplus +} +#endif + +#endif //UBLOX_LIB_INTERFACE_H_ \ No newline at end of file diff --git a/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.cpp b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.cpp new file mode 100644 index 0000000..0e77979 --- /dev/null +++ b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.cpp @@ -0,0 +1,3492 @@ +/* + This is a library written for the u-blox ZED-F9P and NEO-M8P-2 + SparkFun sells these at its website: www.sparkfun.com + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Written by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + + This library handles configuring and handling the responses + from a u-blox GPS module. Works with most modules from u-blox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + NCS v1.0.3 release - this port + + This port was made by Vid Rajtmajer , www.irnas.eu + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "SparkFun_Ublox_Zephyr_Library.h" +#include + +SFE_UBLOX_GPS::SFE_UBLOX_GPS(void) +{ + // Constructor + currentGeofenceParams.numFences = 0; // Zero the number of geofences currently in use + moduleQueried.versionNumber = false; +} + +// Get i2c device to the class and configure checksumFailurePin +bool SFE_UBLOX_GPS::init_gpio_pins(struct device &gpio_dev) +{ + _gpio_dev = &gpio_dev; + if (checksumFailurePin >= 0) + { + int err; + err = gpio_pin_configure(_gpio_dev, (uint8_t)checksumFailurePin, GPIO_OUTPUT); + err = gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, HIGH); + if (err) + { + return false; + } + } + return true; +} + +//Initialize the I2C port +bool SFE_UBLOX_GPS::begin(struct device &i2c_dev, uint8_t deviceAddress) +{ + commType = COMM_TYPE_I2C; + _i2cPort = &i2c_dev; //Grab which port the user wants us to use + + //We expect caller to begin their I2C port, with the speed of their choice external to the library + //But if they forget, we start the hardware here. + + //We're moving away from the practice of starting Wire hardware in a library. This is to avoid cross platform issues. + //ie, there are some platforms that don't handle multiple starts to the wire hardware. Also, every time you start the wire + //hardware the clock speed reverts back to 100kHz regardless of previous Wire.setClocks(). + //_i2cPort->begin(); + + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + + return (isConnected()); +} + +//Initialize the Serial port - function not ported +/* +bool SFE_UBLOX_GPS::begin(Stream &serialPort) +{ + commType = COMM_TYPE_SERIAL; + _serialPort = &serialPort; //Grab which port the user wants us to use + + return (isConnected()); +} +*/ + +//Enable or disable the printing of sent/response HEX values. +//Use this in conjunction with 'Transport Logging' from the Universal Reader Assistant to see what they're doing that we're not +void SFE_UBLOX_GPS::enableDebugging(bool printLimitedDebug) +{ + if (printLimitedDebug == false) + { + _printDebug = true; //Should we print the commands we send? Good for debugging + } + else + { + _printLimitedDebug = true; //Should we print limited debug messages? Good for debugging high navigation rates + } +} + +void SFE_UBLOX_GPS::disableDebugging(void) +{ + _printDebug = false; //Turn off extra print statements + _printLimitedDebug = false; +} + +//Safely print messages +void SFE_UBLOX_GPS::debugPrint(char *message) +{ + if (_printDebug == true) + { + printk("%s", message); + } +} +//Safely print messages +void SFE_UBLOX_GPS::debugPrintln(char *message) +{ + if (_printDebug == true) + { + printk("%s\n", 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_NACK: + return "Command not acknowledged (NACK)"; + 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; + case SFE_UBLOX_STATUS_DATA_OVERWRITTEN: + return "Data Packet Overwritten"; + break; + default: + return "Unknown Status"; + break; + } + return "None"; +} + +void SFE_UBLOX_GPS::factoryReset() +{ + // Copy default settings to permanent + // Note: this does not load the permanent configuration into the current configuration. Calling factoryDefault() will do that. + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 13; + packetCfg.startingSpot = 0; + for (uint8_t i = 0; i < 4; i++) + { + payloadCfg[0 + i] = 0xff; // clear mask: copy default config to permanent config + payloadCfg[4 + i] = 0x00; // save mask: don't save current to permanent + payloadCfg[8 + i] = 0x00; // load mask: don't copy permanent config to current + } + payloadCfg[12] = 0xff; // all forms of permanent memory + sendCommand(&packetCfg, 0); // don't expect ACK + hardReset(); // cause factory default config to actually be loaded and used cleanly +} + +void SFE_UBLOX_GPS::hardReset() +{ + // Issue hard reset + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RST; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + payloadCfg[0] = 0xff; // cold start + payloadCfg[1] = 0xff; // cold start + payloadCfg[2] = 0; // 0=HW reset + payloadCfg[3] = 0; // reserved + sendCommand(&packetCfg, 0); // don't expect ACK +} + +// Write data to I2C, Arguments: buffer - data to write, num_bytes - buffer length, +// stop - if true send STOP, if false send RESTART after message +int SFE_UBLOX_GPS::transferWriteI2C(u8_t *buffer, u32_t num_bytes, bool stop) +{ + struct i2c_msg msgs[1]; + // Data to be written and STOP or RESTART after this. + msgs[0].buf = buffer; + msgs[0].len = num_bytes; + if (stop) { + msgs[0].flags = I2C_MSG_WRITE | I2C_MSG_STOP; + } + else { + msgs[0].flags = I2C_MSG_WRITE | I2C_MSG_RESTART; + } + return i2c_transfer(_i2cPort, &msgs[0], 1, _gpsI2Caddress); +} + +// Read data from I2C, Arguments: buffer - store read data, num_bytes - buffer length +int SFE_UBLOX_GPS::transferReadI2C(u8_t *buffer, u32_t num_bytes) +{ + struct i2c_msg msgs[1]; + // Data to be read and STOP after this + msgs[0].buf = buffer; + msgs[0].len = num_bytes; + msgs[0].flags = I2C_MSG_READ | I2C_MSG_STOP; + + int err = i2c_transfer(_i2cPort, &msgs[0], 1, _gpsI2Caddress); + return err; +} + +//Changes the serial baud rate of the u-blox module, can't return success/fail 'cause ACK from modem +//is lost due to baud rate change +void SFE_UBLOX_GPS::setSerialRate(uint32_t baudrate, uint8_t uartPort, uint16_t maxWait) +{ + //Get the current config values for the UART port + getPortSettings(uartPort, maxWait); //This will load the payloadCfg array with current port settings + + if (_printDebug == true) + { + printk("Current baud rate: "); + printk("%d\n", ((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[8] = baudrate; + payloadCfg[9] = baudrate >> 8; + payloadCfg[10] = baudrate >> 16; + payloadCfg[11] = baudrate >> 24; + + if (_printDebug == true) + { + printk("New baud rate: "); + printk("%d\n", ((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + printk("setSerialRate: sendCommand returned: %s\n", statusString(retVal)); + } +} + +//Changes the I2C address that the u-blox module responds to +//0x42 is the default but can be changed with this command +bool SFE_UBLOX_GPS::setI2CAddress(uint8_t deviceAddress, uint16_t maxWait) +{ + //Get the current config values for the I2C port + getPortSettings(COM_PORT_I2C, maxWait); //This will load the payloadCfg array with current port settings + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[4] = deviceAddress << 1; //DDC mode LSB + + if (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT) // We are only expecting an ACK + { + //Success! Now change our internal global. + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + return (true); + } + return (false); +} + +//Want to see the NMEA messages on the Serial port? Here's how - ported to print to console, here only enable the flag +void SFE_UBLOX_GPS::setNMEAOutputPort() +{ + _nmeaOutputPort = true; +} + +//Called regularly to check for available bytes on the user' specified port +bool SFE_UBLOX_GPS::checkUblox(uint8_t requestedClass, uint8_t requestedID) +{ + return checkUbloxInternal(&packetCfg, requestedClass, requestedID); +} + +//Called regularly to check for available bytes on the user' specified port +bool SFE_UBLOX_GPS::checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (commType == COMM_TYPE_I2C) + return (checkUbloxI2C(incomingUBX, requestedClass, requestedID)); + else if (commType == COMM_TYPE_SERIAL) + return (checkUbloxSerial(incomingUBX, requestedClass, requestedID)); + return false; +} + +//Polls I2C for data, passing any new bytes to process() +//Returns true if new bytes are available +bool SFE_UBLOX_GPS::checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (k_uptime_get_32() - lastCheck >= i2cPollingWait) + { + uint16_t bytesAvailable = 0; + int err; + + //Get the number of bytes available from the module + //0xFD (MSB) and 0xFE (LSB) are the registers that contain number of bytes available + u8_t data_buffer[1]; + data_buffer[0] = 0xFD; + err = transferWriteI2C(data_buffer, 1, true); + if (err) { + return (false); + } + + bool data_read_ok = true; + u8_t read_buffer[2]; + err = transferReadI2C(data_buffer, 2); + if (err) { + data_read_ok = false; + } + + if (data_read_ok) + { + uint8_t msb = read_buffer[0]; + uint8_t lsb = read_buffer[1]; + if (lsb == 0xFF) + { + //I believe this is a u-blox bug. Device should never present an 0xFF. + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + printk("checkUbloxI2C: u-blox bug, length lsb is 0xFF\n"); + } + if (checksumFailurePin >= 0) + { + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, LOW); + k_msleep(10); + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, HIGH); + } + lastCheck = k_uptime_get_32(); //Put off checking to avoid I2C bus traffic + return (false); + } + bytesAvailable = (uint16_t)msb << 8 | lsb; + } + + if (bytesAvailable == 0) + { + if (_printDebug == true) + { + printk("checkUbloxI2C: OK, zero bytes available\n"); + } + lastCheck = k_uptime_get_32(); //Put off checking to avoid I2C bus traffic + return (false); + } + + //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. 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 + bytesAvailable &= ~((uint16_t)1 << 15); + + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + printk("checkUbloxI2C: Bytes available error: %d\n", bytesAvailable); + if (checksumFailurePin >= 0) + { + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, LOW); + k_msleep(10); + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, HIGH); + } + } + } + + if (bytesAvailable > 100) + { + if (_printDebug == true) + { + printk("checkUbloxI2C: Large packet of %d bytes received\n", bytesAvailable); + } + } + else + { + if (_printDebug == true) + { + printk("checkUbloxI2C: Reading %d bytes\n", bytesAvailable); + } + } + + while (bytesAvailable) + { + u8_t data_buffer[1]; + data_buffer[0] = 0xFF; //0xFF is the register to read data from + err = transferWriteI2C(data_buffer, 1, false); //Send a restart command. Do not release bus. + if (err) { + return (false); + } + + //Limit to 32 bytes or whatever the buffer limit is for given platform + uint16_t bytesToRead = bytesAvailable; + if (bytesToRead > I2C_BUFFER_LENGTH) + bytesToRead = I2C_BUFFER_LENGTH; + + TRY_AGAIN: + bool data_read_ok = true; + u8_t read_buffer[(uint8_t)bytesToRead]; + err = transferReadI2C(read_buffer, (uint8_t)bytesToRead); + if (err) { + data_read_ok = false; + } + + if (data_read_ok) + { + for (uint16_t x = 0; x < bytesToRead; x++) + { + uint8_t incoming = read_buffer[x]; //Grab the actual character + + //Check to see if the first read is 0x7F. If it is, the module is not ready + //to respond. Stop, wait, and try again + if (x == 0) + { + if (incoming == 0x7F) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + printk("checkUbloxU2C: u-blox error, module not ready with data\n"); + } + k_msleep(5); //In logic analyzation, the module starting responding after 1.48ms + if (checksumFailurePin >= 0) + { + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, LOW); + k_msleep(10); + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, HIGH); + } + goto TRY_AGAIN; + } + } + process(incoming, incomingUBX, requestedClass, requestedID); //Process this valid character + } + } + else + return (false); //Sensor did not respond + + bytesAvailable -= bytesToRead; + } + } + + return (true); + +} //end checkUbloxI2C() + +//Checks Serial for data, passing any new bytes to process() +bool SFE_UBLOX_GPS::checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + printk("checkUbloxSerial function not ported...\n"); + /* + while (_serialPort->available()) + { + process(_serialPort->read(), incomingUBX, requestedClass, requestedID); + } + */ + return (true); + +} //end checkUbloxSerial() + +//Processes NMEA and UBX binary sentences one byte at a time +//Take a given byte and file it into the proper array +void SFE_UBLOX_GPS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if ((currentSentence == NONE) || (currentSentence == NMEA)) + { + if (incoming == 0xB5) //UBX binary frames start with 0xB5, aka μ + { + //This is the start of a binary sentence. Reset flags. + //We still don't know the response class + ubxFrameCounter = 0; + currentSentence = UBX; + //Reset the packetBuf.counter even though we will need to reset it again when ubxFrameCounter == 2 + packetBuf.counter = 0; + ignoreThisPayload = false; //We should not ignore this payload - yet + //Store data in packetBuf until we know if we have a requested class and ID match + activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + } + else if (incoming == '$') + { + currentSentence = NMEA; + } + else if (incoming == 0xD3) //RTCM frames start with 0xD3 + { + rtcmFrameCounter = 0; + currentSentence = RTCM; + } + else + { + //This character is unknown or we missed the previous start of a sentence + } + } + + //Depending on the sentence, pass the character to the individual processor + if (currentSentence == UBX) + { + //Decide what type of response this is + if ((ubxFrameCounter == 0) && (incoming != 0xB5)) //ISO 'μ' + currentSentence = NONE; //Something went wrong. Reset. + else if ((ubxFrameCounter == 1) && (incoming != 0x62)) //ASCII 'b' + currentSentence = NONE; //Something went wrong. Reset. + // Note to future self: + // There may be some duplication / redundancy in the next few lines as processUBX will also + // load information into packetBuf, but we'll do it here too for clarity + else if (ubxFrameCounter == 2) //Class + { + // Record the class in packetBuf until we know what to do with it + packetBuf.cls = incoming; // (Duplication) + rollingChecksumA = 0; //Reset our rolling checksums here (not when we receive the 0xB5) + rollingChecksumB = 0; + packetBuf.counter = 0; //Reset the packetBuf.counter (again) + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // Reset the packet validity (redundant?) + packetBuf.startingSpot = incomingUBX->startingSpot; //Copy the startingSpot + } + else if (ubxFrameCounter == 3) //ID + { + // Record the ID in packetBuf until we know what to do with it + packetBuf.id = incoming; // (Duplication) + //We can now identify the type of response + //If the packet we are receiving is not an ACK then check for a class and ID match + if (packetBuf.cls != UBX_CLASS_ACK) + { + //This is not an ACK so check for a class and ID match + if ((packetBuf.cls == requestedClass) && (packetBuf.id == requestedID)) + { + //This is not an ACK and we have a class and ID match + //So start diverting data into incomingUBX (usually packetCfg) + activePacketBuffer = SFE_UBLOX_PACKET_PACKETCFG; + incomingUBX->cls = packetBuf.cls; //Copy the class and ID into incomingUBX (usually packetCfg) + incomingUBX->id = packetBuf.id; + incomingUBX->counter = packetBuf.counter; //Copy over the .counter too + } + else + { + //This is not an ACK and we do not have a class and ID match + //so we should keep diverting data into packetBuf and ignore the payload + ignoreThisPayload = true; + } + } + else + { + // This is an ACK so it is to early to do anything with it + // We need to wait until we have received the length and data bytes + // So we should keep diverting data into packetBuf + } + } + else if (ubxFrameCounter == 4) //Length LSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len = incoming; // (Duplication) + } + else if (ubxFrameCounter == 5) //Length MSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len |= incoming << 8; // (Duplication) + } + else if (ubxFrameCounter == 6) //This should be the first byte of the payload unless .len is zero + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + if (_printDebug == true) + { + printk("process: ZERO LENGTH packet received: Class: 0x%x ID: 0x%x\n", packetBuf.cls, packetBuf.id); + } + //If length is zero (!) this will be the first byte of the checksum so record it + packetBuf.checksumA = incoming; + } + else + { + //The length is not zero so record this byte in the payload + packetBuf.payload[0] = incoming; + } + } + else if (ubxFrameCounter == 7) //This should be the second byte of the payload unless .len is zero or one + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + //If length is zero (!) this will be the second byte of the checksum so record it + packetBuf.checksumB = incoming; + } + else if (packetBuf.len == 1) // Check if length is one + { + //The length is one so this is the first byte of the checksum + packetBuf.checksumA = incoming; + } + else // Length is >= 2 so this must be a payload byte + { + packetBuf.payload[1] = incoming; + } + // Now that we have received two payload bytes, we can check for a matching ACK/NACK + if ((activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) // If we are not already processing a data packet + && (packetBuf.cls == UBX_CLASS_ACK) // and if this is an ACK/NACK + && (packetBuf.payload[0] == requestedClass) // and if the class matches + && (packetBuf.payload[1] == requestedID)) // and if the ID matches + { + if (packetBuf.len == 2) // Check if .len is 2 + { + // Then this is a matching ACK so copy it into packetAck + activePacketBuffer = SFE_UBLOX_PACKET_PACKETACK; + packetAck.cls = packetBuf.cls; + packetAck.id = packetBuf.id; + packetAck.len = packetBuf.len; + packetAck.counter = packetBuf.counter; + packetAck.payload[0] = packetBuf.payload[0]; + packetAck.payload[1] = packetBuf.payload[1]; + } + else // Length is not 2 (hopefully this is impossible!) + { + if (_printDebug == true) + { + printk("process: ACK received with .len != 2: Class: 0x%x ID: 0x%x, len: %d\n", packetBuf.payload[0], packetBuf.payload[1], packetBuf.len); + } + } + } + } + + //Divert incoming into the correct buffer + if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETACK) + processUBX(incoming, &packetAck, requestedClass, requestedID); + else if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG) + processUBX(incoming, incomingUBX, requestedClass, requestedID); + else // if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) + processUBX(incoming, &packetBuf, requestedClass, requestedID); + + //Finally, increment the frame counter + ubxFrameCounter++; + } + else if (currentSentence == NMEA) + { + processNMEA(incoming); //Process each NMEA character + } + else if (currentSentence == RTCM) + { + processRTCMframe(incoming); //Deal with RTCM bytes + } +} + +//This is the default or generic NMEA processor. We're only going to pipe the data to serial port so we can see it. +//User could overwrite this function to pipe characters to nmea.process(c) of tinyGPS or MicroNMEA +//Or user could pipe each character to a buffer, radio, etc. +void SFE_UBLOX_GPS::processNMEA(char incoming) +{ + //If user has assigned an output port then pipe the characters there + + if (_nmeaOutputPort) + printk("%c", incoming); //Echo this byte to the serial port +} + +//We need to be able to identify an RTCM packet and then the length +//so that we know when the RTCM message is completely received and we then start +//listening for other sentences (like NMEA or UBX) +//RTCM packet structure is very odd. I never found RTCM STANDARD 10403.2 but +//http://d1.amobbs.com/bbs_upload782111/files_39/ourdev_635123CK0HJT.pdf is good +//https://dspace.cvut.cz/bitstream/handle/10467/65205/F3-BP-2016-Shkalikava-Anastasiya-Prenos%20polohove%20informace%20prostrednictvim%20datove%20site.pdf?sequence=-1 +//Lead me to: https://forum.u-blox.com/index.php/4348/how-to-read-rtcm-messages-from-neo-m8p +//RTCM 3.2 bytes look like this: +//Byte 0: Always 0xD3 +//Byte 1: 6-bits of zero +//Byte 2: 10-bits of length of this packet including the first two-ish header bytes, + 6. +//byte 3 + 4 bits: Msg type 12 bits +//Example: D3 00 7C 43 F0 ... / 0x7C = 124+6 = 130 bytes in this packet, 0x43F = Msg type 1087 +void SFE_UBLOX_GPS::processRTCMframe(uint8_t incoming) +{ + if (rtcmFrameCounter == 1) + { + rtcmLen = (incoming & 0x03) << 8; //Get the last two bits of this byte. Bits 8&9 of 10-bit length + } + else if (rtcmFrameCounter == 2) + { + rtcmLen |= incoming; //Bits 0-7 of packet length + rtcmLen += 6; //There are 6 additional bytes of what we presume is header, msgType, CRC, and stuff + } + /*else if (rtcmFrameCounter == 3) + { + rtcmMsgType = incoming << 4; //Message Type, MS 4 bits + } + else if (rtcmFrameCounter == 4) + { + rtcmMsgType |= (incoming >> 4); //Message Type, bits 0-7 + }*/ + + rtcmFrameCounter++; + + processRTCM(incoming); //Here is where we expose this byte to the user + + if (rtcmFrameCounter == rtcmLen) + { + //We're done! + currentSentence = NONE; //Reset and start looking for next sentence type + } +} + +//This function is called for each byte of an RTCM frame +//Ths user can overwrite this function and process the RTCM frame as they please +//Bytes can be piped to Serial or other interface. The consumer could be a radio or the internet (Ntrip broadcaster) +void SFE_UBLOX_GPS::processRTCM(uint8_t incoming) +{ + //Radio.sendReliable((String)incoming); //An example of passing this byte to a radio + + //Debug printing + // printk(" "); + // if(incoming < 0x10) printk("0"); + // if(incoming < 0x10) printk("0"); + // printk("%d", incoming); + // if(rtcmFrameCounter % 16 == 0) printk("\n"); +} + +//Given a character, file it away into the uxb packet structure +//Set valid to VALID or NOT_VALID once sentence is completely received and passes or fails CRC +//The payload portion of the packet can be 100s of bytes but the max array +//size is MAX_PAYLOAD_SIZE bytes. startingSpot can be set so we only record +//a subset of bytes within a larger packet. +void SFE_UBLOX_GPS::processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + //Add all incoming bytes to the rolling checksum + //Stop at len+4 as this is the checksum bytes to that should not be added to the rolling checksum + if (incomingUBX->counter < incomingUBX->len + 4) + addToChecksum(incoming); + + if (incomingUBX->counter == 0) + { + incomingUBX->cls = incoming; + } + else if (incomingUBX->counter == 1) + { + incomingUBX->id = incoming; + } + else if (incomingUBX->counter == 2) //Len LSB + { + incomingUBX->len = incoming; + } + else if (incomingUBX->counter == 3) //Len MSB + { + incomingUBX->len |= incoming << 8; + } + else if (incomingUBX->counter == incomingUBX->len + 4) //ChecksumA + { + incomingUBX->checksumA = incoming; + } + else if (incomingUBX->counter == incomingUBX->len + 5) //ChecksumB + { + incomingUBX->checksumB = incoming; + + currentSentence = NONE; //We're done! Reset the sentence to being looking for a new start char + + //Validate this sentence + if ((incomingUBX->checksumA == rollingChecksumA) && (incomingUBX->checksumB == rollingChecksumB)) + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_VALID; // Flag the packet as valid + + // Let's check if the class and ID match the requestedClass and requestedID + // Remember - this could be a data packet or an ACK packet + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is a NACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_NACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_NOTACKNOWLEDGED; // If we have a match, set the classAndIDmatch flag to NOTACKNOWLEDGED + if (_printDebug == true) + { + printk("processUBX: NACK received: Requested Class: 0x%x, Requested ID: 0x%x\n", incomingUBX->payload[0], incomingUBX->payload[1]); + } + } + + if (_printDebug == true) + { + printk("Incoming: Size: %d Received: \n", incomingUBX->len); + printPacket(incomingUBX); + + if (incomingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + printk("packetCfg now valid\n"); + } + if (packetAck.valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + printk("packetAck now valid\n"); + } + if (incomingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + printk("packetCfg classAndIDmatch\n"); + } + if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + printk("packetAck classAndIDmatch\n"); + } + } + + //We've got a valid packet, now do something with it but only if ignoreThisPayload is false + if (ignoreThisPayload == false) + { + processUBXpacket(incomingUBX); + } + } + else // Checksum failure + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; + + // Let's check if the class and ID match the requestedClass and requestedID. + // This is potentially risky as we are saying that we saw the requested Class and ID + // but that the packet checksum failed. Potentially it could be the class or ID bytes + // that caused the checksum error! + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + + if ((_printDebug == true) || (_printLimitedDebug == true)) // Print this if doing limited debugging + { + //Drive an external pin to allow for easier logic analyzation + if (checksumFailurePin >= 0) + { + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, LOW); + k_msleep(10); + gpio_pin_set(_gpio_dev, (uint8_t)checksumFailurePin, HIGH); + } + + printk("Checksum failed: checksumA: %d checksumB: %d\n", incomingUBX->checksumA, incomingUBX->checksumB); + printk("rollingChecksumA: %d, rollingChecksumB: %d\n", rollingChecksumA, rollingChecksumB); + printk("Failed: Size: %d Received: \n", incomingUBX->len); + printPacket(incomingUBX); + } + } + } + else //Load this byte into the payload array + { + //If a UBX_NAV_PVT packet comes in asynchronously, we need to fudge the startingSpot + uint16_t startingSpot = incomingUBX->startingSpot; + if (incomingUBX->cls == UBX_CLASS_NAV && incomingUBX->id == UBX_NAV_PVT) + startingSpot = 0; + //Begin recording if counter goes past startingSpot + if ((incomingUBX->counter - 4) >= startingSpot) + { + //Check to see if we have room for this byte + if (((incomingUBX->counter - 4) - startingSpot) < MAX_PAYLOAD_SIZE) //If counter = 208, starting spot = 200, we're good to record. + { + // Check if this is payload data which should be ignored + if (ignoreThisPayload == false) + { + incomingUBX->payload[incomingUBX->counter - 4 - startingSpot] = incoming; //Store this byte into payload array + } + } + } + } + + //Increment the counter + incomingUBX->counter++; + + if (incomingUBX->counter == MAX_PAYLOAD_SIZE) + { + //Something has gone very wrong + currentSentence = NONE; //Reset the sentence to being looking for a new start char + if (_printDebug == true) + { + printk("processUBX: counter hit MAX_PAYLOAD_SIZE\n"); + } + } +} + +//Once a packet has been received and validated, identify this packet's class/id and update internal flags +//Note: if the user requests a PVT or a HPPOSLLH message using a custom packet, the data extraction will +// not work as expected beacuse extractLong etc are hardwired to packetCfg payloadCfg. Ideally +// extractLong etc should be updated so they receive a pointer to the packet buffer. +void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) +{ + switch (msg->cls) + { + case UBX_CLASS_NAV: + if (msg->id == UBX_NAV_PVT && msg->len == 92) + { + //Parse various byte fields into global vars + constexpr int startingSpot = 0; //fixed value used in processUBX + + timeOfWeek = extractLong(0); + gpsMillisecond = extractLong(0) % 1000; //Get last three digits of iTOW + gpsYear = extractInt(4); + gpsMonth = extractByte(6); + gpsDay = extractByte(7); + gpsHour = extractByte(8); + gpsMinute = extractByte(9); + gpsSecond = extractByte(10); + gpsDateValid = extractByte(11) & 0x01; + gpsTimeValid = (extractByte(11) & 0x02) >> 1; + gpsNanosecond = extractLong(16); //Includes milliseconds + + fixType = extractByte(20 - startingSpot); + carrierSolution = extractByte(21 - startingSpot) >> 6; //Get 6th&7th bits of this byte + SIV = extractByte(23 - startingSpot); + longitude = extractLong(24 - startingSpot); + latitude = extractLong(28 - startingSpot); + altitude = extractLong(32 - startingSpot); + altitudeMSL = extractLong(36 - startingSpot); + groundSpeed = extractLong(60 - startingSpot); + headingOfMotion = extractLong(64 - startingSpot); + pDOP = extractInt(76 - startingSpot); + + //Mark all datums as fresh (not read before) + moduleQueried.gpsiTOW = true; + moduleQueried.gpsYear = true; + moduleQueried.gpsMonth = true; + moduleQueried.gpsDay = true; + moduleQueried.gpsHour = true; + moduleQueried.gpsMinute = true; + moduleQueried.gpsSecond = true; + moduleQueried.gpsDateValid = true; + moduleQueried.gpsTimeValid = true; + moduleQueried.gpsNanosecond = true; + + moduleQueried.all = true; + moduleQueried.longitude = true; + moduleQueried.latitude = true; + moduleQueried.altitude = true; + moduleQueried.altitudeMSL = true; + moduleQueried.SIV = true; + moduleQueried.fixType = true; + moduleQueried.carrierSolution = true; + moduleQueried.groundSpeed = true; + moduleQueried.headingOfMotion = true; + moduleQueried.pDOP = true; + } + else if (msg->id == UBX_NAV_HPPOSLLH && msg->len == 36) + { + timeOfWeek = extractLong(4); + highResLongitude = extractLong(8); + highResLatitude = extractLong(12); + elipsoid = extractLong(16); + meanSeaLevel = extractLong(20); + highResLongitudeHp = extractSignedChar(24); + highResLatitudeHp = extractSignedChar(25); + elipsoidHp = extractSignedChar(26); + meanSeaLevelHp = extractSignedChar(27); + horizontalAccuracy = extractLong(28); + verticalAccuracy = extractLong(32); + + highResModuleQueried.all = true; + highResModuleQueried.highResLatitude = true; + highResModuleQueried.highResLatitudeHp = true; + highResModuleQueried.highResLongitude = true; + highResModuleQueried.highResLongitudeHp = true; + highResModuleQueried.elipsoid = true; + highResModuleQueried.elipsoidHp = true; + highResModuleQueried.meanSeaLevel = true; + highResModuleQueried.meanSeaLevelHp = true; + highResModuleQueried.geoidSeparation = true; + highResModuleQueried.horizontalAccuracy = true; + highResModuleQueried.verticalAccuracy = true; + moduleQueried.gpsiTOW = true; // this can arrive via HPPOS too. + + if (_printDebug == true) + { + printk("Sec: %f", ((float)extractLong(4)) / 1000.0f); + printk("\n"); + printk("LON: %f", ((float)(int32_t)extractLong(8)) / 10000000.0f); + printk("\n"); + printk("LAT: %f", ((float)(int32_t)extractLong(12)) / 10000000.0f); + printk("\n"); + printk("ELI M: %f", ((float)(int32_t)extractLong(16)) / 1000.0f); + printk("\n"); + printk("MSL M: %f", ((float)(int32_t)extractLong(20)) / 1000.0f); + printk("\n"); + printk("LON HP: %c", extractSignedChar(24)); + printk("\n"); + printk("LAT HP: %c", extractSignedChar(25)); + printk("\n"); + printk("ELI HP: %calcChecksum", extractSignedChar(26)); + printk("\n"); + printk("MSL HP: %c", extractSignedChar(27)); + printk("\n"); + printk("HA 2D M: %f", ((float)(int32_t)extractLong(28)) / 10000.0f); + printk("\n"); + printk("VERT M: %f", ((float)(int32_t)extractLong(32)) / 10000.0f); + } + } + break; + } +} + +//Given a packet and payload, send everything including CRC bytes via I2C port +sfe_ublox_status_e SFE_UBLOX_GPS::sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait) +{ + sfe_ublox_status_e retVal = SFE_UBLOX_STATUS_SUCCESS; + + calcChecksum(outgoingUBX); //Sets checksum A and B bytes of the packet + + if (_printDebug == true) + { + printk("\nSending: "); + printPacket(outgoingUBX); + } + + if (commType == COMM_TYPE_I2C) + { + retVal = sendI2cCommand(outgoingUBX, maxWait); + if (retVal != SFE_UBLOX_STATUS_SUCCESS) + { + if (_printDebug == true) + { + printk("Send I2C Command failed\n"); + } + return retVal; + } + } + else if (commType == COMM_TYPE_SERIAL) + { + sendSerialCommand(outgoingUBX); + } + + if (maxWait > 0) + { + //Depending on what we just sent, either we need to look for an ACK or not + if (outgoingUBX->cls == UBX_CLASS_CFG) + { + if (_printDebug == true) + { + printk("sendCommand: Waiting for ACK response\n"); + } + retVal = waitForACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + else + { + if (_printDebug == true) + { + printk("sendCommand: Waiting for No ACK response\n"); + } + retVal = waitForNoACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + } + return retVal; +} + +//Returns false if sensor fails to respond to I2C traffic +sfe_ublox_status_e SFE_UBLOX_GPS::sendI2cCommand(ubxPacket *outgoingUBX, uint16_t maxWait) +{ + int err; + //Point at 0xFF data register + u8_t small_buffer[1]; + small_buffer[0] = 0xFF; + err = transferWriteI2C(small_buffer, 1); //There is no register to write to, we just begin writing data bytes + if (err) { + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + } + + //Write header bytes + u8_t header_buffer[6]; //There is no register to write to, we just begin writing data bytes + header_buffer[0] = UBX_SYNCH_1; //μ - oh ublox, you're funny. I will call you micro-blox from now on. + header_buffer[1] = UBX_SYNCH_2; //b + header_buffer[2] = outgoingUBX->cls; + header_buffer[3] = outgoingUBX->id; + header_buffer[4] = outgoingUBX->len & 0xFF; //LSB + header_buffer[5] = outgoingUBX->len >> 8; //MSB + + err = transferWriteI2C(header_buffer, 6, false); + k_msleep(1); // added this millisecond sleep to not progress too fast + if (err) { + 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 + uint16_t bytesToSend = outgoingUBX->len; + + //"The number of data bytes must be at least 2 to properly distinguish + //from the write access to set the address counter in random read accesses." + uint16_t startSpot = 0; + while (bytesToSend > 1) + { + uint8_t len = bytesToSend; + if (len > I2C_BUFFER_LENGTH) + len = I2C_BUFFER_LENGTH; + + u8_t data_buffer[len]; + for (uint16_t x = 0; x < len; x++) { + data_buffer[x] = outgoingUBX->payload[startSpot + x]; + } + err = transferWriteI2C(data_buffer, len, false); //Write a portion of the payload to the bus + if (err) { + return (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + } + + //*outgoingUBX->payload += len; //Move the pointer forward + startSpot += len; //Move the pointer forward + bytesToSend -= len; + } + + //Write checksum + if (bytesToSend == 1) { + u8_t checksum_buffer[3]; + uint8_t payload = *outgoingUBX->payload; + checksum_buffer[0] = payload; + checksum_buffer[1] = outgoingUBX->checksumA; + checksum_buffer[2] = outgoingUBX->checksumB; + err = transferWriteI2C(checksum_buffer, 3, true); + } + else { + u8_t checksum_buffer[2]; + checksum_buffer[0] = outgoingUBX->checksumA; + checksum_buffer[1] = outgoingUBX->checksumB; + err = transferWriteI2C(checksum_buffer, 2, true); + } + if (err) { + 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 +void SFE_UBLOX_GPS::sendSerialCommand(ubxPacket *outgoingUBX) +{ + printk("sendSerialCommand function not ported...\n"); + /* + //Write header bytes + _serialPort->write(UBX_SYNCH_1); //μ - oh ublox, you're funny. I will call you micro-blox from now on. + _serialPort->write(UBX_SYNCH_2); //b + _serialPort->write(outgoingUBX->cls); + _serialPort->write(outgoingUBX->id); + _serialPort->write(outgoingUBX->len & 0xFF); //LSB + _serialPort->write(outgoingUBX->len >> 8); //MSB + + //Write payload. + for (int i = 0; i < outgoingUBX->len; i++) + { + _serialPort->write(outgoingUBX->payload[i]); + } + + //Write checksum + _serialPort->write(outgoingUBX->checksumA); + _serialPort->write(outgoingUBX->checksumB); + */ +} + +//Returns true if I2C device ack's +bool SFE_UBLOX_GPS::isConnected(uint16_t maxWait) +{ + if (commType == COMM_TYPE_I2C) + { + u8_t buff[1]; + buff[0] = 0x00; + int err = transferWriteI2C(buff, 1); + if (err) { + return false; //Sensor did not ack + } + } + + // Query navigation rate to see whether we get a meaningful response + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are polling the RATE so we expect data and an ACK + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // data send is returned, not data received - TODO library bug? +} + +//Given a message, calc and store the two byte "8-Bit Fletcher" checksum over the entirety of the message +//This is called before we send a command message +void SFE_UBLOX_GPS::calcChecksum(ubxPacket *msg) +{ + msg->checksumA = 0; + msg->checksumB = 0; + + msg->checksumA += msg->cls; + msg->checksumB += msg->checksumA; + + msg->checksumA += msg->id; + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len & 0xFF); + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len >> 8); + msg->checksumB += msg->checksumA; + + for (uint16_t i = 0; i < msg->len; i++) + { + msg->checksumA += msg->payload[i]; + msg->checksumB += msg->checksumA; + } +} + +//Given a message and a byte, add to rolling "8-Bit Fletcher" checksum +//This is used when receiving messages from module +void SFE_UBLOX_GPS::addToChecksum(uint8_t incoming) +{ + rollingChecksumA += incoming; + rollingChecksumB += rollingChecksumA; +} + +//Pretty prints the current ubxPacket +void SFE_UBLOX_GPS::printPacket(ubxPacket *packet) +{ + if (_printDebug == true) + { + printk("CLS:"); + if (packet->cls == UBX_CLASS_NAV) //1 + printk("NAV"); + else if (packet->cls == UBX_CLASS_ACK) //5 + printk("ACK"); + else if (packet->cls == UBX_CLASS_CFG) //6 + printk("CFG"); + else if (packet->cls == UBX_CLASS_MON) //0x0A + printk("MON"); + else + { + printk("0x%x", packet->cls); + } + + printk(" ID:"); + if (packet->cls == UBX_CLASS_NAV && packet->id == UBX_NAV_PVT) + printk("PVT"); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_RATE) + printk("RATE"); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_CFG) + printk("SAVE"); + else + { + printk("0x%x", packet->id); + } + + printk(" Len: 0x%x", packet->len); + + // Only print the payload is ignoreThisPayload is false otherwise + // we could be printing gibberish from beyond the end of packetBuf + if (ignoreThisPayload == false) + { + printk(" Payload:"); + + for (int x = 0; x < packet->len; x++) + { + printk(" %x", packet->payload[x]); + } + } + else + { + printk(" Payload: IGNORED"); + } + printk("\n"); + } +} + +//=-=-=-=-=-=-=-= Specific commands =-=-=-=-=-=-=-==-=-=-=-=-=-=-= +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//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. + +//When we poll or get a setting, we will receive _both_ a config packet and an ACK +//If the poll or get request is not valid, we will receive _only_ a NACK + +//If we are trying to get or poll a setting, then packetCfg.len will be 0 or 1 when the packetCfg is _sent_. +//If we poll the setting for a particular port using UBX-CFG-PRT then .len will be 1 initially +//For all other gets or polls, .len will be 0 initially +//(It would be possible for .len to be 2 _if_ we were using UBX-CFG-MSG to poll the settings for a particular message - but we don't use that (currently)) + +//If the get or poll _fails_, i.e. is NACK'd, then packetCfg.len could still be 0 or 1 after the NACK is received +//But if the get or poll is ACK'd, then packetCfg.len will have been updated by the incoming data and will always be at least 2 + +//If we are going to set the value for a setting, then packetCfg.len will be at least 3 when the packetCfg is _sent_. +//(UBX-CFG-MSG appears to have the shortest set length of 3 bytes) + +//We need to think carefully about how interleaved PVT packets affect things. +//It is entirely possible that our packetCfg and packetAck were received successfully +//but while we are still in the "if (checkUblox() == true)" loop a PVT packet is processed +//or _starts_ to arrive (remember that Serial data can arrive very slowly). + +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got an ACK and a valid packetCfg (module is responding with register content) +//Returns SFE_UBLOX_STATUS_DATA_SENT if we got an ACK and no packetCfg (no valid packetCfg needed, module absorbs new register data) +//Returns SFE_UBLOX_STATUS_FAIL if something very bad happens (e.g. a double checksum failure) +//Returns SFE_UBLOX_STATUS_COMMAND_NACK if the packet was not-acknowledged (NACK) +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we had a checksum failure +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an ACK and a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = k_uptime_get_32(); + while (k_uptime_get_32() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: valid data and valid ACK received after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and a correct ACK! + } + + // We can be confident that the data packet (if we are going to get one) will always arrive + // before the matching ACK. So if we sent a config packet which only produces an ACK + // then outgoingUBX->classAndIDmatch will be NOT_DEFINED and the packetAck.classAndIDmatch will VALID. + // We should not check outgoingUBX->valid, outgoingUBX->cls or outgoingUBX->id + // as these may have been changed by a PVT packet. + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: no data and valid ACK after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_SENT); //We got an ACK but no data... + } + + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by another packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && !((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + printk("waitForACKResponse: data being OVERWRITTEN after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If packetAck.classAndIDmatch is VALID but both outgoingUBX->valid and outgoingUBX->classAndIDmatch + // are NOT_VALID then we can be confident we have had a checksum failure on the data packet + else if ((packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: CRC failed after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //Checksum fail + } + + // If our packet was not-acknowledged (NACK) we do not receive a data packet - we only get the NACK. + // So you would expect outgoingUBX->valid and outgoingUBX->classAndIDmatch to still be NOT_DEFINED + // But if a full PVT packet arrives afterwards outgoingUBX->valid could be VALID (or just possibly NOT_VALID) + // but outgoingUBX->cls and outgoingUBX->id would not match... + // So I think this is telling us we need a special state for packetAck.classAndIDmatch to tell us + // the packet was definitely NACK'd otherwise we are possibly just guessing... + // Note: the addition of packetBuf changes the logic of this, but we'll leave the code as is for now. + else if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_NOTACKNOWLEDGED) + { + if (_printDebug == true) + { + printk("waitForACKResponse: data was NOTACKNOWLEDGED (NACK) after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_COMMAND_NACK); //We received a NACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID but the packetAck.classAndIDmatch is NOT_VALID + // then the ack probably had a checksum error. We will take a gamble and return DATA_RECEIVED. + // If we were playing safe, we should return FAIL instead + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: VALID data and INVALID ACK received after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID and the packetAck.classAndIDmatch is NOT_VALID + // then we return a FAIL. This must be a double checksum failure? + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: INVALID data and INVALID ACK received after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_FAIL); //We received invalid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID and the packetAck.classAndIDmatch is NOT_DEFINED + // then the ACK has not yet been received and we should keep waiting for it + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: valid data after %lu msec. Waiting for ACK\n", k_uptime_get_32() - startTime); + } + } + + } //checkUbloxInternal == true + + k_usleep(500); + } //while (k_uptime_get_32() - startTime < maxTime) + + // We have timed out... + // If the outgoingUBX->classAndIDmatch is VALID then we can take a gamble and return DATA_RECEIVED + // even though we did not get an ACK + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + printk("waitForACKResponse: TIMEOUT with valid data after %lu msec.\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data... But no ACK! + } + + if (_printDebug == true) + { + printk("waitForACKResponse: TIMEOUT after %lu msec.\n", k_uptime_get_32() - startTime); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +//For non-CFG queries no ACK is sent so we use this function +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got a config packet full of response data that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we got a corrupt config packet that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = k_uptime_get_32(); + while (k_uptime_get_32() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + + // If outgoingUBX->classAndIDmatch is VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + printk("waitForNoACKResponse: valid data with CLS/ID match after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data! + } + + // If the outgoingUBX->classAndIDmatch is VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by another packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && !((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + printk("waitForNoACKResponse: data being OVERWRITTEN after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If outgoingUBX->classAndIDmatch is NOT_DEFINED + // and outgoingUBX->valid is VALID then this must be (e.g.) a PVT packet + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + if (_printDebug == true) + { + printk("waitForNoACKResponse: valid but UNWANTED data after %lu msec. Class: %d ID: %d\n", k_uptime_get_32() - startTime, outgoingUBX->cls, outgoingUBX->id); + } + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID then we return CRC failure + else if (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) + { + if (_printDebug == true) + { + printk("waitForNoACKResponse: CLS/ID match but failed CRC after %lu msec\n", k_uptime_get_32() - startTime); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //We received invalid data + } + } + + k_usleep(500); + } + + if (_printDebug == true) + { + printk("waitForNoACKResponse: TIMEOUT after %lu msec. No packet received.\n", k_uptime_get_32() - startTime); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +//Save current configuration to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +bool SFE_UBLOX_GPS::saveConfiguration(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[4] = 0xFF; //Set any bit in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Save the selected configuration sub-sections to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +bool SFE_UBLOX_GPS::saveConfigSelective(uint32_t configMask, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[4] = configMask & 0xFF; //Set the appropriate bits in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = (configMask >> 8) & 0xFF; + packetCfg.payload[6] = (configMask >> 16) & 0xFF; + packetCfg.payload[7] = (configMask >> 24) & 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Reset module to factory defaults +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +bool SFE_UBLOX_GPS::factoryDefault(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + packetCfg.payload[0] = 0xFF; //Set any bit in the clearMask field to clear saved config + packetCfg.payload[1] = 0xFF; + packetCfg.payload[8] = 0xFF; //Set any bit in the loadMask field to discard current config and rebuild from lower non-volatile memory layers + packetCfg.payload[9] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a group, ID and size, return the value of this config spot +//The 32-bit key is put together from group/ID/size. See other getVal to send key directly. +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer, uint16_t maxWait) +{ + //Create key + uint32_t key = 0; + key |= (uint32_t)id; + key |= (uint32_t)group << 16; + key |= (uint32_t)size << 28; + + if (_printDebug == true) + { + printk("key: 0x%x\n", key); + } + + return getVal8(key, layer, maxWait); +} + +//Given a key, return its value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALGET; + packetCfg.len = 4 + 4 * 1; //While multiple keys are allowed, we will send only one key at a time + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + //VALGET uses different memory layer definitions to VALSET + //because it can only return the value for one layer. + //So we need to fiddle the layer here. + //And just to complicate things further, the ZED-F9P only responds + //correctly to layer 0 (RAM) and layer 7 (Default)! + uint8_t getLayer = 7; // 7 is the "Default Layer" + if ((layer & VAL_LAYER_RAM) == VAL_LAYER_RAM) // Did the user request the RAM layer? + { + getLayer = 0; // Layer 0 is RAM + } + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = getLayer; //Layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + if (_printDebug == true) + { + printk("key: 0x%x\n", key); + } + + //Send VALGET command with this key + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + printk("getVal8: sendCommand returned: %s\n", statusString(retVal)); + } + if (retVal != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (0); //If command send fails then bail + + //Verify the response is the correct length as compared to what the user called (did the module respond with 8-bits but the user called getVal32?) + //Response is 8 bytes plus cfg data + //if(packet->len > 8+1) + + //Pull the requested value from the response + //Response starts at 4+1*N with the 32-bit key so the actual data we're looking for is at 8+1*N + return (extractByte(8)); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + return setVal16(key, value, layer, maxWait); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal16(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set an 8-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal8(uint32_t key, uint8_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set a 32-bit value +//This function takes a full 32-bit key +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal32(uint32_t key, uint32_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset32(uint32_t key, uint32_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset16(uint32_t key, uint16_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset8(uint32_t key, uint8_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint16_t x = 0; x < MAX_PAYLOAD_SIZE; x++) + packetCfg.payload[x] = 0; + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset32(uint32_t key, uint32_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + payloadCfg[packetCfg.len + 6] = value >> 8 * 2; + payloadCfg[packetCfg.len + 7] = value >> 8 * 3; + + //Update packet length: 4 byte key ID, 4 bytes of value + packetCfg.len = packetCfg.len + 4 + 4; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset16(uint32_t key, uint16_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + + //Update packet length: 4 byte key ID, 2 bytes of value + packetCfg.len = packetCfg.len + 4 + 2; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset8(uint32_t key, uint8_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value; //Value + + //Update packet length: 4 byte key ID, 1 byte value + packetCfg.len = packetCfg.len + 4 + 1; + + //All done + return (true); +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset32(uint32_t key, uint32_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset32(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset16(uint32_t key, uint16_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset16(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset8(uint32_t key, uint8_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset8(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the current TimeMode3 settings - these contain survey in statuses +bool SFE_UBLOX_GPS::getSurveyMode(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Control Survey-In for NEO-M8P +bool SFE_UBLOX_GPS::setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + if (getSurveyMode(maxWait) == false) //Ask module for the current TimeMode3 settings. Loads into payloadCfg. + return (false); + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 40; + packetCfg.startingSpot = 0; + + //Clear packet payload + for (uint8_t x = 0; x < packetCfg.len; x++) + packetCfg.payload[x] = 0; + + //payloadCfg should be loaded with poll response. Now modify only the bits we care about + payloadCfg[2] = mode; //Set mode. Survey-In and Disabled are most common. Use ECEF (not LAT/LON/ALT). + + //svinMinDur is U4 (uint32_t) but we'll only use a uint16_t (waiting more than 65535 seconds seems excessive!) + payloadCfg[24] = observationTime & 0xFF; //svinMinDur in seconds + payloadCfg[25] = observationTime >> 8; //svinMinDur in seconds + payloadCfg[26] = 0; //Truncate to 16 bits + payloadCfg[27] = 0; //Truncate to 16 bits + + //svinAccLimit is U4 (uint32_t) in 0.1mm. + uint32_t svinAccLimit = (uint32_t)(requiredAccuracy * 10000.0); //Convert m to 0.1mm + payloadCfg[28] = svinAccLimit & 0xFF; //svinAccLimit in 0.1mm increments + payloadCfg[29] = svinAccLimit >> 8; + payloadCfg[30] = svinAccLimit >> 16; + payloadCfg[31] = svinAccLimit >> 24; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Begin Survey-In for NEO-M8P +bool SFE_UBLOX_GPS::enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_ENABLE, observationTime, requiredAccuracy, maxWait)); +} + +//Stop Survey-In for NEO-M8P +bool SFE_UBLOX_GPS::disableSurveyMode(uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_DISABLE, 0, 0, maxWait)); +} + +//Reads survey in status and sets the global variables +//for status, position valid, observation time, and mean 3D StdDev +//Returns true if commands was successful +bool SFE_UBLOX_GPS::getSurveyStatus(uint16_t maxWait) +{ + //Reset variables + svin.active = false; + svin.valid = false; + svin.observationTime = 0; + svin.meanAccuracy = 0; + + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_SVIN; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if ((sendCommand(&packetCfg, maxWait)) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //We got a response, now parse the bits into the svin structure + + //dur (Passed survey-in observation time) is U4 (uint32_t) seconds. We truncate to 16 bits + //(waiting more than 65535 seconds (18.2 hours) seems excessive!) + uint32_t tmpObsTime = extractLong(8); + if (tmpObsTime <= 0xFFFF) + { + svin.observationTime = (uint16_t)tmpObsTime; + } + else + { + svin.observationTime = 0xFFFF; + } + + // meanAcc is U4 (uint32_t) in 0.1mm. We convert this to float. + uint32_t tempFloat = extractLong(28); + svin.meanAccuracy = ((float)tempFloat) / 10000.0; //Convert 0.1mm to m + + svin.valid = payloadCfg[36]; //1 if survey-in position is valid, 0 otherwise + svin.active = payloadCfg[37]; //1 if survey-in in progress, 0 otherwise + + return (true); +} + +//Loads the payloadCfg array with the current protocol bits located the UBX-CFG-PRT register for a given port +bool SFE_UBLOX_GPS::getPortSettings(uint8_t portID, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 1; + packetCfg.startingSpot = 0; + + payloadCfg[0] = portID; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +bool SFE_UBLOX_GPS::setPortOutput(uint8_t portID, uint8_t outStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[14] = outStreamSettings; //OutProtocolMask LSB - Set outStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +bool SFE_UBLOX_GPS::setPortInput(uint8_t portID, uint8_t inStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + //This will load the payloadCfg array with current port settings + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[12] = inStreamSettings; //InProtocolMask LSB - Set inStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a port to output UBX, NMEA, RTCM3 or a combination thereof +bool SFE_UBLOX_GPS::setI2COutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_I2C, comSettings, maxWait)); +} +bool SFE_UBLOX_GPS::setUART1Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART1, comSettings, maxWait)); +} +bool SFE_UBLOX_GPS::setUART2Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART2, comSettings, maxWait)); +} +bool SFE_UBLOX_GPS::setUSBOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_USB, comSettings, maxWait)); +} +bool SFE_UBLOX_GPS::setSPIOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_SPI, comSettings, maxWait)); +} + +//Set the rate at which the module will give us an updated navigation solution +//Expects a number that is the updates per second. For example 1 = 1Hz, 2 = 2Hz, etc. +//Max is 40Hz(?!) +bool SFE_UBLOX_GPS::setNavigationFrequency(uint8_t navFreq, uint16_t maxWait) +{ + //if(updateRate > 40) updateRate = 40; //Not needed: module will correct out of bounds values + + //Adjust the I2C polling timeout based on update rate + i2cPollingWait = 1000 / (navFreq * 4); //This is the number of ms to wait between checks for new I2C data + + //Query the module for the latest lat/long + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + uint16_t measurementRate = 1000 / navFreq; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[0] = measurementRate & 0xFF; //measRate LSB + payloadCfg[1] = measurementRate >> 8; //measRate MSB + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the rate at which the module is outputting nav solutions +uint8_t SFE_UBLOX_GPS::getNavigationFrequency(uint16_t maxWait) +{ + //Query the module for the latest lat/long + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + uint16_t measurementRate = 0; + + //payloadCfg is now loaded with current bytes. Get what we need + measurementRate = extractInt(0); //Pull from payloadCfg at measRate LSB + + measurementRate = 1000 / measurementRate; //This may return an int when it's a float, but I'd rather not return 4 bytes + return (measurementRate); +} + +//In case no config access to the GPS is possible and PVT is send cyclically already +//set config to suitable parameters +bool SFE_UBLOX_GPS::assumeAutoPVT(bool enabled, bool implicitUpdate) +{ + bool changes = autoPVT != enabled || autoPVTImplicitUpdate != implicitUpdate; + if (changes) + { + autoPVT = enabled; + autoPVTImplicitUpdate = implicitUpdate; + } + return changes; +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getPVT +//works. +bool SFE_UBLOX_GPS::setAutoPVT(bool enable, uint16_t maxWait) +{ + return setAutoPVT(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GPS. This changes the way getPVT +//works. +bool SFE_UBLOX_GPS::setAutoPVT(bool enable, bool implicitUpdate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_PVT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + bool ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + autoPVT = enable; + autoPVTImplicitUpdate = implicitUpdate; + } + moduleQueried.all = false; + return ok; +} + +//Configure a given message type for a given port (UART1, I2C, SPI, etc) +bool SFE_UBLOX_GPS::configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + //Poll for the current settings for a given message + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + payloadCfg[0] = msgClass; + payloadCfg[1] = msgID; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //Now send it back with new mods + packetCfg.len = 8; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[2 + portID] = sendRate; //Send rate is relative to the event a message is registered on. For example, if the rate of a navigation message is set to 2, the message is sent every 2nd navigation solution. + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Enable a given message type, default of 1 per update rate (usually 1 per second) +bool SFE_UBLOX_GPS::enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, rate, maxWait)); +} +//Disable a given message type on a given port +bool SFE_UBLOX_GPS::disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, 0, maxWait)); +} + +bool SFE_UBLOX_GPS::enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(UBX_CLASS_NMEA, msgID, portID, rate, maxWait)); +} +bool SFE_UBLOX_GPS::disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (enableNMEAMessage(msgID, portID, 0, maxWait)); +} + +//Given a message number turns on a message ID for output over a given portID (UART, I2C, SPI, USB, etc) +//To disable a message, set secondsBetween messages to 0 +//Note: This function will return false if the message is already enabled +//For base station RTK output we need to enable various sentences + +//NEO-M8P has four: +//1005 = 0xF5 0x05 - Stationary RTK reference ARP +//1077 = 0xF5 0x4D - GPS MSM7 +//1087 = 0xF5 0x57 - GLONASS MSM7 +//1230 = 0xF5 0xE6 - GLONASS code-phase biases, set to once every 10 seconds + +//ZED-F9P has six: +//1005, 1074, 1084, 1094, 1124, 1230 + +//Much of this configuration is not documented and instead discerned from u-center binary console +bool SFE_UBLOX_GPS::enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + return (configureMessage(UBX_RTCM_MSB, messageNumber, portID, sendRate, maxWait)); +} + +//Disable a given message on a given port by setting secondsBetweenMessages to zero +bool SFE_UBLOX_GPS::disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait) +{ + return (enableRTCMmessage(messageNumber, portID, 0, maxWait)); +} + +//Add a new geofence using UBX-CFG-GEOFENCE +bool SFE_UBLOX_GPS::addGeofence(int32_t latitude, int32_t longitude, uint32_t radius, uint8_t confidence, uint8_t pinPolarity, uint8_t pin, uint16_t maxWait) +{ + if (currentGeofenceParams.numFences >= 4) + return (false); // Quit if we already have four geofences defined + + // Store the new geofence parameters + currentGeofenceParams.lats[currentGeofenceParams.numFences] = latitude; + currentGeofenceParams.longs[currentGeofenceParams.numFences] = longitude; + currentGeofenceParams.rads[currentGeofenceParams.numFences] = radius; + currentGeofenceParams.numFences = currentGeofenceParams.numFences + 1; // Increment the number of fences + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = (currentGeofenceParams.numFences * 12) + 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = currentGeofenceParams.numFences; // numFences + payloadCfg[2] = confidence; // confLvl = Confidence level 0-4 (none, 68%, 95%, 99.7%, 99.99%) + payloadCfg[3] = 0; // reserved1 + if (pin > 0) + { + payloadCfg[4] = 1; // enable PIO combined fence state + } + else + { + payloadCfg[4] = 0; // disable PIO combined fence state + } + payloadCfg[5] = pinPolarity; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = pin; // PIO pin + payloadCfg[7] = 0; //reserved2 + payloadCfg[8] = currentGeofenceParams.lats[0] & 0xFF; + payloadCfg[9] = currentGeofenceParams.lats[0] >> 8; + payloadCfg[10] = currentGeofenceParams.lats[0] >> 16; + payloadCfg[11] = currentGeofenceParams.lats[0] >> 24; + payloadCfg[12] = currentGeofenceParams.longs[0] & 0xFF; + payloadCfg[13] = currentGeofenceParams.longs[0] >> 8; + payloadCfg[14] = currentGeofenceParams.longs[0] >> 16; + payloadCfg[15] = currentGeofenceParams.longs[0] >> 24; + payloadCfg[16] = currentGeofenceParams.rads[0] & 0xFF; + payloadCfg[17] = currentGeofenceParams.rads[0] >> 8; + payloadCfg[18] = currentGeofenceParams.rads[0] >> 16; + payloadCfg[19] = currentGeofenceParams.rads[0] >> 24; + if (currentGeofenceParams.numFences >= 2) + { + payloadCfg[20] = currentGeofenceParams.lats[1] & 0xFF; + payloadCfg[21] = currentGeofenceParams.lats[1] >> 8; + payloadCfg[22] = currentGeofenceParams.lats[1] >> 16; + payloadCfg[23] = currentGeofenceParams.lats[1] >> 24; + payloadCfg[24] = currentGeofenceParams.longs[1] & 0xFF; + payloadCfg[25] = currentGeofenceParams.longs[1] >> 8; + payloadCfg[26] = currentGeofenceParams.longs[1] >> 16; + payloadCfg[27] = currentGeofenceParams.longs[1] >> 24; + payloadCfg[28] = currentGeofenceParams.rads[1] & 0xFF; + payloadCfg[29] = currentGeofenceParams.rads[1] >> 8; + payloadCfg[30] = currentGeofenceParams.rads[1] >> 16; + payloadCfg[31] = currentGeofenceParams.rads[1] >> 24; + } + if (currentGeofenceParams.numFences >= 3) + { + payloadCfg[32] = currentGeofenceParams.lats[2] & 0xFF; + payloadCfg[33] = currentGeofenceParams.lats[2] >> 8; + payloadCfg[34] = currentGeofenceParams.lats[2] >> 16; + payloadCfg[35] = currentGeofenceParams.lats[2] >> 24; + payloadCfg[36] = currentGeofenceParams.longs[2] & 0xFF; + payloadCfg[37] = currentGeofenceParams.longs[2] >> 8; + payloadCfg[38] = currentGeofenceParams.longs[2] >> 16; + payloadCfg[39] = currentGeofenceParams.longs[2] >> 24; + payloadCfg[40] = currentGeofenceParams.rads[2] & 0xFF; + payloadCfg[41] = currentGeofenceParams.rads[2] >> 8; + payloadCfg[42] = currentGeofenceParams.rads[2] >> 16; + payloadCfg[43] = currentGeofenceParams.rads[2] >> 24; + } + if (currentGeofenceParams.numFences >= 4) + { + payloadCfg[44] = currentGeofenceParams.lats[3] & 0xFF; + payloadCfg[45] = currentGeofenceParams.lats[3] >> 8; + payloadCfg[46] = currentGeofenceParams.lats[3] >> 16; + payloadCfg[47] = currentGeofenceParams.lats[3] >> 24; + payloadCfg[48] = currentGeofenceParams.longs[3] & 0xFF; + payloadCfg[49] = currentGeofenceParams.longs[3] >> 8; + payloadCfg[50] = currentGeofenceParams.longs[3] >> 16; + payloadCfg[51] = currentGeofenceParams.longs[3] >> 24; + payloadCfg[52] = currentGeofenceParams.rads[3] & 0xFF; + payloadCfg[53] = currentGeofenceParams.rads[3] >> 8; + payloadCfg[54] = currentGeofenceParams.rads[3] >> 16; + payloadCfg[55] = currentGeofenceParams.rads[3] >> 24; + } + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear all geofences using UBX-CFG-GEOFENCE +bool SFE_UBLOX_GPS::clearGeofences(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = 0; // numFences + payloadCfg[2] = 0; // confLvl + payloadCfg[3] = 0; // reserved1 + payloadCfg[4] = 0; // disable PIO combined fence state + payloadCfg[5] = 0; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = 0; // PIO pin + payloadCfg[7] = 0; //reserved2 + + currentGeofenceParams.numFences = 0; // Zero the number of geofences currently in use + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear the antenna control settings using UBX-CFG-ANT +//This function is hopefully redundant but may be needed to release +//any PIO pins pre-allocated for antenna functions +bool SFE_UBLOX_GPS::clearAntPIO(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_ANT; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0x10; // Antenna flag mask: set the recovery bit + payloadCfg[1] = 0; + payloadCfg[2] = 0xFF; // Antenna pin configuration: set pinSwitch and pinSCD to 31 + payloadCfg[3] = 0xFF; // Antenna pin configuration: set pinOCD to 31, set reconfig bit + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Returns the combined geofence state using UBX-NAV-GEOFENCE +bool SFE_UBLOX_GPS::getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_GEOFENCE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the geofence status. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + currentGeofenceState.status = payloadCfg[5]; // Extract the status + currentGeofenceState.numFences = payloadCfg[6]; // Extract the number of geofences + currentGeofenceState.combState = payloadCfg[7]; // Extract the combined state of all geofences + if (currentGeofenceState.numFences > 0) + currentGeofenceState.states[0] = payloadCfg[8]; // Extract geofence 1 state + if (currentGeofenceState.numFences > 1) + currentGeofenceState.states[1] = payloadCfg[10]; // Extract geofence 2 state + if (currentGeofenceState.numFences > 2) + currentGeofenceState.states[2] = payloadCfg[12]; // Extract geofence 3 state + if (currentGeofenceState.numFences > 3) + currentGeofenceState.states[3] = payloadCfg[14]; // Extract geofence 4 state + + return (true); +} + +//Power Save Mode +//Enables/Disables Low Power Mode using UBX-CFG-RXM +bool SFE_UBLOX_GPS::powerSaveMode(bool power_save, uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + printk"(Protocol version is %d\n", protVer); + } + */ + if (protVer >= 27) + { + if (_printDebug == true) + { + printk("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version\n"); + } + return (false); + } + + // Now let's change the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + if (power_save) + { + payloadCfg[1] = 1; // Power Save Mode + } + else + { + payloadCfg[1] = 0; // Continuous Mode + } + + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +// Get Power Save Mode +// Returns the current Low Power Mode using UBX-CFG-RXM +// Returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getPowerSaveMode(uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + printk("Protocol version is %d\n", protVer); + } + */ + if (protVer >= 27) + { + if (_printDebug == true) + { + printk("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version\n"); + } + return (255); + } + + // Now let's read the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + return (payloadCfg[1]); // Return the low power mode +} + +// Powers off the GPS device for a given duration to reduce power consumption. +// NOTE: Querying the device before the duration is complete, for example by "getLatitude()" will wake it up! +// Returns true if command has not been not acknowledged. +// Returns false if command has not been acknowledged or maxWait = 0. +bool SFE_UBLOX_GPS::powerOff(uint32_t durationInMs, uint16_t maxWait) +{ + // use durationInMs = 0 for infinite duration + if (_printDebug == true) + { + printk("Powering off for %d ms\n", durationInMs); + } + + // Power off device using UBX-RXM-PMREQ + packetCfg.cls = UBX_CLASS_RXM; // 0x02 + packetCfg.id = UBX_RXM_PMREQ; // 0x41 + packetCfg.len = 8; + packetCfg.startingSpot = 0; + + // duration + // big endian to little endian, switch byte order + payloadCfg[0] = (durationInMs >> (8 * 0)) & 0xff; + payloadCfg[1] = (durationInMs >> (8 * 1)) & 0xff; + payloadCfg[2] = (durationInMs >> (8 * 2)) & 0xff; + payloadCfg[3] = (durationInMs >> (8 * 3)) & 0xff; + + payloadCfg[4] = 0x02; //Flags : set the backup bit + payloadCfg[5] = 0x00; //Flags + payloadCfg[6] = 0x00; //Flags + payloadCfg[7] = 0x00; //Flags + + if (maxWait != 0) + { + // check for "not acknowledged" command + return (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_COMMAND_NACK); + } + else + { + sendCommand(&packetCfg, maxWait); + return false; // can't tell if command not acknowledged if maxWait = 0 + } +} + +// Powers off the GPS device for a given duration to reduce power consumption. +// While powered off it can be woken up by creating a falling or rising voltage edge on the specified pin. +// NOTE: The GPS seems to be sensitve to signals on the pins while powered off. Works best when Microcontroller is in deepsleep. +// NOTE: Querying the device before the duration is complete, for example by "getLatitude()" will wake it up! +// Returns true if command has not been not acknowledged. +// Returns false if command has not been acknowledged or maxWait = 0. +bool SFE_UBLOX_GPS::powerOffWithInterrupt(uint32_t durationInMs, uint32_t wakeupSources, bool forceWhileUsb, uint16_t maxWait) +{ + // use durationInMs = 0 for infinite duration + if (_printDebug == true) + { + printk("Powering off for %d ms\n", durationInMs); + } + + // Power off device using UBX-RXM-PMREQ + packetCfg.cls = UBX_CLASS_RXM; // 0x02 + packetCfg.id = UBX_RXM_PMREQ; // 0x41 + packetCfg.len = 16; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0x00; // message version + + // bytes 1-3 are reserved - and must be set to zero + payloadCfg[1] = 0x00; + payloadCfg[2] = 0x00; + payloadCfg[3] = 0x00; + + // duration + // big endian to little endian, switch byte order + payloadCfg[4] = (durationInMs >> (8 * 0)) & 0xff; + payloadCfg[5] = (durationInMs >> (8 * 1)) & 0xff; + payloadCfg[6] = (durationInMs >> (8 * 2)) & 0xff; + payloadCfg[7] = (durationInMs >> (8 * 3)) & 0xff; + + // flags + + // disables USB interface when powering off, defaults to true + if (forceWhileUsb) + { + payloadCfg[8] = 0x06; // force | backup + } + else + { + payloadCfg[8] = 0x02; // backup only (leave the force bit clear - module will stay on if USB is connected) + } + + payloadCfg[9] = 0x00; + payloadCfg[10] = 0x00; + payloadCfg[11] = 0x00; + + // wakeUpSources + + // wakeupPin mapping, defaults to VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 + + // Possible values are: + // VAL_RXM_PMREQ_WAKEUPSOURCE_UARTRX + // VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 + // VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT1 + // VAL_RXM_PMREQ_WAKEUPSOURCE_SPICS + + payloadCfg[12] = (wakeupSources >> (8 * 0)) & 0xff; + payloadCfg[13] = (wakeupSources >> (8 * 1)) & 0xff; + payloadCfg[14] = (wakeupSources >> (8 * 2)) & 0xff; + payloadCfg[15] = (wakeupSources >> (8 * 3)) & 0xff; + + if (maxWait != 0) + { + // check for "not acknowledged" command + return (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_COMMAND_NACK); + } + else + { + sendCommand(&packetCfg, maxWait); + return false; // can't tell if command not acknowledged if maxWait = 0 + } +} + +//Change the dynamic platform model using UBX-CFG-NAV5 +//Possible values are: +//PORTABLE,STATIONARY,PEDESTRIAN,AUTOMOTIVE,SEA, +//AIRBORNE1g,AIRBORNE2g,AIRBORNE4g,WRIST,BIKE +//WRIST is not supported in protocol versions less than 18 +//BIKE is supported in protocol versions 19.2 +bool SFE_UBLOX_GPS::setDynamicModel(dynModel newDynamicModel, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + payloadCfg[0] = 0x01; // mask: set only the dyn bit (0) + payloadCfg[1] = 0x00; // mask + payloadCfg[2] = newDynamicModel; // dynModel + + packetCfg.len = 36; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the dynamic platform model using UBX-CFG-NAV5 +//Returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getDynamicModel(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + return (payloadCfg[2]); // Return the dynamic model +} + +//Given a spot in the payload array, extract four bytes and build a long +uint32_t SFE_UBLOX_GPS::extractLong(uint8_t spotToStart) +{ + uint32_t val = 0; + val |= (uint32_t)payloadCfg[spotToStart + 0] << 8 * 0; + val |= (uint32_t)payloadCfg[spotToStart + 1] << 8 * 1; + val |= (uint32_t)payloadCfg[spotToStart + 2] << 8 * 2; + val |= (uint32_t)payloadCfg[spotToStart + 3] << 8 * 3; + return (val); +} + +//Given a spot in the payload array, extract two bytes and build an int +uint16_t SFE_UBLOX_GPS::extractInt(uint8_t spotToStart) +{ + uint16_t val = 0; + val |= (uint16_t)payloadCfg[spotToStart + 0] << 8 * 0; + val |= (uint16_t)payloadCfg[spotToStart + 1] << 8 * 1; + return (val); +} + +//Given a spot, extract a byte from the payload +uint8_t SFE_UBLOX_GPS::extractByte(uint8_t spotToStart) +{ + return (payloadCfg[spotToStart]); +} + +//Given a spot, extract a signed 8-bit value from the payload +int8_t SFE_UBLOX_GPS::extractSignedChar(uint8_t spotToStart) +{ + return ((int8_t)payloadCfg[spotToStart]); +} + +//Get the current year +uint16_t SFE_UBLOX_GPS::getYear(uint16_t maxWait) +{ + if (moduleQueried.gpsYear == false) + getPVT(maxWait); + moduleQueried.gpsYear = false; //Since we are about to give this to user, mark this data as stale + return (gpsYear); +} + +//Get the current month +uint8_t SFE_UBLOX_GPS::getMonth(uint16_t maxWait) +{ + if (moduleQueried.gpsMonth == false) + getPVT(maxWait); + moduleQueried.gpsMonth = false; //Since we are about to give this to user, mark this data as stale + return (gpsMonth); +} + +//Get the current day +uint8_t SFE_UBLOX_GPS::getDay(uint16_t maxWait) +{ + if (moduleQueried.gpsDay == false) + getPVT(maxWait); + moduleQueried.gpsDay = false; //Since we are about to give this to user, mark this data as stale + return (gpsDay); +} + +//Get the current hour +uint8_t SFE_UBLOX_GPS::getHour(uint16_t maxWait) +{ + if (moduleQueried.gpsHour == false) + getPVT(maxWait); + moduleQueried.gpsHour = false; //Since we are about to give this to user, mark this data as stale + return (gpsHour); +} + +//Get the current minute +uint8_t SFE_UBLOX_GPS::getMinute(uint16_t maxWait) +{ + if (moduleQueried.gpsMinute == false) + getPVT(maxWait); + moduleQueried.gpsMinute = false; //Since we are about to give this to user, mark this data as stale + return (gpsMinute); +} + +//Get the current second +uint8_t SFE_UBLOX_GPS::getSecond(uint16_t maxWait) +{ + if (moduleQueried.gpsSecond == false) + getPVT(maxWait); + moduleQueried.gpsSecond = false; //Since we are about to give this to user, mark this data as stale + return (gpsSecond); +} + +//Get the current date validity +bool SFE_UBLOX_GPS::getDateValid(uint16_t maxWait) +{ + if (moduleQueried.gpsDateValid == false) + getPVT(maxWait); + moduleQueried.gpsDateValid = false; //Since we are about to give this to user, mark this data as stale + return (gpsDateValid); +} + +//Get the current time validity +bool SFE_UBLOX_GPS::getTimeValid(uint16_t maxWait) +{ + if (moduleQueried.gpsTimeValid == false) + getPVT(maxWait); + moduleQueried.gpsTimeValid = false; //Since we are about to give this to user, mark this data as stale + return (gpsTimeValid); +} + +//Get the current millisecond +uint16_t SFE_UBLOX_GPS::getMillisecond(uint16_t maxWait) +{ + if (moduleQueried.gpsiTOW == false) + getPVT(maxWait); + moduleQueried.gpsiTOW = false; //Since we are about to give this to user, mark this data as stale + return (gpsMillisecond); +} + +//Get the current nanoseconds - includes milliseconds +int32_t SFE_UBLOX_GPS::getNanosecond(uint16_t maxWait) +{ + if (moduleQueried.gpsNanosecond == false) + getPVT(maxWait); + moduleQueried.gpsNanosecond = false; //Since we are about to give this to user, mark this data as stale + return (gpsNanosecond); +} + +//Get the latest Position/Velocity/Time solution and fill all global variables +bool SFE_UBLOX_GPS::getPVT(uint16_t maxWait) +{ + if (autoPVT && autoPVTImplicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + if (_printDebug == true) + { + printk("getPVT: Autoreporting\n"); + } + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_PVT); + return moduleQueried.all; + } + else if (autoPVT && !autoPVTImplicitUpdate) + { + //Someone else has to call checkUblox for us... + if (_printDebug == true) + { + printk("getPVT: Exit immediately\n"); + } + return (false); + } + else + { + if (_printDebug == true) + { + printk("getPVT: Polling\n"); + } + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_PVT; + packetCfg.len = 0; + //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 + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + + if (retVal == SFE_UBLOX_STATUS_DATA_RECEIVED) + return (true); + + if (_printDebug == true) + { + printk("getPVT retVal: %s\n", statusString(retVal)); + } + return (false); + } +} + +uint32_t SFE_UBLOX_GPS::getTimeOfWeek(uint16_t maxWait /* = 250*/) +{ + if (moduleQueried.gpsiTOW == false) + getPVT(maxWait); + moduleQueried.gpsiTOW = false; //Since we are about to give this to user, mark this data as stale + return (timeOfWeek); +} + +int32_t SFE_UBLOX_GPS::getHighResLatitude(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLatitude == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLatitude = false; //Since we are about to give this to user, mark this data as stale + return (highResLatitude); +} + +int8_t SFE_UBLOX_GPS::getHighResLatitudeHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLatitudeHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLatitudeHp = false; //Since we are about to give this to user, mark this data as stale + return (highResLatitudeHp); +} + +int32_t SFE_UBLOX_GPS::getHighResLongitude(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLongitude == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLongitude = false; //Since we are about to give this to user, mark this data as stale + return (highResLongitude); +} + +int8_t SFE_UBLOX_GPS::getHighResLongitudeHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.highResLongitudeHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.highResLongitudeHp = false; //Since we are about to give this to user, mark this data as stale + return (highResLongitudeHp); +} + +int32_t SFE_UBLOX_GPS::getElipsoid(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.elipsoid == false) + getHPPOSLLH(maxWait); + highResModuleQueried.elipsoid = false; //Since we are about to give this to user, mark this data as stale + return (elipsoid); +} + +int8_t SFE_UBLOX_GPS::getElipsoidHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.elipsoidHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.elipsoidHp = false; //Since we are about to give this to user, mark this data as stale + return (elipsoidHp); +} + +int32_t SFE_UBLOX_GPS::getMeanSeaLevel(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.meanSeaLevel == false) + getHPPOSLLH(maxWait); + highResModuleQueried.meanSeaLevel = false; //Since we are about to give this to user, mark this data as stale + return (meanSeaLevel); +} + +int8_t SFE_UBLOX_GPS::getMeanSeaLevelHp(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.meanSeaLevelHp == false) + getHPPOSLLH(maxWait); + highResModuleQueried.meanSeaLevelHp = false; //Since we are about to give this to user, mark this data as stale + return (meanSeaLevelHp); +} + +// getGeoidSeparation is currently redundant. The geoid separation seems to only be provided in NMEA GGA and GNS messages. +int32_t SFE_UBLOX_GPS::getGeoidSeparation(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.geoidSeparation == false) + getHPPOSLLH(maxWait); + highResModuleQueried.geoidSeparation = false; //Since we are about to give this to user, mark this data as stale + return (geoidSeparation); +} + +uint32_t SFE_UBLOX_GPS::getHorizontalAccuracy(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.horizontalAccuracy == false) + getHPPOSLLH(maxWait); + highResModuleQueried.horizontalAccuracy = false; //Since we are about to give this to user, mark this data as stale + return (horizontalAccuracy); +} + +uint32_t SFE_UBLOX_GPS::getVerticalAccuracy(uint16_t maxWait /* = 250*/) +{ + if (highResModuleQueried.verticalAccuracy == false) + getHPPOSLLH(maxWait); + highResModuleQueried.verticalAccuracy = false; //Since we are about to give this to user, mark this data as stale + return (verticalAccuracy); +} + +bool SFE_UBLOX_GPS::getHPPOSLLH(uint16_t maxWait) +{ + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_HPPOSLLH; + packetCfg.len = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are only expecting data (no ACK) +} + +//Get the current 3D high precision positional accuracy - a fun thing to watch +//Returns a long representing the 3D accuracy in millimeters +uint32_t SFE_UBLOX_GPS::getPositionAccuracy(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_HPPOSECEF; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (0); //If command send fails then bail + + uint32_t tempAccuracy = extractLong(24); //We got a response, now extract a long beginning at a given position + + if ((tempAccuracy % 10) >= 5) + tempAccuracy += 5; //Round fraction of mm up to next mm if .5 or above + tempAccuracy /= 10; //Convert 0.1mm units to mm + + return (tempAccuracy); +} + +//Get the current latitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLatitude(uint16_t maxWait) +{ + if (moduleQueried.latitude == false) + getPVT(maxWait); + moduleQueried.latitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (latitude); +} + +//Get the current longitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLongitude(uint16_t maxWait) +{ + if (moduleQueried.longitude == false) + getPVT(maxWait); + moduleQueried.longitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (longitude); +} + +//Get the current altitude in mm according to ellipsoid model +int32_t SFE_UBLOX_GPS::getAltitude(uint16_t maxWait) +{ + if (moduleQueried.altitude == false) + getPVT(maxWait); + moduleQueried.altitude = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (altitude); +} + +//Get the current altitude in mm according to mean sea level +//Ellipsoid model: https://www.esri.com/news/arcuser/0703/geoid1of3.html +//Difference between Ellipsoid Model and Mean Sea Level: https://eos-gnss.com/elevation-for-beginners/ +int32_t SFE_UBLOX_GPS::getAltitudeMSL(uint16_t maxWait) +{ + if (moduleQueried.altitudeMSL == false) + getPVT(maxWait); + moduleQueried.altitudeMSL = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (altitudeMSL); +} + +//Get the number of satellites used in fix +uint8_t SFE_UBLOX_GPS::getSIV(uint16_t maxWait) +{ + if (moduleQueried.SIV == false) + getPVT(maxWait); + moduleQueried.SIV = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (SIV); +} + +//Get the current fix type +//0=no fix, 1=dead reckoning, 2=2D, 3=3D, 4=GNSS, 5=Time fix +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; + + return (fixType); +} + +//Get the carrier phase range solution status +//Useful when querying module to see if it has high-precision RTK fix +//0=No solution, 1=Float solution, 2=Fixed solution +uint8_t SFE_UBLOX_GPS::getCarrierSolutionType(uint16_t maxWait) +{ + if (moduleQueried.carrierSolution == false) + getPVT(maxWait); + moduleQueried.carrierSolution = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (carrierSolution); +} + +//Get the ground speed in mm/s +int32_t SFE_UBLOX_GPS::getGroundSpeed(uint16_t maxWait) +{ + if (moduleQueried.groundSpeed == false) + getPVT(maxWait); + moduleQueried.groundSpeed = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (groundSpeed); +} + +//Get the heading of motion (as opposed to heading of car) in degrees * 10^-5 +int32_t SFE_UBLOX_GPS::getHeading(uint16_t maxWait) +{ + if (moduleQueried.headingOfMotion == false) + getPVT(maxWait); + moduleQueried.headingOfMotion = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (headingOfMotion); +} + +//Get the positional dillution of precision * 10^-2 +uint16_t SFE_UBLOX_GPS::getPDOP(uint16_t maxWait) +{ + if (moduleQueried.pDOP == false) + getPVT(maxWait); + moduleQueried.pDOP = false; //Since we are about to give this to user, mark this data as stale + moduleQueried.all = false; + + return (pDOP); +} + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionHigh(uint16_t maxWait) +{ + if (moduleQueried.versionNumber == false) + getProtocolVersion(maxWait); + return (versionHigh); +} + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionLow(uint16_t maxWait) +{ + if (moduleQueried.versionNumber == false) + getProtocolVersion(maxWait); + return (versionLow); +} + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +bool SFE_UBLOX_GPS::getProtocolVersion(uint16_t maxWait) +{ + //Send packet with only CLS and ID, length of zero. This will cause the module to respond with the contents of that CLS/ID. + packetCfg.cls = UBX_CLASS_MON; + packetCfg.id = UBX_MON_VER; + + packetCfg.len = 0; + packetCfg.startingSpot = 40; //Start at first "extended software information" string + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (false); //If command send fails then bail + + //Payload should now contain ~220 characters (depends on module type) + + // if (_printDebug == true) + // { + // printk("MON VER Payload:")); + // for (int location = 0; location < packetCfg.len; location++) + // { + // if (location % 30 == 0) + // printk("\n"); + // printk("%d", payloadCfg[location]); + // } + // printk("\n"); + // } + + //We will step through the payload looking at each extension field of 30 bytes + for (uint8_t extensionNumber = 0; extensionNumber < 10; extensionNumber++) + { + //Now we need to find "PROTVER=18.00" in the incoming byte stream + if (payloadCfg[(30 * extensionNumber) + 0] == 'P' && payloadCfg[(30 * extensionNumber) + 6] == 'R') + { + 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 + + if (_printDebug == true) + { + printk("Protocol version: %d.%d\n", versionHigh, 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.gpsDateValid = false; + moduleQueried.gpsTimeValid = 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 +bool SFE_UBLOX_GPS::getRELPOSNED(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_RELPOSNED; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (false); //If command send fails then bail + + //We got a response, now parse the bits + + uint16_t refStationID = extractInt(2); + //printk("refStationID: %d\n", refStationID); + + int32_t tempRelPos; + + tempRelPos = extractLong(8); + relPosInfo.relPosN = tempRelPos / 100.0; //Convert cm to m + + tempRelPos = extractLong(12); + relPosInfo.relPosE = tempRelPos / 100.0; //Convert cm to m + + tempRelPos = extractLong(16); + relPosInfo.relPosD = tempRelPos / 100.0; //Convert cm to m + + relPosInfo.relPosLength = extractLong(20); + relPosInfo.relPosHeading = extractLong(24); + + relPosInfo.relPosHPN = payloadCfg[32]; + relPosInfo.relPosHPE = payloadCfg[33]; + relPosInfo.relPosHPD = payloadCfg[34]; + relPosInfo.relPosHPLength = payloadCfg[35]; + + uint32_t tempAcc; + + tempAcc = extractLong(36); + relPosInfo.accN = tempAcc / 10000.0; //Convert 0.1 mm to m + + tempAcc = extractLong(40); + relPosInfo.accE = tempAcc / 10000.0; //Convert 0.1 mm to m + + tempAcc = extractLong(44); + relPosInfo.accD = tempAcc / 10000.0; //Convert 0.1 mm to m + + uint8_t flags = payloadCfg[60]; + + relPosInfo.gnssFixOk = flags & (1 << 0); + relPosInfo.diffSoln = flags & (1 << 1); + relPosInfo.relPosValid = flags & (1 << 2); + relPosInfo.carrSoln = (flags & (0b11 << 3)) >> 3; + relPosInfo.isMoving = flags & (1 << 5); + relPosInfo.refPosMiss = flags & (1 << 6); + relPosInfo.refObsMiss = flags & (1 << 7); + + return (true); +} +bool SFE_UBLOX_GPS::getEsfInfo(uint16_t maxWait) +{ + // Requesting Data from the receiver + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_STATUS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + // payload should be loaded. + imuMeas.version = extractByte(4); + imuMeas.fusionMode = extractByte(12); + ubloxSen.numSens = extractByte(15); + + // Individual Status Sensor in different function + return (true); +} + +// +bool SFE_UBLOX_GPS::getEsfIns(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_INS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + // Validity of each sensor value below + uint32_t validity = extractLong(0); + + imuMeas.xAngRateVald = (validity && 0x0080) >> 8; + imuMeas.yAngRateVald = (validity && 0x0100) >> 9; + imuMeas.zAngRateVald = (validity && 0x0200) >> 10; + imuMeas.xAccelVald = (validity && 0x0400) >> 11; + imuMeas.yAccelVald = (validity && 0x0800) >> 12; + imuMeas.zAccelVald = (validity && 0x1000) >> 13; + + imuMeas.xAngRate = extractLong(12); // deg/s + imuMeas.yAngRate = extractLong(16); // deg/s + imuMeas.zAngRate = extractLong(20); // deg/s + + imuMeas.xAccel = extractLong(24); // m/s + imuMeas.yAccel = extractLong(28); // m/s + imuMeas.zAccel = extractLong(32); // m/s + + return (true); +} + +// +bool SFE_UBLOX_GPS::getEsfDataInfo(uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_MEAS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + uint32_t timeStamp = extractLong(0); + uint32_t flags = extractInt(4); + + uint8_t timeSent = (flags && 0x01) >> 1; + uint8_t timeEdge = (flags && 0x02) >> 2; + uint8_t tagValid = (flags && 0x04) >> 3; + uint8_t numMeas = (flags && 0x1000) >> 15; + + if (numMeas > DEF_NUM_SENS) + numMeas = DEF_NUM_SENS; + + uint8_t byteOffset = 4; + + for (uint8_t i = 0; i < numMeas; i++) + { + + uint32_t bitField = extractLong(4 + byteOffset * i); + imuMeas.dataType[i] = (bitField && 0xFF000000) >> 23; + imuMeas.data[i] = (bitField && 0xFFFFFF); + imuMeas.dataTStamp[i] = extractLong(8 + byteOffset * i); + } + + return (true); +} + +bool SFE_UBLOX_GPS::getEsfRawDataInfo(uint16_t maxWait) +{ + + // Need to know the number of sensor to get the correct data + // Rate selected in UBX-CFG-MSG is not respected + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_RAW; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); //If command send fails then bail + + checkUblox(); + + uint32_t bitField = extractLong(4); + imuMeas.rawDataType = (bitField && 0xFF000000) >> 23; + imuMeas.rawData = (bitField && 0xFFFFFF); + imuMeas.rawTStamp = extractLong(8); + + return (true); +} + +sfe_ublox_status_e SFE_UBLOX_GPS::getSensState(uint8_t sensor, uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_STATUS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (SFE_UBLOX_STATUS_FAIL); //If command send fails then bail + + ubloxSen.numSens = extractByte(15); + + if (sensor > ubloxSen.numSens) + return (SFE_UBLOX_STATUS_OUT_OF_RANGE); + + checkUblox(); + + uint8_t offset = 4; + + // Only the last sensor value checked will remain. + for (uint8_t i = 0; i < sensor; i++) + { + + uint8_t sensorFieldOne = extractByte(16 + offset * i); + uint8_t sensorFieldTwo = extractByte(17 + offset * i); + ubloxSen.freq = extractByte(18 + offset * i); + uint8_t sensorFieldThr = extractByte(19 + offset * i); + + ubloxSen.senType = (sensorFieldOne && 0x10) >> 5; + ubloxSen.isUsed = (sensorFieldOne && 0x20) >> 6; + ubloxSen.isReady = (sensorFieldOne && 0x30) >> 7; + + ubloxSen.calibStatus = sensorFieldTwo && 0x03; + ubloxSen.timeStatus = (sensorFieldTwo && 0xC) >> 2; + + ubloxSen.badMeas = (sensorFieldThr && 0x01); + ubloxSen.badTag = (sensorFieldThr && 0x02) >> 1; + ubloxSen.missMeas = (sensorFieldThr && 0x04) >> 2; + ubloxSen.noisyMeas = (sensorFieldThr && 0x08) >> 3; + } + + return (SFE_UBLOX_STATUS_SUCCESS); +} + +bool SFE_UBLOX_GPS::getVehAtt(uint16_t maxWait) +{ + + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_ATT; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (SFE_UBLOX_STATUS_FAIL); //If command send fails then bail + + checkUblox(); + + vehAtt.roll = extractLong(8); + vehAtt.pitch = extractLong(12); + vehAtt.heading = extractLong(16); + + vehAtt.accRoll = extractLong(20); + vehAtt.accPitch = extractLong(24); + vehAtt.accHeading = extractLong(28); + + return (true); +} diff --git a/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.h b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.h new file mode 100644 index 0000000..7163d79 --- /dev/null +++ b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/SparkFun_Ublox_Zephyr_Library.h @@ -0,0 +1,901 @@ +/* + This is a library written for the u-blox ZED-F9P and NEO-M8P-2 + SparkFun sells these at its website: www.sparkfun.com + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Written by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + + This library handles configuring and handling the responses + from a u-blox GPS module. Works with most modules from u-blox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + NCS v1.0.3 release + + This port was made by Vid Rajtmajer , IRNAS www.irnas.eu + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SPARKFUN_UBLOX_ZEPHYR_LIBRARY_H +#define SPARKFUN_UBLOX_ZEPHYR_LIBRARY_H + +#include +#include + +//The catch-all default is 32 +#define I2C_BUFFER_LENGTH 32 + +//Define pin states +#define LOW 0 +#define HIGH 1 + +//Define a digital pin to aid checksum failure capture and analysis +//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_NACK, // Indicates that the command was unrecognised, invalid or that the module is too busy to respond + 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, // This indicates that a 'set' was successful + SFE_UBLOX_STATUS_DATA_RECEIVED, // This indicates that a 'get' (poll) was successful + SFE_UBLOX_STATUS_I2C_COMM_FAILURE, + SFE_UBLOX_STATUS_DATA_OVERWRITTEN // This is an error - the data was valid but has been or _is being_ overwritten by another packet +} sfe_ublox_status_e; + +// ubxPacket validity +typedef enum +{ + SFE_UBLOX_PACKET_VALIDITY_NOT_VALID, + SFE_UBLOX_PACKET_VALIDITY_VALID, + SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, + SFE_UBLOX_PACKET_NOTACKNOWLEDGED // This indicates that we received a NACK +} sfe_ublox_packet_validity_e; + +// Identify which packet buffer is in use: +// packetCfg (or a custom packet), packetAck or packetBuf +typedef enum +{ + SFE_UBLOX_PACKET_PACKETCFG, + SFE_UBLOX_PACKET_PACKETACK, + SFE_UBLOX_PACKET_PACKETBUF +} sfe_ublox_packet_buffer_e; + +//Registers +const uint8_t UBX_SYNCH_1 = 0xB5; +const uint8_t UBX_SYNCH_2 = 0x62; + +//The following are UBX Class IDs. Descriptions taken from ZED-F9P Interface Description Document page 32, NEO-M8P Interface Description page 145 +const uint8_t UBX_CLASS_NAV = 0x01; //Navigation Results Messages: Position, Speed, Time, Acceleration, Heading, DOP, SVs used +const uint8_t UBX_CLASS_RXM = 0x02; //Receiver Manager Messages: Satellite Status, RTC Status +const uint8_t UBX_CLASS_INF = 0x04; //Information Messages: Printf-Style Messages, with IDs such as Error, Warning, Notice +const uint8_t UBX_CLASS_ACK = 0x05; //Ack/Nak Messages: Acknowledge or Reject messages to UBX-CFG input messages +const uint8_t UBX_CLASS_CFG = 0x06; //Configuration Input Messages: Configure the receiver. +const uint8_t UBX_CLASS_UPD = 0x09; //Firmware Update Messages: Memory/Flash erase/write, Reboot, Flash identification, etc. +const uint8_t UBX_CLASS_MON = 0x0A; //Monitoring Messages: Communication Status, CPU Load, Stack Usage, Task Status +const uint8_t UBX_CLASS_AID = 0x0B; //(NEO-M8P ONLY!!!) AssistNow Aiding Messages: Ephemeris, Almanac, other A-GPS data input +const uint8_t UBX_CLASS_TIM = 0x0D; //Timing Messages: Time Pulse Output, Time Mark Results +const uint8_t UBX_CLASS_ESF = 0x10; //(NEO-M8P ONLY!!!) External Sensor Fusion Messages: External Sensor Measurements and Status Information +const uint8_t UBX_CLASS_MGA = 0x13; //Multiple GNSS Assistance Messages: Assistance data for various GNSS +const uint8_t UBX_CLASS_LOG = 0x21; //Logging Messages: Log creation, deletion, info and retrieval +const uint8_t UBX_CLASS_SEC = 0x27; //Security Feature Messages +const uint8_t UBX_CLASS_HNR = 0x28; //(NEO-M8P ONLY!!!) High Rate Navigation Results Messages: High rate time, position speed, heading +const uint8_t UBX_CLASS_NMEA = 0xF0; //NMEA Strings: standard NMEA strings + +//The following are used for configuration. Descriptions are from the ZED-F9P Interface Description pg 33-34 and NEO-M9N Interface Description pg 47-48 +const uint8_t UBX_CFG_ANT = 0x13; //Antenna Control Settings. Used to configure the antenna control settings +const uint8_t UBX_CFG_BATCH = 0x93; //Get/set data batching configuration. +const uint8_t UBX_CFG_CFG = 0x09; //Clear, Save, and Load Configurations. Used to save current configuration +const uint8_t UBX_CFG_DAT = 0x06; //Set User-defined Datum or The currently defined Datum +const uint8_t UBX_CFG_DGNSS = 0x70; //DGNSS configuration +const uint8_t UBX_CFG_GEOFENCE = 0x69; //Geofencing configuration. Used to configure a geofence +const uint8_t UBX_CFG_GNSS = 0x3E; //GNSS system configuration +const uint8_t UBX_CFG_INF = 0x02; //Depending on packet length, either: poll configuration for one protocol, or information message configuration +const uint8_t UBX_CFG_ITFM = 0x39; //Jamming/Interference Monitor configuration +const uint8_t UBX_CFG_LOGFILTER = 0x47; //Data Logger Configuration +const uint8_t UBX_CFG_MSG = 0x01; //Poll a message configuration, or Set Message Rate(s), or Set Message Rate +const uint8_t UBX_CFG_NAV5 = 0x24; //Navigation Engine Settings. Used to configure the navigation engine including the dynamic model. +const uint8_t UBX_CFG_NAVX5 = 0x23; //Navigation Engine Expert Settings +const uint8_t UBX_CFG_NMEA = 0x17; //Extended NMEA protocol configuration V1 +const uint8_t UBX_CFG_ODO = 0x1E; //Odometer, Low-speed COG Engine Settings +const uint8_t UBX_CFG_PM2 = 0x3B; //Extended power management configuration +const uint8_t UBX_CFG_PMS = 0x86; //Power mode setup +const uint8_t UBX_CFG_PRT = 0x00; //Used to configure port specifics. Polls the configuration for one I/O Port, or Port configuration for UART ports, or Port configuration for USB port, or Port configuration for SPI port, or Port configuration for DDC port +const uint8_t UBX_CFG_PWR = 0x57; //Put receiver in a defined power state +const uint8_t UBX_CFG_RATE = 0x08; //Navigation/Measurement Rate Settings. Used to set port baud rates. +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_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 +const uint8_t UBX_CFG_VALDEL = 0x8C; //Used for config of higher version u-blox modules (ie protocol v27 and above). Deletes values corresponding to provided keys/ provided keys with a transaction +const uint8_t UBX_CFG_VALGET = 0x8B; //Used for config of higher version u-blox modules (ie protocol v27 and above). Configuration Items +const uint8_t UBX_CFG_VALSET = 0x8A; //Used for config of higher version u-blox modules (ie protocol v27 and above). Sets values corresponding to provided key-value pairs/ provided key-value pairs within a transaction. + +//The following are used to enable NMEA messages. Descriptions come from the NMEA messages overview in the ZED-F9P Interface Description +const uint8_t UBX_NMEA_MSB = 0xF0; //All NMEA enable commands have 0xF0 as MSB +const uint8_t UBX_NMEA_DTM = 0x0A; //GxDTM (datum reference) +const uint8_t UBX_NMEA_GAQ = 0x45; //GxGAQ (poll a standard message (if the current talker ID is GA)) +const uint8_t UBX_NMEA_GBQ = 0x44; //GxGBQ (poll a standard message (if the current Talker ID is GB)) +const uint8_t UBX_NMEA_GBS = 0x09; //GxGBS (GNSS satellite fault detection) +const uint8_t UBX_NMEA_GGA = 0x00; //GxGGA (Global positioning system fix data) +const uint8_t UBX_NMEA_GLL = 0x01; //GxGLL (latitude and long, whith time of position fix and status) +const uint8_t UBX_NMEA_GLQ = 0x43; //GxGLQ (poll a standard message (if the current Talker ID is GL)) +const uint8_t UBX_NMEA_GNQ = 0x42; //GxGNQ (poll a standard message (if the current Talker ID is GN)) +const uint8_t UBX_NMEA_GNS = 0x0D; //GxGNS (GNSS fix data) +const uint8_t UBX_NMEA_GPQ = 0x040; //GxGPQ (poll a standard message (if the current Talker ID is GP)) +const uint8_t UBX_NMEA_GRS = 0x06; //GxGRS (GNSS range residuals) +const uint8_t UBX_NMEA_GSA = 0x02; //GxGSA (GNSS DOP and Active satellites) +const uint8_t UBX_NMEA_GST = 0x07; //GxGST (GNSS Pseudo Range Error Statistics) +const uint8_t UBX_NMEA_GSV = 0x03; //GxGSV (GNSS satellites in view) +const uint8_t UBX_NMEA_RMC = 0x04; //GxRMC (Recommended minimum data) +const uint8_t UBX_NMEA_TXT = 0x41; //GxTXT (text transmission) +const uint8_t UBX_NMEA_VLW = 0x0F; //GxVLW (dual ground/water distance) +const uint8_t UBX_NMEA_VTG = 0x05; //GxVTG (course over ground and Ground speed) +const uint8_t UBX_NMEA_ZDA = 0x08; //GxZDA (Time and Date) + +//The following are used to configure the NMEA protocol main talker ID and GSV talker ID +const uint8_t UBX_NMEA_MAINTALKERID_NOTOVERRIDDEN = 0x00; //main talker ID is system dependent +const uint8_t UBX_NMEA_MAINTALKERID_GP = 0x01; //main talker ID is GPS +const uint8_t UBX_NMEA_MAINTALKERID_GL = 0x02; //main talker ID is GLONASS +const uint8_t UBX_NMEA_MAINTALKERID_GN = 0x03; //main talker ID is combined receiver +const uint8_t UBX_NMEA_MAINTALKERID_GA = 0x04; //main talker ID is Galileo +const uint8_t UBX_NMEA_MAINTALKERID_GB = 0x05; //main talker ID is BeiDou +const uint8_t UBX_NMEA_GSVTALKERID_GNSS = 0x00; //GNSS specific Talker ID (as defined by NMEA) +const uint8_t UBX_NMEA_GSVTALKERID_MAIN = 0x01; //use the main Talker ID + +//The following are used to configure INF UBX messages (information messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_INF_CLASS = 0x04; //All INF messages have 0x04 as the class +const uint8_t UBX_INF_DEBUG = 0x04; //ASCII output with debug contents +const uint8_t UBX_INF_ERROR = 0x00; //ASCII output with error contents +const uint8_t UBX_INF_NOTICE = 0x02; //ASCII output with informational contents +const uint8_t UBX_INF_TEST = 0x03; //ASCII output with test contents +const uint8_t UBX_INF_WARNING = 0x01; //ASCII output with warning contents + +//The following are used to configure LOG UBX messages (loggings messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_LOG_CREATE = 0x07; //Create Log File +const uint8_t UBX_LOG_ERASE = 0x03; //Erase Logged Data +const uint8_t UBX_LOG_FINDTIME = 0x0E; //Find index of a log entry based on a given time, or response to FINDTIME requested +const uint8_t UBX_LOG_INFO = 0x08; //Poll for log information, or Log information +const uint8_t UBX_LOG_RETRIEVEPOSEXTRA = 0x0F; //Odometer log entry +const uint8_t UBX_LOG_RETRIEVEPOS = 0x0B; //Position fix log entry +const uint8_t UBX_LOG_RETRIEVESTRING = 0x0D; //Byte string log entry +const uint8_t UBX_LOG_RETRIEVE = 0x09; //Request log data +const uint8_t UBX_LOG_STRING = 0x04; //Store arbitrary string on on-board flash + +//The following are used to configure MGA UBX messages (Multiple GNSS Assistance Messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_MGA_ACK_DATA0 = 0x60; //Multiple GNSS Acknowledge message +const uint8_t UBX_MGA_BDS_EPH = 0x03; //BDS Ephemeris Assistance +const uint8_t UBX_MGA_BDS_ALM = 0x03; //BDS Almanac Assistance +const uint8_t UBX_MGA_BDS_HEALTH = 0x03; //BDS Health Assistance +const uint8_t UBX_MGA_BDS_UTC = 0x03; //BDS UTC Assistance +const uint8_t UBX_MGA_BDS_IONO = 0x03; //BDS Ionospheric Assistance +const uint8_t UBX_MGA_DBD = 0x80; //Either: Poll the Navigation Database, or Navigation Database Dump Entry +const uint8_t UBX_MGA_GAL_EPH = 0x02; //Galileo Ephemeris Assistance +const uint8_t UBX_MGA_GAL_ALM = 0x02; //Galileo Almanac Assitance +const uint8_t UBX_MGA_GAL_TIMOFFSET = 0x02; //Galileo GPS time offset assistance +const uint8_t UBX_MGA_GAL_UTC = 0x02; //Galileo UTC Assistance +const uint8_t UBX_MGA_GLO_EPH = 0x06; //GLONASS Ephemeris Assistance +const uint8_t UBX_MGA_GLO_ALM = 0x06; //GLONASS Almanac Assistance +const uint8_t UBX_MGA_GLO_TIMEOFFSET = 0x06; //GLONASS Auxiliary Time Offset Assistance +const uint8_t UBX_MGA_GPS_EPH = 0x00; //GPS Ephemeris Assistance +const uint8_t UBX_MGA_GPS_ALM = 0x00; //GPS Almanac Assistance +const uint8_t UBX_MGA_GPS_HEALTH = 0x00; //GPS Health Assistance +const uint8_t UBX_MGA_GPS_UTC = 0x00; //GPS UTC Assistance +const uint8_t UBX_MGA_GPS_IONO = 0x00; //GPS Ionosphere Assistance +const uint8_t UBX_MGA_INI_POS_XYZ = 0x40; //Initial Position Assistance +const uint8_t UBX_MGA_INI_POS_LLH = 0x40; //Initial Position Assitance +const uint8_t UBX_MGA_INI_TIME_UTC = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_TIME_GNSS = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_CLKD = 0x40; //Initial Clock Drift Assitance +const uint8_t UBX_MGA_INI_FREQ = 0x40; //Initial Frequency Assistance +const uint8_t UBX_MGA_INI_EOP = 0x40; //Earth Orientation Parameters Assistance +const uint8_t UBX_MGA_QZSS_EPH = 0x05; //QZSS Ephemeris Assistance +const uint8_t UBX_MGA_QZSS_ALM = 0x05; //QZSS Almanac Assistance +const uint8_t UBX_MGA_QZAA_HEALTH = 0x05; //QZSS Health Assistance + +//The following are used to configure the MON UBX messages (monitoring messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35) +const uint8_t UBX_MON_COMMS = 0x36; //Comm port information +const uint8_t UBX_MON_GNSS = 0x28; //Information message major GNSS selection +const uint8_t UBX_MON_HW2 = 0x0B; //Extended Hardware Status +const uint8_t UBX_MON_HW3 = 0x37; //HW I/O pin information +const uint8_t UBX_MON_HW = 0x09; //Hardware Status +const uint8_t UBX_MON_IO = 0x02; //I/O Subsystem Status +const uint8_t UBX_MON_MSGPP = 0x06; //Message Parse and Process Status +const uint8_t UBX_MON_PATCH = 0x27; //Output information about installed patches +const uint8_t UBX_MON_RF = 0x38; //RF information +const uint8_t UBX_MON_RXBUF = 0x07; //Receiver Buffer Status +const uint8_t UBX_MON_RXR = 0x21; //Receiver Status Information +const uint8_t UBX_MON_TXBUF = 0x08; //Transmitter Buffer Status. Used for query tx buffer size/state. +const uint8_t UBX_MON_VER = 0x04; //Receiver/Software Version. Used for obtaining Protocol Version. + +//The following are used to configure the NAV UBX messages (navigation results messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35-36) +const uint8_t UBX_NAV_ATT = 0x05; //Vehicle "Attitude" Solution +const uint8_t UBX_NAV_CLOCK = 0x22; //Clock Solution +const uint8_t UBX_NAV_DOP = 0x04; //Dilution of precision +const uint8_t UBX_NAV_EOE = 0x61; //End of Epoch +const uint8_t UBX_NAV_GEOFENCE = 0x39; //Geofencing status. Used to poll the geofence status +const uint8_t UBX_NAV_HPPOSECEF = 0x13; //High Precision Position Solution in ECEF. Used to find our positional accuracy (high precision). +const uint8_t UBX_NAV_HPPOSLLH = 0x14; //High Precision Geodetic Position Solution. Used for obtaining lat/long/alt in high precision +const uint8_t UBX_NAV_ODO = 0x09; //Odometer Solution +const uint8_t UBX_NAV_ORB = 0x34; //GNSS Orbit Database Info +const uint8_t UBX_NAV_POSECEF = 0x01; //Position Solution in ECEF +const uint8_t UBX_NAV_POSLLH = 0x02; //Geodetic Position Solution +const uint8_t UBX_NAV_PVT = 0x07; //All the things! Position, velocity, time, PDOP, height, h/v accuracies, number of satellites. Navigation Position Velocity Time Solution. +const uint8_t UBX_NAV_RELPOSNED = 0x3C; //Relative Positioning Information in NED frame +const uint8_t UBX_NAV_RESETODO = 0x10; //Reset odometer +const uint8_t UBX_NAV_SAT = 0x35; //Satellite Information +const uint8_t UBX_NAV_SIG = 0x43; //Signal Information +const uint8_t UBX_NAV_STATUS = 0x03; //Receiver Navigation Status +const uint8_t UBX_NAV_SVIN = 0x3B; //Survey-in data. Used for checking Survey In status +const uint8_t UBX_NAV_TIMEBDS = 0x24; //BDS Time Solution +const uint8_t UBX_NAV_TIMEGAL = 0x25; //Galileo Time Solution +const uint8_t UBX_NAV_TIMEGLO = 0x23; //GLO Time Solution +const uint8_t UBX_NAV_TIMEGPS = 0x20; //GPS Time Solution +const uint8_t UBX_NAV_TIMELS = 0x26; //Leap second event information +const uint8_t UBX_NAV_TIMEUTC = 0x21; //UTC Time Solution +const uint8_t UBX_NAV_VELECEF = 0x11; //Velocity Solution in ECEF +const uint8_t UBX_NAV_VELNED = 0x12; //Velocity Solution in NED + +//The following are used to configure the RXM UBX messages (receiver manager messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_RXM_MEASX = 0x14; //Satellite Measurements for RRLP +const uint8_t UBX_RXM_PMREQ = 0x41; //Requests a Power Management task (two differenent packet sizes) +const uint8_t UBX_RXM_RAWX = 0x15; //Multi-GNSS Raw Measurement Data +const uint8_t UBX_RXM_RLM = 0x59; //Galileo SAR Short-RLM report (two different packet sizes) +const uint8_t UBX_RXM_RTCM = 0x32; //RTCM input status +const uint8_t UBX_RXM_SFRBX = 0x13; //Boradcast Navigation Data Subframe + +//The following are used to configure the SEC UBX messages (security feature messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_SEC_UNIQID = 0x03; //Unique chip ID + +//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 UPD UBX messages (firmware update messages). Descriptions from UBX messages overview (ZED-F9P Interface Description Document page 36) +const uint8_t UBX_UPD_SOS = 0x14; //Poll Backup Fil Restore Status, Create Backup File in Flash, Clear Backup File in Flash, Backup File Creation Acknowledge, System Restored from Backup + +//The following are used to enable RTCM messages +const uint8_t UBX_RTCM_MSB = 0xF5; //All RTCM enable commands have 0xF5 as MSB +const uint8_t UBX_RTCM_1005 = 0x05; //Stationary RTK reference ARP +const uint8_t UBX_RTCM_1074 = 0x4A; //GPS MSM4 +const uint8_t UBX_RTCM_1077 = 0x4D; //GPS MSM7 +const uint8_t UBX_RTCM_1084 = 0x54; //GLONASS MSM4 +const uint8_t UBX_RTCM_1087 = 0x57; //GLONASS MSM7 +const uint8_t UBX_RTCM_1094 = 0x5E; //Galileo MSM4 +const uint8_t UBX_RTCM_1097 = 0x61; //Galileo MSM7 +const uint8_t UBX_RTCM_1124 = 0x7C; //BeiDou MSM4 +const uint8_t UBX_RTCM_1127 = 0x7F; //BeiDou MSM7 +const uint8_t UBX_RTCM_1230 = 0xE6; //GLONASS code-phase biases, set to once every 10 seconds +const uint8_t UBX_RTCM_4072_0 = 0xFE; //Reference station PVT (ublox proprietary RTCM message) +const uint8_t UBX_RTCM_4072_1 = 0xFD; //Additional reference station information (ublox proprietary RTCM message) + +const uint8_t UBX_ACK_NACK = 0x00; +const uint8_t UBX_ACK_ACK = 0x01; +const uint8_t UBX_ACK_NONE = 0x02; //Not a real value + +// The following constants are used to get External Sensor Measurements and Status +// Information. +const uint8_t UBX_ESF_MEAS = 0x02; +const uint8_t UBX_ESF_RAW = 0x03; +const uint8_t UBX_ESF_STATUS = 0x10; +const uint8_t UBX_ESF_INS = 0x15; //36 bytes + +const uint8_t SVIN_MODE_DISABLE = 0x00; +const uint8_t SVIN_MODE_ENABLE = 0x01; + +//The following consts are used to configure the various ports and streams for those ports. See -CFG-PRT. +const uint8_t COM_PORT_I2C = 0; +const uint8_t COM_PORT_UART1 = 1; +const uint8_t COM_PORT_UART2 = 2; +const uint8_t COM_PORT_USB = 3; +const uint8_t COM_PORT_SPI = 4; + +const uint8_t COM_TYPE_UBX = (1 << 0); +const uint8_t COM_TYPE_NMEA = (1 << 1); +const uint8_t COM_TYPE_RTCM3 = (1 << 5); + +//The following consts are used to generate KEY values for the advanced protocol functions of VELGET/SET/DEL +const uint8_t VAL_SIZE_1 = 0x01; //One bit +const uint8_t VAL_SIZE_8 = 0x02; //One byte +const uint8_t VAL_SIZE_16 = 0x03; //Two bytes +const uint8_t VAL_SIZE_32 = 0x04; //Four bytes +const uint8_t VAL_SIZE_64 = 0x05; //Eight bytes + +//These are the Bitfield layers definitions for the UBX-CFG-VALSET message (not to be confused with Bitfield deviceMask in UBX-CFG-CFG) +const uint8_t VAL_LAYER_RAM = (1 << 0); +const uint8_t VAL_LAYER_BBR = (1 << 1); +const uint8_t VAL_LAYER_FLASH = (1 << 2); + +//Below are various Groups, IDs, and sizes for various settings +//These can be used to call getVal/setVal/delVal +const uint8_t VAL_GROUP_I2COUTPROT = 0x72; +const uint8_t VAL_GROUP_I2COUTPROT_SIZE = VAL_SIZE_1; //All fields in I2C group are currently 1 bit + +const uint8_t VAL_ID_I2COUTPROT_UBX = 0x01; +const uint8_t VAL_ID_I2COUTPROT_NMEA = 0x02; +const uint8_t VAL_ID_I2COUTPROT_RTCM3 = 0x03; + +const uint8_t VAL_GROUP_I2C = 0x51; +const uint8_t VAL_GROUP_I2C_SIZE = VAL_SIZE_8; //All fields in I2C group are currently 1 byte + +const uint8_t VAL_ID_I2C_ADDRESS = 0x01; + +// Configuration Sub-Section mask definitions for saveConfigSelective (UBX-CFG-CFG) +const uint32_t VAL_CFG_SUBSEC_IOPORT = 0x00000001; // ioPort - communications port settings (causes IO system reset!) +const uint32_t VAL_CFG_SUBSEC_MSGCONF = 0x00000002; // msgConf - message configuration +const uint32_t VAL_CFG_SUBSEC_INFMSG = 0x00000004; // infMsg - INF message configuration +const uint32_t VAL_CFG_SUBSEC_NAVCONF = 0x00000008; // navConf - navigation configuration +const uint32_t VAL_CFG_SUBSEC_RXMCONF = 0x00000010; // rxmConf - receiver manager configuration +const uint32_t VAL_CFG_SUBSEC_SENCONF = 0x00000100; // senConf - sensor interface configuration (requires protocol 19+) +const uint32_t VAL_CFG_SUBSEC_RINVCONF = 0x00000200; // rinvConf - remove inventory configuration +const uint32_t VAL_CFG_SUBSEC_ANTCONF = 0x00000400; // antConf - antenna configuration +const uint32_t VAL_CFG_SUBSEC_LOGCONF = 0x00000800; // logConf - logging configuration +const uint32_t VAL_CFG_SUBSEC_FTSCONF = 0x00001000; // ftsConf - FTS configuration (FTS products only) + +// Bitfield wakeupSources for UBX_RXM_PMREQ +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_UARTRX = 0x00000008; // uartrx +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 = 0x00000020; // extint0 +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT1 = 0x00000040; // extint1 +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_SPICS = 0x00000080; // spics + +enum dynModel // Possible values for the dynamic platform model, which provide more accuract position output for the situation. Description extracted from ZED-F9P Integration Manual +{ + DYN_MODEL_PORTABLE = 0, //Applications with low acceleration, e.g. portable devices. Suitable for most situations. + // 1 is not defined + DYN_MODEL_STATIONARY = 2, //Used in timing applications (antenna must be stationary) or other stationary applications. Velocity restricted to 0 m/s. Zero dynamics assumed. + DYN_MODEL_PEDESTRIAN, //Applications with low acceleration and speed, e.g. how a pedestrian would move. Low acceleration assumed. + DYN_MODEL_AUTOMOTIVE, //Used for applications with equivalent dynamics to those of a passenger car. Low vertical acceleration assumed + DYN_MODEL_SEA, //Recommended for applications at sea, with zero vertical velocity. Zero vertical velocity assumed. Sea level assumed. + DYN_MODEL_AIRBORNE1g, //Airborne <1g acceleration. Used for applications with a higher dynamic range and greater vertical acceleration than a passenger car. No 2D position fixes supported. + DYN_MODEL_AIRBORNE2g, //Airborne <2g acceleration. Recommended for typical airborne environments. No 2D position fixes supported. + DYN_MODEL_AIRBORNE4g, //Airborne <4g acceleration. Only recommended for extremely dynamic environments. No 2D position fixes supported. + DYN_MODEL_WRIST, // Not supported in protocol versions less than 18. Only recommended for wrist worn applications. Receiver will filter out arm motion. + DYN_MODEL_BIKE, // Supported in protocol versions 19.2 +}; + +#ifndef MAX_PAYLOAD_SIZE + +#define MAX_PAYLOAD_SIZE 256 //We need ~220 bytes for getProtocolVersion on most ublox modules +//#define MAX_PAYLOAD_SIZE 768 //Worst case: UBX_CFG_VALSET packet with 64 keyIDs each with 64 bit values + +#endif + +//-=-=-=-=- UBX binary specific variables +typedef struct +{ + uint8_t cls; + uint8_t 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; + uint8_t checksumA; //Given to us from 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 +} ubxPacket; + +// Struct to hold the results returned by getGeofenceState (returned by UBX-NAV-GEOFENCE) +typedef struct +{ + uint8_t status; // Geofencing status: 0 - Geofencing not available or not reliable; 1 - Geofencing active + uint8_t numFences; // Number of geofences + uint8_t combState; // Combined (logical OR) state of all geofences: 0 - Unknown; 1 - Inside; 2 - Outside + uint8_t states[4]; // Geofence states: 0 - Unknown; 1 - Inside; 2 - Outside +} geofenceState; + +// Struct to hold the current geofence parameters +typedef struct +{ + uint8_t numFences; // Number of active geofences + int32_t lats[4]; // Latitudes of geofences (in degrees * 10^-7) + int32_t longs[4]; // Longitudes of geofences (in degrees * 10^-7) + uint32_t rads[4]; // Radii of geofences (in m * 10^-2) +} geofenceParams; + +class SFE_UBLOX_GPS +{ +public: + SFE_UBLOX_GPS(void); + +// A default of 250ms for maxWait seems fine for I2C but is not enough for SerialUSB. +// If you know you are only going to be using I2C / Qwiic communication, you can +// safely reduce defaultMaxWait to 250. +#ifndef defaultMaxWait // Let's allow the user to define their own value if they want to +#define defaultMaxWait 250 // only I2C is used, so this has been reduced from 1100 +#endif + + // Set gpio device, used by checksumFailurePin + bool init_gpio_pins(struct device &gpio_dev); + + //By default use the default I2C address + bool begin(struct device &i2c_dev, uint8_t deviceAddress = 0x42); //Returns true if module is detected + //serialPort needs to be perviously initialized to correct baud rate + //bool begin(Stream &serialPort); //Returns true if module is detected - function not ported + + //Returns true if device answers on _gpsI2Caddress address or via Serial + //maxWait is only used for Serial + bool isConnected(uint16_t maxWait = 1100); + + //Changed in V1.8.1: provides backward compatibility for the examples that call checkUblox directly + //Will default to using packetCfg to look for explicit autoPVT packets so they get processed correctly by processUBX + bool checkUblox(uint8_t requestedClass = UBX_CLASS_NAV, uint8_t requestedID = UBX_NAV_PVT); //Checks module with user selected commType + + bool checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for I2C polling of data, passing any new bytes to process() + bool checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for serial polling of data, passing any new bytes to process() + + void process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Processes NMEA and UBX binary sentences one byte at a time + void processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Given a character, file it away into the uxb packet structure + void processRTCMframe(uint8_t incoming); //Monitor the incoming bytes for start and length bytes + void processRTCM(uint8_t incoming) __attribute__((weak)); //Given rtcm byte, do something with it. User can overwrite if desired to pipe bytes to radio, internet, etc. + + 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 + sfe_ublox_status_e sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait = defaultMaxWait); //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 + + void factoryReset(); //Send factory reset sequence (i.e. load "default" configuration and perform hardReset) + void hardReset(); //Perform a reset leading to a cold start (zero info start-up) + + int transferWriteI2C(u8_t *buf, u32_t num_bytes, bool stop = true); // Port to Zephyr, i2c function to actualy WRITE data + int transferReadI2C(u8_t *buf, u32_t num_bytes); // Port to Zephyr, i2c function to actualy READ data + + bool setI2CAddress(uint8_t deviceAddress, uint16_t maxTime = 250); //Changes the I2C address of the u-blox module + void setSerialRate(uint32_t baudrate, uint8_t uartPort = COM_PORT_UART1, uint16_t maxTime = defaultMaxWait); //Changes the serial baud rate of the u-blox module, uartPort should be COM_PORT_UART1/2 + void setNMEAOutputPort(); //Sets the internal variable for the port to direct NMEA characters to + + bool setNavigationFrequency(uint8_t navFreq, uint16_t maxWait = defaultMaxWait); //Set the number of nav solutions sent per second + uint8_t getNavigationFrequency(uint16_t maxWait = defaultMaxWait); //Get the number of nav solutions sent per second currently being output by module + bool saveConfiguration(uint16_t maxWait = defaultMaxWait); //Save current configuration to flash and BBR (battery backed RAM) + bool factoryDefault(uint16_t maxWait = defaultMaxWait); //Reset module to factory defaults + bool saveConfigSelective(uint32_t configMask, uint16_t maxWait = defaultMaxWait); //Save the selected configuration sub-sections to flash and BBR (battery backed RAM) + + sfe_ublox_status_e waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //Poll the module until a config packet and an ACK is received + sfe_ublox_status_e waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //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 + + bool assumeAutoPVT(bool enabled, bool implicitUpdate = true); //In case no config access to the GPS is possible and PVT is send cyclically already + bool setAutoPVT(bool enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency + bool 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. + bool setAutoPVT(bool enabled, bool implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + bool 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 number of sats used in fix + 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 + uint16_t getYear(uint16_t maxWait = getPVTmaxWait); + uint8_t getMonth(uint16_t maxWait = getPVTmaxWait); + uint8_t getDay(uint16_t maxWait = getPVTmaxWait); + uint8_t getHour(uint16_t maxWait = getPVTmaxWait); + uint8_t getMinute(uint16_t maxWait = getPVTmaxWait); + uint8_t getSecond(uint16_t maxWait = getPVTmaxWait); + uint16_t getMillisecond(uint16_t maxWait = getPVTmaxWait); + int32_t getNanosecond(uint16_t maxWait = getPVTmaxWait); + uint32_t getTimeOfWeek(uint16_t maxWait = getPVTmaxWait); + bool getDateValid(uint16_t maxWait = getPVTmaxWait); + bool getTimeValid(uint16_t maxWait = getPVTmaxWait); + + int32_t getHighResLatitude(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getHighResLatitudeHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getHighResLongitude(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getHighResLongitudeHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getElipsoid(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getElipsoidHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getMeanSeaLevel(uint16_t maxWait = getHPPOSLLHmaxWait); + int8_t getMeanSeaLevelHp(uint16_t maxWait = getHPPOSLLHmaxWait); + int32_t getGeoidSeparation(uint16_t maxWait = getHPPOSLLHmaxWait); + uint32_t getHorizontalAccuracy(uint16_t maxWait = getHPPOSLLHmaxWait); + uint32_t getVerticalAccuracy(uint16_t maxWait = getHPPOSLLHmaxWait); + + //Port configurations + bool setPortOutput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof + bool setPortInput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof + bool getPortSettings(uint8_t portID, uint16_t maxWait = defaultMaxWait); //Returns the current protocol bits in the UBX-CFG-PRT command for a given port + + bool setI2COutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure I2C port to output UBX, NMEA, RTCM3 or a combination thereof + bool setUART1Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART1 port to output UBX, NMEA, RTCM3 or a combination thereof + bool setUART2Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART2 port to output UBX, NMEA, RTCM3 or a combination thereof + bool setUSBOutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure USB port to output UBX, NMEA, RTCM3 or a combination thereof + bool setSPIOutput(uint8_t comSettings, uint16_t maxWait = 250); //Configure SPI port to output UBX, NMEA, RTCM3 or a combination thereof + + //Functions to turn on/off message types for a given port ID (see COM_PORT_I2C, etc above) + bool configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); + bool enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + bool disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + bool enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + bool disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + bool enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); //Given a message number turns on a message ID for output over given PortID + bool disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait = defaultMaxWait); //Turn off given RTCM message from a given port + + //General configuration (used only on protocol v27 and higher - ie, ZED-F9P) + //It is probably safe to assume that users ofovaj the ZED-F9P will be using I2C / Qwiic. + //If they are using Serial then the higher baud rate will also help. So let's leave maxWait set to 250ms. + uint8_t getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint8_t getVal8(uint32_t keyID, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint8_t setVal(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 8-bit value at a given group/id/size location + uint8_t setVal16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_BBR, uint16_t maxWait = 250); //Sets the 32-bit value at a given group/id/size location + uint8_t newCfgValset8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 8-bit value + uint8_t newCfgValset16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 16-bit value + uint8_t newCfgValset32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_BBR); //Define a new UBX-CFG-VALSET with the given KeyID and 32-bit value + uint8_t addCfgValset8(uint32_t keyID, uint8_t value); //Add a new KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset16(uint32_t keyID, uint16_t value); //Add a new KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset32(uint32_t keyID, uint32_t value); //Add a new KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t sendCfgValset8(uint32_t keyID, uint8_t value, uint16_t maxWait = 250); //Add the final KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset16(uint32_t keyID, uint16_t value, uint16_t maxWait = 250); //Add the final KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset32(uint32_t keyID, uint32_t value, uint16_t maxWait = 250); //Add the final KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + + //Functions used for RTK and base station setup + //It is probably safe to assume that users of the RTK will be using I2C / Qwiic. So let's leave maxWait set to 250ms. + bool getSurveyMode(uint16_t maxWait = 250); //Get the current TimeMode3 settings + bool setSurveyMode(uint8_t mode, uint16_t ovajobservationTime, float requiredAccuracy, uint16_t maxWait = 250); //Control survey in mode + bool enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); //Begin Survey-In for NEO-M8P + bool disableSurveyMode(uint16_t maxWait = 250); //Stop Survey-In mode + + bool getSurveyStatus(uint16_t maxWait); //Reads survey in status and sets the global variables + + uint32_t getPositionAccuracy(uint16_t maxWait = 1100); //Returns the 3D accuracy of the current high-precision fix, in mm. Supported on NEO-M8P, ZED-F9P, + + uint8_t getProtocolVersionHigh(uint16_t maxWait = 500); //Returns the PROTVER XX.00 from UBX-MON-VER register + uint8_t getProtocolVersionLow(uint16_t maxWait = 500); //Returns the PROTVER 00.XX from UBX-MON-VER register + bool getProtocolVersion(uint16_t maxWait = 500); //Queries module, loads low/high bytes + + bool getRELPOSNED(uint16_t maxWait = 1100); //Get Relative Positioning Information of the NED frame + + void enableDebugging(bool printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + 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 + bool addGeofence(int32_t latitude, int32_t longitude, uint32_t radius, uint8_t confidence = 0, uint8_t pinPolarity = 0, uint8_t pin = 0, uint16_t maxWait = 1100); // Add a new geofence + bool clearGeofences(uint16_t maxWait = 1100); //Clears all geofences + bool getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait = 1100); //Returns the combined geofence state + bool clearAntPIO(uint16_t maxWait = 1100); //Clears the antenna control pin settings to release the PIOs + geofenceParams currentGeofenceParams; // Global to store the geofence parameters + + bool powerSaveMode(bool power_save = true, uint16_t maxWait = 1100); + uint8_t getPowerSaveMode(uint16_t maxWait = 1100); // Returns 255 if the sendCommand fails + bool powerOff(uint32_t durationInMs, uint16_t maxWait = 1100); + bool powerOffWithInterrupt(uint32_t durationInMs, uint32_t wakeupSources = VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0, bool forceWhileUsb = true, uint16_t maxWait = 1100); + + //Change the dynamic platform model using UBX-CFG-NAV5 + bool setDynamicModel(dynModel newDynamicModel = DYN_MODEL_PORTABLE, uint16_t maxWait = 1100); + uint8_t getDynamicModel(uint16_t maxWait = 1100); // Get the dynamic model - returns 255 if the sendCommand fails + + bool getEsfInfo(uint16_t maxWait = 1100); + bool getEsfIns(uint16_t maxWait = 1100); + bool getEsfDataInfo(uint16_t maxWait = 1100); + bool getEsfRawDataInfo(uint16_t maxWait = 1100); + sfe_ublox_status_e getSensState(uint8_t sensor, uint16_t maxWait = 1100); + bool getVehAtt(uint16_t maxWait = 1100); + + //Survey-in specific controls + struct svinStructure + { + bool active; + bool valid; + uint16_t observationTime; + float meanAccuracy; + } svin; + + //Relative Positioning Info in NED frame specific controls + struct frelPosInfoStructure + { + uint16_t refStationID; + + float relPosN; + float relPosE; + float relPosD; + + long relPosLength; + long relPosHeading; + + int8_t relPosHPN; + int8_t relPosHPE; + int8_t relPosHPD; + int8_t relPosHPLength; + + float accN; + float accE; + float accD; + + bool gnssFixOk; + bool diffSoln; + bool relPosValid; + uint8_t carrSoln; + bool isMoving; + bool refPosMiss; + bool refObsMiss; + } relPosInfo; + + //The major datums we want to globally store + uint16_t gpsYear; + uint8_t gpsMonth; + uint8_t gpsDay; + uint8_t gpsHour; + uint8_t gpsMinute; + uint8_t gpsSecond; + uint16_t gpsMillisecond; + int32_t gpsNanosecond; + bool gpsDateValid; + bool gpsTimeValid; + + int32_t latitude; //Degrees * 10^-7 (more accurate than floats) + int32_t longitude; //Degrees * 10^-7 (more accurate than floats) + int32_t altitude; //Number of mm above ellipsoid + int32_t altitudeMSL; //Number of mm above Mean Sea Level + uint8_t SIV; //Number of satellites used in position solution + uint8_t fixType; //Tells us when we have a solution aka lock + uint8_t carrierSolution; //Tells us when we have an RTK float/fixed solution + int32_t groundSpeed; //mm/s + int32_t headingOfMotion; //degrees * 10^-5 + uint16_t pDOP; //Positional dilution of precision + uint8_t versionLow; //Loaded from getProtocolVersion(). + uint8_t versionHigh; + + uint32_t timeOfWeek; // ms + int32_t highResLatitude; // Degrees * 10^-7 + int32_t highResLongitude; // Degrees * 10^-7 + int32_t elipsoid; // Height above ellipsoid in mm (Typo! Should be eLLipsoid! **Uncorrected for backward-compatibility.**) + int32_t meanSeaLevel; // Height above mean sea level in mm + int32_t geoidSeparation; // This seems to only be provided in NMEA GGA and GNS messages + uint32_t horizontalAccuracy; // mm * 10^-1 (i.e. 0.1mm) + uint32_t verticalAccuracy; // mm * 10^-1 (i.e. 0.1mm) + int8_t elipsoidHp; // High precision component of the height above ellipsoid in mm * 10^-1 (Deliberate typo! Should be eLLipsoidHp!) + int8_t meanSeaLevelHp; // High precision component of Height above mean sea level in mm * 10^-1 + int8_t highResLatitudeHp; // High precision component of latitude: Degrees * 10^-9 + int8_t highResLongitudeHp; // High precision component of longitude: Degrees * 10^-9 + + uint16_t rtcmFrameCounter = 0; //Tracks the type of incoming byte inside RTCM frame + +#define DEF_NUM_SENS 7 + struct deadReckData + { + uint8_t version; + uint8_t fusionMode; + + uint8_t xAngRateVald; + uint8_t yAngRateVald; + uint8_t zAngRateVald; + uint8_t xAccelVald; + uint8_t yAccelVald; + uint8_t zAccelVald; + + int32_t xAngRate; + int32_t yAngRate; + int32_t zAngRate; + + int32_t xAccel; + int32_t yAccel; + int32_t zAccel; + + // The array size is based on testing directly on M8U and F9R + uint32_t rawData; + uint32_t rawDataType; + uint32_t rawTStamp; + + uint32_t data[DEF_NUM_SENS]; + uint32_t dataType[DEF_NUM_SENS]; + uint32_t dataTStamp[DEF_NUM_SENS]; + } imuMeas; + + struct indivImuData + { + + uint8_t numSens; + + uint8_t senType; + bool isUsed; + bool isReady; + uint8_t calibStatus; + uint8_t timeStatus; + + uint8_t freq; // Hz + + bool badMeas; + bool badTag; + bool missMeas; + bool noisyMeas; + } ubloxSen; + + struct vehicleAttitude + { + // All values in degrees + int32_t roll; + int32_t pitch; + int32_t heading; + uint32_t accRoll; + uint32_t accPitch; + uint32_t accHeading; + } vehAtt; + +private: + //Depending on the sentence type the processor will load characters into different arrays + enum SentenceTypes + { + NONE = 0, + NMEA, + UBX, + RTCM + } currentSentence = NONE; + + //Depending on the ubx binary response class, store binary responses into different places + enum classTypes + { + CLASS_NONE = 0, + CLASS_ACK, + CLASS_NOT_AN_ACK + } ubxFrameClass = CLASS_NONE; + + enum commTypes + { + COMM_TYPE_I2C = 0, + COMM_TYPE_SERIAL, + COMM_TYPE_SPI + } commType = COMM_TYPE_I2C; //Controls which port we look to for incoming bytes + + //Functions + bool checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass = 255, uint8_t requestedID = 255); //Checks module with user selected commType + uint32_t extractLong(uint8_t spotToStart); //Combine four bytes from payload into long + uint16_t extractInt(uint8_t spotToStart); //Combine two bytes from payload into int + uint8_t extractByte(uint8_t spotToStart); //Get byte from payload + int8_t extractSignedChar(uint8_t spotToStart); //Get signed 8-bit value from payload + void addToChecksum(uint8_t incoming); //Given an incoming byte, adjust rollingChecksumA/B + + //Variables + struct device *_gpio_dev; // GPIO device + struct device *_i2cPort; //The generic connection to user's chosen I2C hardware + //Stream *_serialPort; //The generic connection to user's chosen Serial hardware + bool _nmeaOutputPort = false; //The user can assign an output port to print NMEA sentences if they wish - ported to print to console, converted to flag + //Stream *_debugSerial; //The stream to send debug messages to if enabled + + uint8_t _gpsI2Caddress = 0x42; //Default 7-bit unshifted address of the ublox 6/7/8/M8/F9 series + //This can be changed using the ublox configuration software + + bool _printDebug = false; //Flag to print the serial commands we are sending to the Serial port for debug + bool _printLimitedDebug = false; //Flag to print limited debug messages. Useful for I2C debugging or high navigation rates + + //The packet buffers + //These are pointed at from within the ubxPacket + uint8_t payloadAck[2]; // Holds the requested ACK/NACK + uint8_t payloadCfg[MAX_PAYLOAD_SIZE]; // Holds the requested data packet + uint8_t payloadBuf[2]; // Temporary buffer used to screen incoming packets or dump unrequested packets + + //Init the packet structures and init them with pointers to the payloadAck, payloadCfg and payloadBuf arrays + ubxPacket packetAck = {0, 0, 0, 0, 0, payloadAck, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetCfg = {0, 0, 0, 0, 0, payloadCfg, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetBuf = {0, 0, 0, 0, 0, payloadBuf, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + + //Flag if this packet is unrequested (and so should be ignored and not copied into packetCfg or packetAck) + bool ignoreThisPayload = false; + + //Identify which buffer is in use + //Data is stored in packetBuf until the requested class and ID can be validated + //If a match is seen, data is diverted into packetAck or packetCfg + sfe_ublox_packet_buffer_e activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + + //Limit checking of new data to every X ms + //If we are expecting an update every X Hz then we should check every half that amount of time + //Otherwise we may block ourselves from seeing new data + uint8_t i2cPollingWait = 100; //Default to 100ms. Adjusted when user calls setNavigationFrequency() + + unsigned long lastCheck = 0; + bool autoPVT = false; //Whether autoPVT is enabled or not + bool autoPVTImplicitUpdate = true; // Whether autoPVT is triggered by accessing stale data (=true) or by a call to checkUblox (=false) + uint16_t ubxFrameCounter; //It counts all UBX frame. [Fixed header(2bytes), CLS(1byte), ID(1byte), length(2bytes), payload(x bytes), checksums(2bytes)] + + uint8_t rollingChecksumA; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + uint8_t rollingChecksumB; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + + //Create bit field for staleness of each datum in PVT we want to monitor + //moduleQueried.latitude goes true each time we call getPVT() + //This reduces the number of times we have to call getPVT as this can take up to ~1s per read + //depending on update rate + struct + { + uint32_t gpsiTOW : 1; + uint32_t gpsYear : 1; + uint32_t gpsMonth : 1; + uint32_t gpsDay : 1; + uint32_t gpsHour : 1; + uint32_t gpsMinute : 1; + uint32_t gpsSecond : 1; + uint32_t gpsDateValid : 1; + uint32_t gpsTimeValid : 1; + uint32_t gpsNanosecond : 1; + + uint32_t all : 1; + uint32_t longitude : 1; + uint32_t latitude : 1; + uint32_t altitude : 1; + uint32_t altitudeMSL : 1; + uint32_t SIV : 1; + uint32_t fixType : 1; + uint32_t carrierSolution : 1; + uint32_t groundSpeed : 1; + uint32_t headingOfMotion : 1; + uint32_t pDOP : 1; + uint32_t versionNumber : 1; + } moduleQueried; + + struct + { + uint16_t all : 1; + uint16_t timeOfWeek : 1; + uint16_t highResLatitude : 1; + uint16_t highResLongitude : 1; + uint16_t elipsoid : 1; + uint16_t meanSeaLevel : 1; + uint16_t geoidSeparation : 1; // Redundant but kept for backward-compatibility + uint16_t horizontalAccuracy : 1; + uint16_t verticalAccuracy : 1; + uint16_t elipsoidHp : 1; + uint16_t meanSeaLevelHp : 1; + uint16_t highResLatitudeHp : 1; + uint16_t highResLongitudeHp : 1; + } highResModuleQueried; + + uint16_t rtcmLen = 0; +}; + +#endif //SPARKFUN_UBLOX_ZEPHYR_LIBRARY_H diff --git a/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/main.c b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/main.c new file mode 100644 index 0000000..2945c21 --- /dev/null +++ b/examples/Zephyr/Example1_GetPositionAndTime_Zephyr/src/main.c @@ -0,0 +1,102 @@ +/* + Reading lat, long and UTC time via UBX binary commands + By: Nathan Seidle + SparkFun Electronics + Date: August 22nd, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example reads the NMEA setences from the u-blox module over I2c and outputs + them to the serial port + + Open the serial monitor at 115200 baud to see the output + I2C clock speed: 100 kHz + + Ported to Zephyr by Vid Rajtmajer , www.irnas.eu + + Development environment specifics: NCS v1.0.3 release + + To build: west build -b -p Can also read NMEA sentences over I2C with check_ublox function + To flash: west flash --erase +*/ +#include +#include +#include +#include +#include + +#include "SparkFun_Ublox_Zephyr_Interface.h" + + +#define I2C_DEV "I2C_0" + +struct device *gpio_dev; +struct device *i2c_dev; +/* I2C pins used are defaults for I2C_0 on nrf52840 + SDA: 26 + SCL: 27 +*/ + +uint8_t init_gpio(void) { + const char* gpioName = "GPIO_0"; + gpio_dev = device_get_binding(gpioName); + if (gpio_dev == NULL) { + printk("Error: Could not get %s device\n", gpioName); + return -EIO; + } + int err = set_gpio_dev(gpio_dev, true); // set GPIO_0 device and enable debugging + if (err) { + return -EIO; + } + return 0; +} + +uint8_t init_i2c(void) { + i2c_dev = device_get_binding(I2C_DEV); + if (!i2c_dev) + { + printk("I2C_0 error\n"); + return -1; + } + else + { + printk("I2C_0 Init OK\n"); + return 0; + } +} + +uint8_t init_gps(void) { + if (gps_begin(i2c_dev) != 0) + { + printk("u-blox GPS init error!\n"); + return -1; + } + return 0; +} + + +void main(void) { + printk("Zephyr u-blox example\n"); + + int err; + err = init_gpio(); + if (err) { + return; + } + err = init_i2c(); + if (err) { + return; + } + + err = init_gps(); + if (err) { + return; + } + + while(1) { + //check_ublox(); // See if new data is available. Process bytes as they come in. + get_position(); + get_datetime(); + k_msleep(250); // Don't pound too hard on the I2C bus + } +} \ No newline at end of file diff --git a/img/Contributing.JPG b/img/Contributing.JPG new file mode 100644 index 0000000000000000000000000000000000000000..39ebf68b048682d490c560ab5cd4fc67656ae778 GIT binary patch literal 62131 zcmeFZ2{hFI|1UnG5R$TmsO*HuQr1*L6QZmWlVlxZl6{*gLPSCcA%wC`*0BteJ$v@d zU@Y0U8Os=Eap!Y?_x$et-gEx<|G)op{^$JfIp3Fgoim5m`}KZ3U$5u#cs&=#e~xDX zXYb!Lz6W4pVgfv6`~Z$|fIEN_OicfL{##={$^1_{#lpgTlJyiT>pz{1or9f??KB%J z>uIjjr#Uzojg|cjHy7ubf4=`S$v=<(^AzL5$;Qg|&xrrMcKj2-!^ZTOxr~|V3g83} z6EhFfaXUZ^0AM=FSnWRx{%?)x1T)LYQ;a2@=3oq{Im=i+GxG_?Vo$QLFh++l{tjT_ zIeG4~vi_;_=8mjaym?h#C+4z=->zukvlt>sTz~d5oc%PvfZzooNhxU=SvgfTb&VT0 zHSgRtxMyf&eE-p7ODk&|TRW%c&MvNQ?jAnAe*OW0LBSDkBBP?;#>6Hie@sbD`;?xM zm;be(u;|xP(np#v{eM4(odq-#2ukN1VkO~we^T~8(Z$1{>jdKrSXlp|i|IrFqciicoV=`j>YTngtE2b%D=M$q zcyA}>R~m)+OlZfzDEv9;I+k4mtV>f_{jJ(%1j zpIY)6x7=Xu@(K;0_^tTw1iptc!~2gq(}aYR=Qy|^meEh1cN#Q+f8{*CPU?<#lFoPf zmJtxfAAg8F1|*xAlp`BXWmK*m!NqdIHa4qtJMz$jBtzPFUv8YhEIexgdIJA&Bq_+H za#Xedwvrs>Ui0g|0RH46LbvdD*3qZ$xq_V^=gn_)T#%Ljk{2s?kJsD)@GsqZ;22Qt z+GWN|<|a{SS=xLB9y!j0!T0k-c}OkTC6Yb`y_%bfKZuLE`)8}`z_YO`>eqZ0MIqFO zXx0wHX^|2M*;U11Rc&;pF;y+-sfmr>T_mR+C`~t`x-*^!}=e! zDbJD?3+rN8g#Vn)_boPV`N;~%mi>w|2Q}%^cpknfk`&w;w8_h_$=Ot|=#C4D6&6ju z@Ld*~%afF30XoTReycTB=F5rygF1Ht2C_OkbPSj+!Bj@ltJPPAv4KTqBa6wj2X>{k zsY{nXKQ$thuA#$&n1`ENkk@IYde67n=#vO`NkYQAxh5n2d3lX7| z`!$vh718+cYetvC^$9VUr(#&{_G`Ug)-kn8apj>rnwdcgd-#*?cK#U2X5}i1d4Njy z`^v9!e|JAGOMploIhG;*RbF;m+y3J?&^Kd6IGoh5R#up$0MVh#Hx~_L%kTIp{Sly_ zRxVk+jF=M~)3!$o7 zZwtxwu5;eSIO=Vams=ODA2b2YlqG8A<-YQ-v~iq}>pbgpq1W2-7;y3=`TQJgGKbFA zO?3T9=NO(JqH4??16aWN?w)Z^b|SN;2W#ZU-)BOFib!~8fl#D96-233L-BP zTw5|eqk;nxHnsJ6l~*)KWuE;|Unk)DXM01mzof9LJ`@?)Fcjzr}VRAp=T?_mo zfnM-E^yU~`p#S7VxL*_3!>PcRHjX>ymJ*yvtwZNU5^u<@y?F8BE|>}7suZWoL!`a0 zAxbZ7F1K^&C<*|>#2*k+a-vJipFfvfn)VF(n*K_nxTN#-4}X!<>!d`|$oGp%vU-n6 zc0nPvlcC1bjXrh2KaUFcJ!JKy1A-ts0n^w)XGHojz!~``vn8hnwRxUmg=p;#Kj?4} zs3GTS^JfcZkZe?cEo+9QC#@OHKi@5obFx(7=XoP}BJEk22A%!f0h}2{UL^eR$SC|i zH~0p_5Y3^UcMlPSO6qiWti0r`+HEv zpA3I&pct#s1vd3q16T1NH%CN^VJKoY=QNe2c1RlnTZn%qCJa%};LrRLczXwA0&Y+9 zqCcB7y!x`;Lc?aN=RpNBwVcBzf>`u(n`sSEnMUQEm~}3Sq_a-L2jPO`RJDG2n-T8# zD`QKMjDEHX!+`BJFMXsF*jK5E1R zU~mjzquP08s@05rPMtAnLOLcm z?F)bMRF#QB`U;#owE~t42T8quNNRz2L_siTaN(*6D5F~UWH*yGn@SrNJewQ z!t=(QcFnQrp60R={1hr(fts|5TRwbk3xC3`aEeWYJkdEwmWXDs8rGHMGz#aZ-li4$ z?Qxv6sk^OgKd$m)-h892q$>oAmGT#>(V%|aCTSl7kY)nMfYnNDDZM>s zCs7Z_N^gS;9Ro&Gtc$6SXeo#}XZi?+wORZa01JCZ=hN0~2T$Uk?dsLQ@lf6iyP++M zL2!rdI!aYS+J*1BY>X2j6zRDk<8*cDQ*?$+imV7JbO?*sa4;iT|Jmp$$6sd%8(hQH z1G*Y9UU=OYLahXASQ^7mGj_{VO`zMvzlMGvxNrF37|`N(vW0QVc2pomHqJgM!!YSN z>r=#Lh(cqbhdZI5dC)!KwE=iM&%*0wlP$QU3U<}l$^5@1{TCn>z!ISlREKj zS2RmX{GC}d+B{5gyWny4_^~4&+oGN3U#m@r){w)=RaY-`nSCfFCk=mAm#3&Lht(!9 zVHUR0O|c|H77Am6+(WqI)Cyn!C7!=JR5Kb}8u94kl!3R(sq@tk|hlQt>%wMEM@}Sz;BET((0lgEstTqXZiIC^m!_vl7cAsUZrZD?bkyEY5|?>) zQ?Ro#!%Jh#!EBgVIRcR$^4yjzwqAZ?7neHF9gH<@3JA8h-wx4HC+NM=s}hJtIK#*a z$AD;GCDz_zEZJ^(?o6(aQ{EIF7y!)^+XVUrZayd-6uEi}A@4u-jB<teBS{}| z&D`7D(eFBUWp$?h<@xkG=Wg#Y4JSYTUb`LmIG##tgx4O};c8zyWTY56d)eN$+99dK zt!$8dFZ z5AwAI?#{M52$JtLEVt%NVo0^a7&m+6OU*yXF(qo~w2Y=571ij6A3TzRYIh@@1JSj& zkUL*G!n!G{43W7;RU+t-jLEMF5b&5+C~QtoY(2QP;HB5IK(8yP=BIZRE~gqM*|a9$ zQ#t!Io(lC6G&Yopy{+n2y26>HVxrpitGy*7$5(-RW(n<3w^t`oVGpZLY27+y?j@<6 z^>g%_yZ-}&1#GGyzd*t3pr&)WgS)fB}n~zEk8-rj}UXD5SvITCe zQk&>?auosDi5JvmE_(9}mD8pxoB=E(4AhSBr;-*P7(0b@?AXZoofAaYYB61!VBmum z(`F9(AcNTuvys*g@?@~1BQg-w;w{n4fk|Cjn}-T)+BH?$w-?UE{d#G4UF7qZhjjoZ zFf=rbe3F>cC3emfN!VQ&q{?reo9|aBR|O58#6l7!icP&PrTmRq_z-?duv^qx>*md6 z9GyL8mSNMMBF=}h)Q{~qPHS}FA&p~d-R)qnx=}Ah`1nDiCBLMjcTEc4?N_;BtGDyw zU{K!rq&3>o#*vP&aczc`>Cw*KZx6oJ0aJ?oUS+J=z;HgaNA&jm3V{Zp(mt*!I7 z?!`dR143#-m^Uro+x=OocAhe-`{fY)5l#K(ia}?_J1a*6UMrVe z1Kx*B|3~#8a65*VoY0vg1ci%{1MFa@+xQb`^(7E~((fs-1oURCphBu|pk-E)#g4Ja zE4h>G^=)|&{CT9FtBtnS7mv!ou6h6Z`FHD|REtoRBXdn34cW7jT)3c~>c!1so=!~q z%S+ERyE2<+{Qarh2fvA_M_NjN239+)_Z-5o+3`#X#j*H`dZDoy_-1vY*IPVnbnat^YX^B~;arB4@t)#li ztYe&HJh#oO80}M1`zl)^a5E~6_N5>ng4jO>xD9@e=l*ngCobR3=K~N&%D6^eMeBLb znu*vt*yF%Y+=izL!VTt`-K;}jAPT(V|J-;kAkXI9{j23G?N7b$F+dX0qE#?C?bbTs zAA+1fbl4u2=$NnZT1SM6(kfw8FyZ=7>`cXM&e`tyZ6VNMYOvI&tEe@(52aO4S{=4~ zVyEwLlgSwx-5e+%8JG>(oha8kpY|GBvo51l_UvOX?|7Q|$%e}83qrDA5+DEA6;HW% zX1M2mR>5nQsrfm$*DGID>+DcvT5di$a-lJS3m<6Kk$rY-#Kgn_dl9O@irciib0JBu zYEXSRzFGxXG0|t1mK+C*6{|g!N$Bd(l^W5TJ4dW-V&F&TIH5vRKmit~(-c`+BF^q3 zL9EWaYicWQmCSru(82?YS;tJf!p8&ts6jhmG|+YfDx^fT-XO^OJpvLY(1Oi60x_fF7HgEN6k7jHHoeq#QX2WP!R zoP@Imf=B@bIWnXL3nuBtqSCDAahunlJcFdq1m*aa_wNG5Yp=il3;>)cARszG+`@rc z78B4*O+YU7t)mg&*z|h!WMj?*QNi%Y`LM4ai~zGPb+9OWt!X0h0{Z&(Ku~AKsgwnv zdgE_D1-tPX88vkfqFVKIj8?x$w%xC`{&=4D2j-vo6m&!y6~aU@@wGdrYTs?q_wy-8 zO!@||bv8MGlsKgQs326cp|)nsTvr;aw)9aV_8N55P8e+#t!0*~yt94SRFBl5eW2eU z=g&t~&3&qwezZBXCU$18-+wkNLlE6NSuSlXdC2P|7ptD|(dm_3Jy@&o0K6!D45-EY z)NT7#YelsKf8UejTNWDJR48=A@cIpMHou!^7bn!j*`=aN-)RQ!`W|MKd|5ap*u<7QP?_Q0a_-eywT@mv8r8c{ywH_^{GgE-RJ8NljSv zUdh1(`tpL_84v%VNuXzboJEGUFU>9+7HR<&6aHQGFOZD-?ox=Uc_7rJ?0Y?1fvHbJiU z@)bx4T7jJp!! zqGklQL?5SGC21SJ{O|*x#!*8mD5=Dob-^-MQ2t?2W2jxK2Bo1Xas9FN2MB5hmS$2;V_%f$E4r$OCh}{7T(I@N>Z?rAvvsA>`$oMB_EN=@Ylzi8LJ`hKi4_D`(4x7ebV>4E6 zMap0i0a3Rnn#JKGsfaTU16yXCfhCU#9j%k-8S#quqpP(*Ke;Ypy>v~}H4Sm)DA^Mw zC(Kw_lKoNO2^i*!js_i5f@})VQHdp*y~Wf-MwX`&PY6~cMcqjbCq81=GUz$5qL1O; z7tyUZ_~-BKUP$u$+xpnC^6{qBV8SuLb5m??J4V~wRU;A&2`eIBS;OMng*plf8f{jM zwIB9XDF=<)np-@5a!2I-Yr)EsGEdCC#@EW5R40hw;4@|ZQzSY0F1R>+606r%P2^jq zUYWT*PSu;cN$)6L4|cApPW4CG+=<&mDqvf*LNmp|P@Q82Y6$_P{uG{Eo=#&FrNcDk6?|U)F~%GRsJu zJFvu?ty7WMHA;Bn;V}Tb>_zhbv#%-NRj%jRbcGhm5I+Jcj%x2Sk2%8t4(3j<0vt&G zozBqrU7`8FM2SUg7VP8I8yg8`pz8e z%$leOAtzqjGGJbTEA(E2L)OVW2b(`A4vCqtcJosTDLh}}#rdGcw=0A+T{kVilB+uH zyprd#INIUzu?MCj`yugnOhJ!pM--IOJE}*n?si;1#q+D(KqT_E$g9AjZh71QKIc$v zEo1sxFpLJJL$)96pO()eCoVK%m{$EqY{=kt2Ze@)oNkbg2#Eb5)I~*Ws-M#+!b{hk zN%AAWxLnMPhV(_AC;POe=?W7j-f5c`li&B4==*{MH?K60%*C3?xzs`4z5NpRb@Cl7 z19$`ay0wa(j^UsiW0IN2zaeGJunR_JXUXB?>uodgBOBV6okVlzxvaKk*h=@@X8P>E zeYk+02z&G0$SE7=X~7tHf1OTRKL#*UCkn;}9rMs74{6^9;^&$amnc7V z)hoyLHy^s&nZ`(D*rG?La5aPJA|=7l+d=M^50|a_o}ZNX20aGEi1GR9jpm<3p4?_<`C$(Q zl3ED`QPGOIBAWf1bwkxn4oncwO5SQ$s zKz2=nOS~R{RUc!@6Lyl|@0W{*o*1^`yc5y5z8d_i_PP!l z*KW;58ZqJM>=*goo|%&Wv`6LZA;URU1J9l1j@9EU+lR7tWM3v6MED6?Cv88dsHNz> zeujmX?r2B;eQ!+n&%XU3X3{b zf`Q zo7Y`bmyFjl z=QzP`#dVAdZZI^p!gYtnq0&9G(}8UcV>R+Ow~?yrHXHK|L*n7f8&&TjvzM;*i_9&^ zGksiAxpgVm(V#YIoo-DjD2wP3CY0 zDs|~9G&KGQvn})ertd)U8-t6!yVjX#?*RW7UM;`-9 zf>-M|My)w!8PS(E%z3{}l`caKlza&}SnnL}Nww}16VII>M;qlcl~HR2_pa&R6f9c7 zi|&Q_kV3#Q<{%GXuI^hb;q`)kz~8I)EE?~8#8>;`;|{j(HjOSSq#f2AcFw&3S ztK_c}vqQKc2ZOn7oP68Cga85;EFFs$cIA%#OEjrjLb;za_~u>#-F1m~Of}~DV9euI zXtXHmK~4X~jiMp<;0hRQuUU)lGFBI}WJT%#86qFyO=O!fA!VPieb$63V*W z%qD_z8kRSVwLoApqRX)GNVgAYZ@rH?Eb|`=Ff227u)x%0rx&_q{LMIsLR-&ex!ws; zrMF>X$3e7PGdE)gH$DoyqS9SRaEb1eMC33j_r5JHe-mZ)7$woUBq848-EktA6nJ1{KWO}w1 z@niPIQL6lW&dGJmQ=x*I?7FMHU=WN4omf-WzPh7z$=AgitgQP)AA!Xg)sB0qHYv5yWr_Sh8u~k9T%C&Uc#1tr#Y&{E*<`!t$2dj@-V4gsn>q2W zJ~lkH5dlK%0JraW;En;HW5ABmmTQT*gZmseHu(+_UN%Qlb;mB{6t6dAk>Vxer_9-_ zOB!$3x#!i%iYrY$w9Iqoi^yel@AuO?1DCQ1HLVzP{SmvxJFEt25_No0Tu@m1v2&x& zY3JM-_Li=PvN%u>c(JfK1Btd*T83pe1h3E+^77CltvS`z4jLKr%RoXvJWN|X@kzHk zvIFW#vdfL|Ukq2i*`PIP>*Vs<;=zeh%fw6}3hDPSS|cwoX`x+|hlq*xdOOK{-MA5UpF$(%*<>cOEG|2STJQl?yZPP*%cuIxIN zo}d>^y)$PY(gtNhkNAB=?V`W`uD+g+>sK`s-bf5ge&P1{n&5?^c_da!>13$U$es?c z6|R69QPVNKD9X*FYK57E23!qUNDxI}U-Z9id#et+Um33z69Bw_b*|D341bT}+lU(eJ z|2+4j9TwO|Y92vLhm;;V@DOXOipW-MMMTkjCkT{;;NE^~+AY_u3A~!v+ACgA?W0nn zB>dJQwU};9I|hs)YxacSS$MByf_+f?qx(Jbtep%`;T|j+!(C1_|8CEdZ;N}En#zXt zPXP(DxV@@=|M#2aLm)rkgi-lv3aKpfUS;(=bB6zezpY)fX102`nqA;Q>VYK;242J$1>`mA zc*X9pGjw*A5Uw@VI$;hei14%zj3b+ZxeIF08Cj_Xf6?o_jjq>Dx{Zn4udH@-F?4-A z(`&{E5TVYzn}=FIc#Rfc$WkBQw$~MC#x&Q|yvN@itF-i7?q~_PisJ=(Ro$|CEb0|- za>Z6%tw@qfAcLwlPE%+aL;8gle43j_$_8!h%^Zx}E8WVXqRaIsb(A(d(4AcA{zlYZkA?z06}k;4h0?nQ8dyXKr|Y z-$70fU2fw0OxLotbMAIt7;nBJlJjPdzQmFm#|y8Ad{GaP5^Qmg7N1?xfn9&iQ z_8r@w>DRU2*oJt3I)_XON&b`D$kYiFjY{{ztpvk7w5tKFMs<)U5JoW_Y?;x>5bn3K z=f0QFU1=M7Cxml?2gp^KnOH47>Fgsxe+)Vm@cFD3{!I>FS+fE;cx_8Z>(LOb8aL^? zX$TyBb9>jME`g;>I$0v|bH3q~kBKQscQ5Te0xPr+y-Tsd4h&izI#5<-x>xppzvwj2 zG=GPOiY=oIog0m7sx&j?`jj-3YJ;pac05>TE;kdLP0qRXIYLd z^E0d%asZaW8BX-HA@@`aSM#Mcx@3Z>PcM?v>g!Ha5O?^k?5rez5sG{r5wv z6HzqxRN7q5xvt#FCk}Q7^TG)g@tUL%U^0|}?5EUvexy8S?tjh9_9^_{6v07<_QkJ^ zl~j!$+Gj(XGlFAGf%0v4+BFOFy&6D&9K>lwDo+mIA3Wow+FlFOtp0I zb2gZ~ulzCqQe>jEtx=?SFs)#C6{=>2y|-y^c=rIR)M!saJz^k6Swg}l2vWZG&b@@J z(Mdm(x@(+cH=;necmzeATQXpO5)-c>uGjHd4 zZnnVl?LwCH!u1jy=)%4Y`kk?YgUno#`yKwzd0jh3TesLIZ~GP{IY~?Ni6#^W44d{$ zG=^4MS&yl1y1LuGzqo@1ZSE{K?j7W9*JRYL94W@`{6e_z2F=st^-%(D ziyCHAV{IL%_w^3mH2tm7+6NXDAjHj&m#4m@JX-T>d)qpe3-c&Ls2&5P4+8AUxK^zr z?~Q7L^4E}uVbix`Wy*b?{?{zBe=^Db?>qBgvn*A3OdGfmFW=G3N91+5K)ja*QOq8; zU&Uk9h2p=)l{?nGGUknjeDif%icdkgzO0f(vbI1&Kt$JYHHQ8qXLmgsLraE)1xWw# z^>K#0MDY)LRdflPiMOA<_RPT;sJ`|1tiZ5aVJ0vIN)M(ZkS~H8G3!Ui0L5R7t1=@{ z`y(oG78ds?FnrL2Y#&erEbT87UlV1CMVtiz<O6T!5VomRNs ztPS<&fJF&aqsINMu;Jv!pWf@k*KxOhuq!N5?kceb4l&(Rk&$6*QD_Y5z*j)i-lFXn zrtNTcVK>MqQ=@9~xjw-?;n^Rjnr?}}o_UrEO7}*1!ky3yxNk+WJ$4wqF2s6~zJbi< zA{k2aTl3BqBEP_J(og8mg&0d>G)?0?Q|&(`ZRzuHQtrWBJ!?+|dF&)iGH^K2exi;0 z&2AtL{N7%#<%kQNDs;{bR^))4Zi=2vz)8B}`rPtTeNX>r>s@>;C%;lG>p(rASWy`c zcN|_Z0^#hBS%B>v?rTfWCczeVZ>4y$?UI`y8&!~-xc++_2H_wjm+Y@Z^M;Tuo10&2s-Fq0JIoIE)A>Cg0% zlj{xK!F$NKOwpjUA|u660wb%3Va%Cec*GD$)dcNkTJ}Z_o-gzsdK4+NZc2_Xuv-N~L0Zn2gC zsMTBMLGi^hGfw)iqv-HG@Cin?6gkr@;xWOYBIx@i=!JpQOO3`(sRP*2C-R`XF;bVo zH`_yASavbvR*FeUjQ9_d3{yaDbuU^~Ea!;Oxz>&W=lup*J&pw4LbY{@$r=oMjS6YH zx_0(Sx`I@#a<^cH6fP!4>xt+!wgE^F{bs1pI9iXuNMpEP(=uYl^PV`1_k%W-4tFRQ+Ni+gV|8pL;CTQJ3>-42!DXp6MhvyJj0q#T%8Ip=@#vlw z0~}f)T6M8Ee@`;giDlUH9HCu$$`(O71Ca1WT4t!zd`@)!Eh2Y2hwwtauzbRqHGcR- zio$ft^w$yz4GmDa1Ta-f^-RUr(=A_r+V?!d$`th-)*r3VA&=?SBR`sZ5)6*8$AGY! z?NOpm2*@JMe;0pOeO40}ssx_9S=J4VYc``^Eql1;fcS$# zxVp@YU(5_`SweKx`lL|ET^CdW*GcxrfHxzp7y;G6&A}=}>xeV#`Ud0nR!REP;y%(R z3OkCYJ=%7vH7gx5Rxk64G5lKw>keyc$_&FH)VB|FgsE_{&-{h7PRto?;bL;eR&VDK zcBa3mdsgR`oy@aNZN+Wv0&AdvTO3Vlssa(Z`Z&(M= zTChv52vE5KjY@Hex+DD5D0B4GgZN)Ir!4NAX^vIe>eTCHm>)FCYlT&P})kxw!jh(?B@n>3|gL6Q@GV!XPkN$rJ?+-{*C;V2e@Y2=fbBJa`N21 z?uX7;9lFzRmFM@4Ao}5@J4&xHl|RS|A+wYB8fkgj;6;u%kwlV zbpZTeW`S=}bFh`NJ$Ye6oG|g00fmaE0-te-S;Y3vjTe-66-%P4s<~kbjc>1(BM>Ss zL85vY>d#yM(p(L0qI4u{F$veyEi>x>2Mk| zmfRb*Ka5w<>lU@A0O5nydSaJGJq#nK&mm*AD8q7wHc za_Sf$E5Y+CRGk)AgL&O7M)*wt$5m(P*$t^uvaYN#7>$dTZ#t?ViB8>8qg=JVR&92^ zr{%N>3UWi{Wuxw^yed0+C2$L}yIP~1Rz}9}T5DI+`>e1_$wbjE@YwR00$mcM4-Nf| zE35wDSphWkyXidD!5Oj0WX#MevTO?Z2|NZEj;WQg0&~oMD?&pR6`s{* zI2$CbZK>BMZ=U0exAugda9lim`OArx;Deiae>Fh_56onuV?kz!O_vWXAJX0Prmz^W>;JA zy%1%3us z8qAc!FASCkCJn4n=5}bQb9*$&vMb}IL%FGf7e#~a^Jb3^Ti`w^$NaS9-WmUmc_xqG zrm8cX$gp|f$5AEp9f7y1_6D94bO{qrVoiKqT*bK#ws`Z`@=dlsCqEcA3_V_!I?O)6 z46Ne2ryb}`)5AkA%W4lHMHy8!jkdmqOoYZc#j3e&;k6k#qC-k19-OR7G z>8|eX97by~=liuIvBvi485Z2XPLz){CKA&&n&z`$9dO;``^3f6WX4q_=6b7Nhz5a? zSE;0s3%a2o0RrVz%XLX4w&S%E@0ZkHdsQMp!pa5UYtW_BGnue&Bkt2Wh>PkIJzXEc z^u7lT-0hhPO6}Ic%EZN{rlzl?p=)XPc0BWIZVUHuNgw^_I(dej>GfgBYeFmk{m0JEHQ*_cYB1!SN;!ZT1ZH~B`cAU(SRcv_>II}z_s+A(A zBS7WeF--a=?{Z`w_Ab<{6ah0UoPk)`X$OaXz{=OA1Qgm&*|+%Yo~w+I=xtERmy6Q~ zJp|byTFiJMa1BD*+fdv2elbf7p)fZnfH&A7{3r~aXyyWaqwla5aMCv8_9E^kmhV=7 za3pWCyDdx8a^Jx*K!H1(8b)EU2^=54P*6`O|NecF9BnziInkcYpI|$pU3S}KM_;n) z!JEpIxYF-(>Q1XQ>G#Q1B&kNCVMM6jmpcf;@~z>$R}#29GOsPLU@9GJ*f{(ggkC-1 zEV#G3O#LM+QknB*rHK7Tf#60NEUA*-xLyaG@v@l469W+MXzI$^e7O)Io-s_#{zy4T z(>-7&z;hT!(HskZQ`?g>1HGK^39pPFyuW)4I7ROT6Jj8`JdN~eiXrt%1HG}_lfpk( zgK%#eIkeq9gO}gDF+D)b63`Bw<}II?hOJW%9EWED4%g{0lZ3Oh8me*-QCgpXkMb4t zBZq>5O4@WYi~sz&PdZZ7ZVS<#&9JKSS1VIzG)h-~hA*gsJ1r zYjG2Ks0GB}j$tg8-hGttYF~_NiY(Sqv68fM#6k8anV1>b8*IiG(<;iK^IU=TTYGY=~*^O zXOWYE7NzWPv)+hyHwFSQk)hrsibdBz#NH4PP#Xhx(cC#?^ar;I{e=`M>-uSL)c`i< zSSh#KE6NsZEr2j_gGZk;q5CutA`>xuw`b zEYzgP#(nVQ`wv*Zf+VcYh|g4*W0b_ zvc{uaMvyM`)#^zdB-Oh3VaYf5D&}V!4)5Fkl|=dH0Ghd{Lxl)Y1x5IE*LIDJ@`{jB zvp~=B5hWklln+;`0KB!VI)L)E*nrhVSZYSFTk{<30KZ(`+)&lFhoAK6z>2lffIou~ z?KY*MVzewD$Naad>Iv2(Ru2y4C9zX)Qg1}?FZ~ljB zyxPE72AxB&mcMiK@@*tF4l-2Z)txAYYLu@Qy~i6SYoI2Tb+k&>Lbi)G9$*&fMt`-n z2uKR^f?2G#Bk35RK|g1rW%h$2orv1NKZhh^;0uiitR_@&a&$RCoT#)RrV$Ijfg?9> zKOxk#R1&pC#ZirQgfE%xY!yc3R}s~A%kQ2tS>BEGkFQsV!tdGh=1X5Fe&>gp$yxdU zoOu^9xqSfIaSbQqNgAV}KmvWc?FFRyeEo>uMTqsdm*WIgnpPrY9OAp%Apx+z`+E?! zWb|FeDeLbB3#VBCMKUnjsCHrxd4_r`M2S1FXNIk2f6Vvw_qAlB+KS13ZcnR&NYdXtvin?S-h!s*Ks;z49%MA^w#li7SDz1sX8&6IlfXv8|1TVxX$&P^p zcT(V$%KDnq!>c=>N*(k!5tU@&E@8mW2o#I;g;MG%a^$?bZ@kwrV4gK0H?AC9B z%5$0rl{&^hQ+D#{!8hK#3c51TL{T{gKv%*CDKJJTDs~Cq8A9*M*|+wd3dgoaAww+Ff@ZoOV zWys2!disKp>E48f%iv>+L(-a4piDnzJMS&%= z>M?U1y-lpvXu=F8Xr6<8AH!|n+=41(A1XIdCq0 zg4R#5KZI2}>&VyC#VuFS^MpSH+MDt}O!Pjf%w=pb$AvTg?_WJ@%#crNhQ*Z`%&iR& zU%rHx%pi-xps2(G?M5!suO6+sd)9Jr@PzgEUG6uC+0WN(14%ky@EBUQSZ;F7&To1; z;Z4W;`tr6A=GM(`9$Y^1Pww-*=HwC3YBsfY^}tsp#A`E(BIrEebJSbI`(Y|Q_R7_( zUkH;mjqx{;$Da> zr0B3m*+xYkp0|cJIhUj$!~B|3ms81%B>jMxocXY0z`Ib6`F{B}q3p)OmVz~+N5gp! z(gBCpa1DA$?g_5iM>m8E&>dcku zBw6Nug~EK1Ar6;1Y>CX-8{&Zgd8p`tfs^6Qj$!k^NeBL+70odP0gy(i;iiphD}&MA z6;o>WdUKh=$fTCX7#?2qfqI>mQKQ604H%{R6hj;?)-^VTLtN+`gk_RJ=eM}-MfDr%Ihc*tCqBiwfvZ* z!Q=Bv{l!C(&$GcMe4lq8JT5H0>5&v$dB58usdBjUL#xG`C+CJux67utC;Euf=ShA< zhniyCGEtnS5S2*dqK z&z#4fqFx(TyXf&ZC(2HC#B&>ZiK6qTo}XaAw{ebDie3Dg7xOa9O{<<%t}S0H49=!Q z@&+)atTbQPT{6#D@@oMqWb1k5hGwOA6S5Ho2W{bZ47YXz2uMMAudWg~>wBOb;yih< zRWpt*>gKGOf{8FGuVAU@Z~9x&C=k8j;x?}O(KFG8^`~XnyLI@mBqq=C&gdS?^ zYg2u;d_qM5GMTw&kW={KhtO|5UFaQ(hNubD{K=osVV-xLgyQWqDvc$|xAu$``rYQf z4?|lI{F`tv1LqQ05x#MhQT@B1`M+0~TNY5z6>`ZH^6FOC)|NyZ{Qj+Pm^rZs$^nLa zLE{*}cT3<~u^u6zg+{?sIT=B)*nBz!P9?DHA^%Wn{^daMWA2!aZX zE*B4IGB9VjE#TbSGYpP?j;C6R{mWdpl*}I^{>RrDQpE`fI0JGsOEEO_x8EG2mSSYX zSvqZ-e#+z+(7~9EjVgv`_{e^CW5~s-|9<_yH~&AG@!zcXZ^8Sw+WEKA{r}qJ@R}Q) zh#eSHTGR+1KgCl!sA7Ej?-z;JuScX=kp=ps5abct^f3U>gCO#5$Y=6e=}m57R>T92 z0ilfA@OJZm8E|;#6O~cN7x59Z^zIli<#P=19y^4Ai*g9w8!x+#0jFVo^%)EFJApH2 z8M(9{5GKVkjL|RhQ_aXvvhDlE8R=Ur13#k!sBbj49)UkCec~>6t!(poxeEe;-J}=) z90T;Vbr!hy!7UrQVH;t{%H@Nb^|1cfkGk^r$CsK1NT~F~Xaj_qm}|o^;7dH7Mie*( z@EHEfimh|$d5nVRV*oXcQRt#8PnV+53-k`b$AG$=J{>-qc3Ad0JzN8E&<>^={7(}d zF{m(H&Fx=^G6ECvV&H@f{aELPJCf!uTH@ z5-fB^K`Gmh4sNPcgSw!TJUxqBo9H0wn2d0J_We#yW_nRg!%@}#02|Sa7BDx}%eN19 z+{l-~&6m;tp5Y%I_V0c8 z4_*H6efal2{ExQ%-~8~u!4KvJMt$kWfWJ<4j7WqVVo%n?BMNaSxTUv_6{DTD%c#C2 zgUn+12)=-85OD}+M$kK%{HU!~vu?u6OQA4(XLxIst2HkZA~^iV>-@A)hq(VCZ|<*~ zrI`1KT2um^mFmj#geo(x#trxrs0lmg5kN|NDEyZ5r9LT~qal}nWkcw4x=6#1R#~Z! z!qVwt3XFujUpc$?5L+4y6mz6Ei^Ck2;tyCD5G?XB17OAMmmto$-cyv|yE<3=>{}N{stMqriBPOby;lEDwU)X!^u%^0hUlbKZ zML?t}D3LDG6e$WwM5T#z=|YquB?3xE0uiKF>C&ZlB3){vD~L#!Py(TsKtc(D6wiA1 z{r27Ze*3uhbMHC#InRB*e<5qlHOKsoF-IR)s1pr<`lglrlLmhIC(V2!>|(en&A{LO zngD+eg!#V<>HMTYzJ=p^zvsNZ0Vs4%@D-LKIQlb~xCok*1@E330dFmU$SyTEOlDZ{ z!SVBGrnXYMR|OemtDa`f5qOIuly_`y+oRM?xkD?-!5hGxOdp*Vh_(-&7lZ zcuGuM&n9(7=b^r^o>J5&0pk}xZVDzpz&}f_SVLt5CFMW$_RZ;i>mc*}F@M|-HGEl7 z+6ND5@%S& zAFkX{@LZ_dH^%ZNqCMtE2vu8kj)@Z8C`(Pg2LX<`{^|xLiF%b8h)XMipmQw*)I~qD zv>ikT)8BrZg|{UUK!{hn+v*v$O0Scohjw^uWs zlnT>rt~$wk!P^>jGVCVmLT|8hqKj8W0rCzw;%;?vC)j>z?AzwH=&S}PsR(^{qk(%q z+%w$z8qrGE=B7<;LV*0G;EImZlkJu7QK3hRF1>z#8vHAqVt%ERDXY#2_-{DK{S5>%3@k#5O{4z%GWBiugn?E-6 zkpWFy=WCj!96ViQP0Xp4dyht?&1E%*P1P(pJUxZa9AApH3iGU_PmUdAwOz8+kyLwc zh5Yqvf8)Kfi8aH+uye zq*-$g`%3jg{iuNyR)s5P%#({8@s>Ge(`I{NLTK7ID&Pe)k(f-fC36$6W4^e7&clp| z(7D?#wCV!GwOK3^#^&7>vLaG?y%k3;2B`9;4Nhzdlr+jCe+9~&7Vs#M>$nnozsN1&ZRo55Etos0^$?^uPog!x$08eE#X@;zTVgJJg;!7a~N zpTep9{^m*JT*uf`h8-fu-n7wryr=t{Nc|mux_2)8r1={U_>Zdt^7SJiZ0&UKoCS(0 zOw+kALW`DVWlbjM-D$al3(MA}CopL^Iq7Cb)l#Pf+il$g!>E5W8+{z}+E1RSjR}cT z01kuRNP}Y4wv%6naJrlXu4>|-IblP8`K!~ZyPw!Dzf}=7ha^`3Y1uQa+cr15r@oDQ zSUB}hcu&Z#q>SqtgU(Q|*ul@k_}H7~`nIr_uG$yYtCLfBrf|Ku&oZA&rqm+kp3L*{ zornoWvr2&@S}*#Vb&R7?oG?$2UBiur38LmhY1mPGdCs4XF5o2gk@+Eq z-LKtC-=`a;te|U=Z)&%=C4wC>_zz<-Nv1Q-wCg%2^;GSKOfF%Msxn)@;D2^%TeiO~ z*VCFyqy@+cuYxhn``*B)29`-rvoB*)4GYhvqTpilJ1hkrAJ52n9<43PMIfPd?e`Wf#T?S?$6 z^i;6ao$zR6Yj#*HCShx7>LH0&JK>^}Rb={+sqpdK{J-iC4faFK;E=I@^l~k+dqs%} z+x>{BnVi(T+Y3AJWFHo-R1bJGm+L; zgO8a{lp;s-o3EOr{V!*&qm-gXsuOiSiF=+sd7_KeP&-QDWjH zb$dp8W>AU-RUbi{(tTJIIE#1I$Ej;#Px&dVpiVxE!S2t+tGQkVdt_x zMjhfI6IHHok2b*b1tTM4-7(X@b&_QOUQ;b43CQ-?=y_@jpwoQMFD?N%=~w0P`Upw}Q5x3q*^c1$*v%hm(HOn*v!9=4G_|&uX&{4J zFO4W%_sw~1CwIF$szg()3mZ80@{6MRE7fVQdu z#AGYHE5VYI;@+<)66&}(>>hFN;@rKwx~29R-9!Pk9bVWq{%dU)Id>66nJp?*meK~^ z=P`iKV#?1Abi7h+<6@8-LbbPlyY0Z`1GP)K^5l-xEF1Hk(=;nIT|oZw8z6CEsTw}T z@{`7e?O^98O+klQtKj2>`WVzB96R-r!R(4eIAb1b;h~O_a(n;@YeK3nuPMk zg#4s&K(ix*sGJbun{F9A7pbX~iz2K1ak#swte-t^?DMLl>vM*#)=ov+iQE9)zN;}G zk=BD+v#>B6i4aA6gF%PS@M}W46`67Und$Cc&Qh{X9Ipx@XpcHqPgi7$o_pHPAGFYv zB>adRPER8b2GTPz_2Fb5+`T#2%vrqa>S0JkGh_~XZrMyz@)Y;d(56njT$`jMpImxO z@*@AMClqF74WpO7>1I3X1>^l9V2f;oXxwx`3zw$Q_7qWTpO8Xq17RnGUWY_0u;tiO zFOJttUq!3n1uGc}O6PBjtBIlJb;7%E^W-(8q-@IcLA{-6wb>_lPDh$``dy-qfLL1D zso&72U?OwcY+fRBby41P6zxaV+{R>#GKkUDeb_d%V_HK6+OPAFueWZPH1N5(>M zY!+{rmFpGX1D@o(N0e8p|5%r*`f<2cU{A-PiFDqMXGq7J*1bSly{NC!`B|hID z#@QoV2Ntm9A){bH|~KuLFt((jt##T7>E*%ve#@5L}$Q0jVg z>P;4mA8Uz~=O&UrWG{rD#CRN!z57;VY2c*a#TTKJ#&C>6w%~-ZeF;K1d-_m6$QKkE@|tw;xlUdo1y0l z5-}7r^?DxT4`K}EUv+O-r&kN}KRI{jXw&6u!hJLGNz-B!cv1%E$<^F(z|Nb6L!|s( z!5An-R0-HqysAH$L$-6Vo;rdD`X|V{V#IjSTBCv^&&77_5al-Ep2M?sv2S`$_a@nM z6+zE8`o{IKt@{53iQZi3b za$&kS_$RzflxKlmU8-lhbMVV)DEi0P@MABJUc%ZY{p9}JZi`0&Y?4>MTAUBH6|VWx zqzY8d*1RA-|4GB;ToV&+gnEZp?G$5Dtt7tQS(S6lW|*5)KZjY{)VQ!=6+}jeYOJu6_7wju?LwsnBvnX8G z^XQjyUje}c7CJ%#F@>Z=Og-G`R=kO00&?qX)V3ttF5NIP>o;ph#l{{p({xHTPc!a5 zrdB{->}1PuzO(Gub&^bY=(ovo>H5Se;L@!3J3Z$4{Dq@)Mevn;tQ3->Ln z4n&rPkkdKH#!Lr5_3`J19Qbn}q2}NOyeFCZ5bj!cNYV;F^{Gfo!H2L6G>JDM=KiFy z1|l@zLv=_nsFI1C8Pg6TU2=1#hzBEGkd8i`qhK3&MAWfUeAPJiri;;qA}t$J*ny&x zu6t}mDCsQFGi8Mg$p6Z5m7g?2E6^RP=uaBIj-NE<+E|HPTXNj30U*|+c>@S+%x!J# z;lXp-*z9=*2_0e<#+-d0bAQ|6l|&d8cB^nr*Wr6-epKX;HhoLY+snP(C&LoXWvjK3 zKy!3&6z7hQ>! zA{S=Ffy-21AJ8z=;k(C7rL{ZRV~Xr#KM0{SXVk{m9{FqJLC>zsMDS~!>iL#(kbArB zQk2?&1f@(-2m-q~Oukl%0TJ)+-iGMQH1>pPHtR)kGM|YKar=x@UQ#Q{wC?>ZYjuIQ zxe$|uWkGoP=pg<51Pc>1ELTK}G7Yk$x+xQ=zL+w* z_e}9l)a&GjV+y(zkCidqgTvbR&9u4vq3C3&=(Wk|2~(+(Z~9*)Rr$ZIOnw?*y!3)r zE(UuX13$BN$QXnHg@q^$TjEuaISv+lA3uBuw=c&!j;V30*!c~xhCj9;e+apVxQVC` zNyBTmuQPFY?RRkVHl*SuV1oM+cCm7yc{jQi!S7DTy=b|ndfRpA9f${}MyYImnF|vg zu5g5D5ljhLh0_k-HNEOQWT>K5(FEh1#LGUn@-kHdq!Wiu)EStt)t7weamNfqLztza z_~uoRg$`SO3Op5Nt8&lYjeStHFP!Xiw&EYx`}|g2-ute7I_TY%DHDF0BYU@5kL(~e zI`CFN1ELW(<60mM-+}{A>zO=WTv!k$`l@~XT7I**q1j3qmdAw0!-Fz)^`wn{lD>rz z$;V5-2V5J{Q*PDhBAoq?9)Iy!harq(&hjmCp}q-7T4>AQT@KyGYZ^njPjb%y#k z%LgAxufoFwr^nXGsbmTFiM&HL4yO#{iL6y;m4UBF6p1j`KzR?h-3krZ>gmVrtBv}d z6Re!uVu+wO5x^(*_vil@;eD1WltS&rho*>`%7zBV-PQ3of;;@AiAgPte2Rwz?QMn9 z-D$)vM=D&h%JDibDyB^sdpCY4g+`TOnHyi&dh4jOmLBz%zod~qzIvOjDph}}F1ZDX zT}L$7m+!x_7>n|@;v(OE)l^iGL^WG|cG$9oQw>zSM%?er;-15K1Yv4$=7?~rxL1$r zh_h4%-9yr&BtojC=CNY}O?+AvM|Sd6pM7F#ra8d`C?L*E7l~b_G(F3!(I)M%*g)<<-GdTX*a39Q z-q*7wqR{4+mbn>RNby;l0u^QvclL#Ngv_6H|qmA2lXtFiuwA~ zGJZgCL1`q2Xw{*}Sx?-0?_juJZwe1B9I24LWFIwF z=}ZW<_(ofA696UNF%iVvjQ3!6B`AiG#E9A*rmcydh3Xi}Z7H?#_f;Qi@xchj6j~2_ z-b+=2Km&W%(VTz+Nv^n0ajq+gtzvh})*f&Qn+r_H7p~`;K2O&-#b$D4X2`wBtdMaw zM^!^ywfhUkGT@5f*(mU~oIRYN1UvGRCfi|jmjml-%ZjYo+W2gGI*;O%gw3fW59qKk%BrQltH3lxd(b?lJanM zXyHwOPdEC2H)a*y@s$YvM3uwGv>_u}Z{XP@Fr}eVnnr{$?(-uG@{}wGLTVsK9pR+{ zLWJj|iEen+`Gx!KZ$BS=UIkQwEOafnD=Q`oRLCukhrE7T4+WClu!y6o>o?R$iR{gp}x8K-5jGJxiy$MFWXG4}b#GIX=BBom=752RvTDI_3*Lc!M+|DA> zTu}%}2e0)4cXP{AvK5(DTTj_rj|$?@5u9tiDE;G02+LdzXp@Nj4mIYb!cny$J;t$@ z`|6C%$z7ny+|I+oNSIJAPmA=FfN|9;wwiT#0&tYARR=w~-$kX-KM(9yh}k!CcGs118_mgA^dLw>B$X+@nj zr7zv;LhsA%YZ(n1wWvsz`ofmNT)2Ir%rc`z9e{mm>f|n9H!fr8@p?VLXaU9ZF|4a{ zD%Eor_MFd8c|{mi`b)LVja7>k^t@;@(35US$?-h@wk!W@m%rsGV6wEyQ)SjbH4>k; z&T?&WthWk`NlmNouE(4$?~Z{Ns$Z`Q>RUDPV6QZLJjoNkDA{Rk#29z1o((9KWoSKa z4|HH@_Q7Pa$`qDCXL#_>D?#udVTs{rGvM<1`FL?+98K~_hxQGw!`zG=#QAEVSQX)eZTKe%H%3I99}Aju-> zC(Yr-y<8Jh>7g~Cr}zl}ZDcZAqnhG+g1GUUDcG#-kFjzZ@4qHL+(&FoGlrC|p)O&^ z-&9njPMVFCXT89!IObef3>1DA^e)kp)*E(?5&9)3Sr5PpkOFV6xfzdRQfoMq=M*rH zIfJjC{Zd#eQJD~<-L$(%3FIt~9IMyqa&>g&QkIf@@0NJ}X<`CSi8g@g@!->^Claj~ z%FC{snVG3n-aDhl%&UCfz~CY0rrLWo#rDi%YHy%uc)e(!smh)?Wz-vXqQBHun;3=BK1M!`Q*No< zBB%!JNnG^Ga9>}-v-N~LJtqbb1$*8O?!`)Q7k>XHWG5k?i|GXu?frcPcx8xfA>#7TlteH|Vgiovs3r1kkY z8mW(N3dTXfa>^kbO_!6@4HO}_4Ky6joDvdRaGpSS8!8^Bq*B@KhKR^6ZZ2Hdc&&(b zsOK1}AEWvHThy)UT5k=O!Y&a8+n1%a})YK48dpqO}; zTP!!36f~|U>KTd}3m-&_EH_Q-ca2-HJG1oQIeNw#BaxgoxIA_4Nh?xV0%cv}odH1#Zq)5}T=FA*xyd?^>ER8@-{snupSS8~r^9bWx%D`=1B;!fL< zk5>!K^WHyqGVcl+podZ=@RqPEgPV%H7Up)`mx*tRQ1@Kt2H4Hlm}K5H`t*t}j+oxG zZkW9MLZHot%cFIV))%d;d{HD~fAMPznw%W3Ep-6iw)gbxE-(<*?3Ly;T}5qbtNKj1 znojx@?;4WVD0!_{r@i#skX*^S#3(#V2l6agtSCZ)-mdP|=!Qe1oG3lRr%0CNFt$8< z@$~9okgxU(it^r140bo35P}a*nh~qQ`4vAE2SS`DpNznsdgo0JoQjr_6bLko>nTnV zkmUEDO6>k#9M!WX5r(e19nuXtt1T1?dpchVNVMr0WuKRGuD(ZCZ`Ala$USm0y{myO z($Aya#{*$}>}XCBr)OlLSUFtYS-dXZ2WezI^X^ga+DL|4u}fFocxKhmcx@as`B{V2 z&{ZP$abiX;?0&--wx)EnxdAu!9;H=OdL}jknt$ITl0&!Y^qNb<#l6qc9d__-A3wl= zspL}wteI)8mS^D`wOC&)v1XXO<(zn!x$BgDMG+Yn2VJ%Sw6y1SigJ{UU3s8+qv9zd zUD>MKR#;&Z{_*%L8%op|LMqj<6FjkB&&-R8U$}1-hq_#)629Ox^UWlz9q369?rdDh zavo9SB|`4uX-ydmB_igMd+Hm%6+gWe6!B2%RJm1jKemF+Pc6LYPG5?wh|$y|gL*&c zp5ZRq8EJ9I`M@(SZriUBjrbytPVQL8d+JsanSGc-YP0Ho{P2o+ScMk~<0Zxxt)&Wd z<;C8Log3r4RahygbXtq=&Se%J#-_+OXt-s$;l2^kz4VJvC|MR`E=01>-bitaIj@PS zv^f&ZJGPDPXLWh7E*$&X_iHh)IEK}za?of1FFQ`9JFrJ>PBtx##cC9iK|N-&W4+6r zpyM$8+|^XS2#yf4Yxw|PjLKv2N#O=}y>O&`fB6((UZtMD|Af5&1F31Y>F^Kpx_?O~ z1yiKJLy}mt5W+Ks!VFx!F%UNN%9ba5-mG7+z5V8%PkxZa$_@IHNAL65TWU)M-=|%? zbGQVw!OLH!8bRb059_CA5T)>OGv9tHeJ}bP`2yaf5^piw_KJ2!beuyu4I@*TpHkz@ zF#}2KJ3jQRQSC%QyL?2bXZ;u9j-Zpetp*M=F+D6^Q1x0s5ep|j#fVAFeM^~lv{fB( zUypLa%&eWyIjX@0&3Gu+&#F9~+;4C2uIG%+w{5QV-mB@g$T)~}AEF}WC(Tixb$4{j zBKv}`ZR7Gb<+o&_W{hK{gR0W{K0-L5T)s-WA8;W2X=?~VwEDfv*6ZNy{de$uc{qqOrLO9QRy#N(QtAV0Wt8#cT>5q0r3 zQ-aJsvQ{W!!wnMV$2bg7NMnl*Cf8cq8n>fgKPEV6m8PpLv@x~=xsPwYVg*e$I4AUO zkVeA_>J!8|6W~tOFyYFk^1Ug5%CGBTwm;RP`kWm79B!kEc{=j(%16Y__rF9o}qX z+(YnXW8Sx%pD_Ytikt6v6Opnzje<|t&kMOvkkJ0~ph`uAcNvk-Jb@VBVZlELuT>kG zRK=%L#o13SbXwQPQN3~#f2f&!^8H$Rg{v;;SeKi2Gy>y{+UkfU`j}-fRw95@B(&pxcU6Pu(tSdF-1?1DU5 z3jxNX%sO{acuC!_X8dOt$M!@ZoHIh}Dc@mIWWO=Xgo-;iL`vl-(uyDx%^>PPKRhSjt1jVN0x3z&1w$keEdc`ib2OS&lq zNE)unTzyXx9pspBX|sOD@{QZK4eTALqgbDlAkZf*hIP(pklk^R)F!*^G-b8Wu@ywU zf@E#GXrZSp42d0p^NKSUkHEs`N;UZ&H-)1i_!E`6>zr;`XC<86Z(^dy_7~IC9-Xz% zwyD=Y6Z=^J#hR6ZW};*{KgBV1NQ7`21=mh%X+DCfjMPwEwbD8{WS!4MFjlNW6vT z%`jOe(_A5F@(VIED=TH&JfxEz;jai9q4ID=DL3EF_9Au4C|<4#%9xKETUt%J6?kt; z<5I6#N&2~S_NuS;7&J8TLLD(bVmjj;4(sx$Vh~0L)BNd)CXb|{ZR7m(JnfGOYUXvS zHLpuK=h-I78boC%-jt+59Uj@U8FDMlYe`e{&(XMTXgpy7s$sd1m~@NnF|S&9C&OG1 z+t`@6Z1x7CJi+%|I3%KpR4Z+uS5Y#l(O}lHfS%1j5JAFbFrn5KyrsP>aUb(A^;sc~ z46WCVVVO~tN=?0~>k-c%j*C>A2soFt5G2S~V-<)W?{;P(rfJ~rXA0mABj%>TrbM3t z2UDl)64>2l+#@5-^v~U!?r}4y`%SSooM2&QmLUU4ZC^lcx@HeN9rr!Sw)MTbM%d#{ zNo^tmtIqA9v;}m zoz2mzQs;CuVJ$wQ{`j>j&zy7kE7*(zPJ%2V5vs_Kud6L4x*9c)iaNoRUlvdH73GIF zA6??`G*fRL`cbdTiRcgQZi}hfoP(yRTd7;ud)_S-eX7x|M(TJW51-U2Xf~QRw;USWcrGyJ*TrdHsn$E}t!rvF*=>3^Hej>t)8*5T*cl}E)5m(v z8)0TJ6t-$3_9d(t;o}Ep{7K_VLIdTJxM~a{ssJYu>JC3mM0OmGhh4^*kABO~pY%H1 z@8TSGxO`u4!Csaous?sJ>gog_s2$Ip?FmaYJX3DA$L0KsE3!B^Gwb$#zhl z{iHEOejKl8zQ+)&ks+NPb}8R2;r;R1d)u!ARr0sdPjwBKw|4RYo@Nu+7kA`H&d<3z zxCr>tb;)k%{(~72`Hy?_YK39-U6QAmKd3D7UEa|fRDMPdMmXO6CCK{k0oM6WSJI?% z3~9|YHGiZASudv+ciuMgY{dyBy2?vkhSXkQE?`!ZHu<2Sq7}C|BG*mw< z7{`VLvc)CGwi{pS*+y*%5p2vj1tBlB^NTPPL0Y@-=?lQHH#7Gg`Bw-b` zCl$W>W^g@2%Ya)VH|Mhv16RiiHNt%J)dko_SCGMQXPX138=zUK7w=B%l^Pn=JmnCU zw-U#)rg^B>JB3vfM05wsrQMIN%?P*e&NOrZszM-06-b+N%%U8!?bwnP?-4wI(&Ur^ z8wxBy{G{nF0(9BWe&2r5>>4mxa3ncwH;a7v5KK7+#C5j5 z=l;h^M*Ug11EvWyHN3o#dM*_>HqKlsh=&B|;)4b?faR+X9Mt?HIP?j4c^8Zmpg#Rc zb9OQUxnd9okev;Fz_JP^D(%7#PfP%HE7_{Qtb#}b&uxRMkrXyFZL)wgoGPvQpU{aq z_6a_)0-x0&cTjoiIpE(*Spa_oK%8x%Avo?00E1nI>Yp^oyD`5k1CfDuEP!p{hga*e zhz}fqh>%0fe?lWl`D*}%-+_wHh{9!zjHP|+%}!$b_8GXF_)w@jOQ5;%^}S?U1wn1Hy@cbNey z%hBUUl~R{*lYe_7|C`ae6qf1(!l zIUSiALZwB11!K5gdQwhTf{DBUaCvuKc>re;eo^)DNZu3KRJh{2lzig>l0F z5{$29)#c|If`N&JpXZ|ZqF^j#bB~@d?OrIw#SnOaDBU0tU_GmfsGl_X-^bUSvA_Wx zw;(d(IS4kyh&c)$#-N;9UO}|dA1*5o33gYD>fABaxM<&kIoXu*daCnCIxtzckHBjf zPxwx}&l+SkZ+`x$${ny%NImFVD__`4Dd@bN+zmaAY9xg3aQKrVs9&;z^_%e0JBI`T zk(!dbI*vZFC3+8*cb_V&0@EJ>PU#Hgp>8S+s<0*gn2>x%m@%!23{_$Bm_Gg~Iukv%=TB{{>C77K;uI| z%neKzQjgXHslK_t-5#9(+UdVtJpad^DP4e6&@JjW0N6;&8u;I!^BW+ej(&C16=e=o zK3#H+=KRqN;C}l9063JMssEXW{^AA!Hqalj>&9-K@Cg1Vj4FROiPQbpr}SL;jdTA+ zm49fh|DwwOcN|o#%=(C(Ro4QJ=OZNG#ygx@#e)=*JsaomI4Sl)oi^VVbt_C%E_Q0o z-mRv4;!<))@b&GZ;Y7MwG?`t$E9(MPglq;lCCOKT#CTKK?N*ZD8&9}YT+BsrCr@>2 zlcy>)cLK4t#|9o}rVv2%fNpyih3;z9ipBWyJs1|RjX2_dym9S8a@@mf*16OsWN1^SaFnK2Q_HSH-TU*#X)+>b2+uJcjAmn-|%b^dt-zP#24C=pzM5~2T-Mh^!b zkZ6H#15U|*d-;ife>p(L|93Cf1wI{~3Hj^O?@pYqBPB@ z07^XA5JEipm#W4GTnAaD5@cGyvQ<4y_NRLLt^*o56VMh{3@`|v%TkX3J5B}#g8xPX zz|#2pPW-mKIb+Ba6ZkA2pjs(JsZ#yQkT~YQ?Bg{Hx`V}i1ob``1N~S}_5Xk-9*0mL z`?}|G2wXQSMMC&!N8&z4EA(#wV?-Pb8+oz z%x};A&Y(u4S^fEc{)x`WeB{)LZ!;p2a<1N{GiKf*%vEc^zo0g+9~NCrpm z#Pk*nBU-1A2*4Vc!_`v0b#nuX!XdL7J*%$rzZK=duz&|jfzl+3oXyIrW*CSRs3%#J z7tN^VtzP41ol>hW~JX#q^qs z*0`qIBQ;}k(QKTTNcTw^qiG1vHQ`GcnH?t+`(iU`pyJ-n6`?-+r;VCFF5*9M-})r| z*VUqg8=ed4gdFM1@ru`|Z^hqR<2DbA(M>RacW$iDrqNWaT|PN>Q&yuUC*f^euARk$ z3avunop)tgJoA*O3lsb`&5qN%PK4pgX}=CQ&d8C4&_F4NiQ$GMg2!H&_qXKtQwj=Y z1md-s?3_S-u1Smsr-^*Gp2r(gfl~8;G<&(EG>X43kbHbtn^8_|2p3ro+dLzCy>;=$ zw-81)-KxT%s~ljDW@o}pd`K|dT6@w;om&WR5$hvRQ0eaMCyLz^a8xvL%zx7--g}Jq z^SrCzJ$G7s)4)#}OKrw=_<7`Pj*Q|td_i0s89cfvAyjQ0-$!~jLZem@)s*r2oZNxe zhO+D})cCdH@SZDouf^&4+StN_+;$rFmIL6k$OzmpCO88V0X||Uo7#6@)TSYPF3!a9 zLgLJHf!le#PfuO$xY5x*$(lmGL9^B%qd}+0((_^r3%;N(Q}eoVYio1F!T{%I|Bz=# zUh`F+Kym`-ZOIh)>O@E}bcT;8OV@!FQ6G69z?ESOiL-a+wY_4aTpM>c5S$#<~!~rOYZkIT8LzL^|?}!z4Fvf~j{pdOxbX`twrL*IiAcWJ$`a-SxErml7FZQ8uw3TLe3xpN_wV)~LH+^>tumNn?Y6rQw{{LYyz% zlKr7ms?@Hfai3J7TUEUY)9$ekShGuW#cR-l&b(5!?Mua+d#+UOJq?m2`6o?Z6ms4T zP7L=b|JDMy&L0bciMs{@Qi);DLr7LSfO^d3Or{3|{hTH_L&(HQ_^b&~Ty-@jwAnG& z>+br)H-@7%{o;;J8^-p7)6@K7a2JF0dnYZ=#IdP5M#KkNMIJLv*r;clU?Ow_o^?eT zG?5)x3|uG8bbzk|lvp4d&}k?U@Yn2%vwMhhtEk)ilM)J`;wX?&%$GQDl{uG}6Rf!k zaQ!!Y465g`Il!j}9A{!Dd*KTGYbjP5qtcKnv>hisF_S7${gA4X9=SHWs1w{4)!ijP zBsGWYy$shfHHR!ZK@VNl{ZJdyU#F9OU|Y~vXl--G!awY=Fi^qd>HNT{)mKaLU{p=Z zvmp`EErO)?_g?>b$FgH*N{W>lda`z+<6#{U} ztoqxoQ%e{;zSMRPA73mUi96ypmg#V?e#$cEZp!qB4}2ci->N!fNLW_v3;gLF5t`#= z?h$D}93{k@3WjY-(o-+KR*Y3`s4Fzk+)|m~l^K3hJF?50hS6p=A>Qk#Kdj4@o!F&_ zqcoa|J#HN6s^m|9cn|2Bvqyc83*YQk7#eim-n?0?zTM*j$41?w!W@w}AlD1e3V-eZ z+L?C*PzInPv;xwGyhIkk({-|QTEv!6DoBsMJCe1b91{gG{lkTpmD)*nPGC;{PdGv{)QDR2RO^_Z4gE)evUq3OQO>h8#R`1WxwzlM)I)@ z67rq%5Fh&(`1BnM^7V_CNMJS^waM?*i9DdX?mw95pH_YFS^P8nH_p)gl@0|Z$((yp z1(lA4ecn*^Y@ds9lamU)f*;igVHa3#J!u+#c`Rx@GbiG5(;pEO&hV;x-Sp$IRda2; zZgu#rO)*288%h_0I-R~`^p;t@73QO}vdS*8I7tk74}181QXjX}kV|}{X|h-gZIL0! zf26IvrEVp0L8Rr2`{d^!HxlZ;J z(AfLJ3jT9osqlspZ;kN8@jr@*MJCBjTMu||F@z0$Z z3ke^o-ItHOOz!{I|8CjiW%OPVqq6+vCf?sq{C7U>CdT5y_lU}KKWd%NqT#lyI}lU* zVu^VEhL4ROqfxZ&BA`?gtH<-lJk)8<$*xq@QXxQXJ#OSr0@Fd*_`Zw{vK1CPkC$!=x zkLL(;%eYFynuY{Q#A}XXwym|R9^Y;@x|(8hWQP?mqx0dQGCknfgTs7^JuvCq{QKV2 z#Gj~_8g6*E>CbYo8HoX7<8fPaiuD)~24@pw5Shn188zM28V2{a&CA`GxVV3<9oHi- z8vD^d{ekXiyCYB0cj>8J#BZYDcM(-=P+!ECy`VQ}&GE<8-*jg3R^viKocA16oU2z4 z&1;f9^)_s`bD}hwo+=5QKJ|(A@N`If=C$Ew=UIv@WLR9eJGOM9tH|^*t6k>@wSfDu zYiAk^>CPOv^eI;pRt^`LLHlO3fSxopngi*uh<$U&s3#Dd{)6Nx1MAKki4qi2+#5bQQCI2-=F zK548G9lEi5P;uGPz~+9E^0T`Jub*6dkm*&}hY+jfu0!ropu~)=-|nB^Z(W01%{F;v z>x~}Cf~5ro`K4`Q{Xw~p-)9o8)OlCkPnlZFwDw6+qF)kx`O(m`3lyn%j(BM<28-kb zl2kI!4mK~=ll*b%r!@I_NE zbUTN|>68Ff%t`kM&!OqanL9@pzzNzJBxK7z)uP!D9jf4*L)6wCDwo!TXbd~;ts^S! z-)0jkDxRmal!+Vk@?!!N6=Eg*{ROGArWe?m%8zO{T6=#Rh1`7dPg zUt;!;FiQ;=4cPdjM7UB#|F4huOPO@|7bX6oR{!s!#Nq-(D(Tb*_w5SV`q|TwRCj4s z;5u>{Ta*wRGXA#k)=3|0;D#VlN093a@RfJPtrzAW9BN+!?eDFkjL7gc$lf-1_o2b< zUI|@g*5JP^UOzi{TW>2}vMG@Tcen|bHr(pO+i&1IhpH-To39={O3!{~mPYC3?3*`` z1VFNNVWJ>&QV?dhBCFI1ME4t41zGT_Kq$jbu>n1cRFSNApv@h=Gc?p#xP$gYatEk( zxy{_{Q>$2jx>Spfi0Fs6Le(jZdUihojtLXmj{AS+H(IW%1ag0fb->&~<+Zjk zo5ji?4+kA$^S^Rmt#7IEvJSk4%06P|P0z}skaIdd-Ni5`Z>nIo5@XbR)$(QYw%@#_ z>0x1xp&{0_U&YB<$iTDI95ih9Qq_ zmYw7}tKBYUc85|duE;YpP^d90OxEzuw)fD?i-i_=v-oCSz&3ZvpYOj|oOtzarDdnE z={6W;I|twBAjy#3akg(@zH^|^?_Px%_o0F;_Dq88wT|V-VvPsiHp-AUUR|FYPY-{0 zm&K^?d$X4E#w{y#Qxth@`oiYS2}%R`2nG=X=*4(QNNryO!tru_YlwdG02D1`ZzA}R z_RNFePoIC%w5EYe&>uF-fwVNEjmc~vwq%vsrf?sm8-B`<7#95_ZDla$CgCZBnJf($ z{13dG2}5a0ql!GMMs+wZo2a0TI7R1L<3}p@^`?#*>P#}nBo$R|B{02PLOyhBkwo7g z7TYMB4MzbIr$z4;trKOESZ*9@tTQ`Q?yv@V=?d*Z{av+5aFvyGa1GT>A)V;L!0G8s@O4@y|@ zdBxfE=n3GRiC+Il*BHc|?NE2f0h1|wycvDSY9OFmf=2*$?D(k=>TNdlM|C`#FgDT) zvdWO36!kl|)F8EQKB5#Im`eMCXt||?(`u?{v&S@KGAQ%MLFr8_RRZLFZ#vpCRBhYv zK{@y2nNUzeN;#tStUWh9-Ui)40*6|#w&4A8izZA_eWe`)qZ3YZ3V?K_o2sZ=n;`gwO#$<(^h#_*RLQ`1w52i9VI*B54w(>Yu%0j9Fk zF_cp~Aq?(lrpeWw`y+0!|27c%cPEoj=*p(bHMM^;5h;6-T%*yUdi| zL;awHM;4`?!)EdaWX;>2I+1Rg6Iz>E8d@2VS=fY)9n$9IQCq^FdId?G=t7nC< z()edfkWjhgNa-l1*}T+(MH`Hbx)*IdUa%2AMi!z9d@aeoOG`8js zbRRIy=vMi_`(mDzJC@7$uA6~HI4+2LEVPXC1J0C}-stjq8^?;~fy=o12*oSZ?{H97 zJ0XkJ-I;d9FzXCi9;0qExa%khJ#d%*;v745d%9-uq{&mR`R%F)s+E|dN&?Prz}&5u z{l-$A+XonhqWN)*xy4?R&a7S`hTqGcLv%+4!&jfjip{qKFP(fMv7{kx=|S5bz9!3l zXw(j(g|%#d7a^>lrVA28Y_gPn*qUfC#tZbxnE`9Nr_w4eXGtSNYLSh;cE|=_0$otjdeX z7Pf*6Xq!*@7r-%btgc1OyWQGr+Y1 zViqMsFluBY-#PHbHM!#z?~~3>IMoH*RH(JX9C!v`8=5MgA3v`g1%C==%tAS6hv`WN zBEnz$Ub6KT23wzyeX>r`9GL35Xpfb~Xq;Is0j^t;{M=Z)I}Ap8fmAyjGdJ@`4;J)BV(8WV&LanA}mM1P zuR%!{Co5t<%LO39g}^)Z{&!G@@j>X&AjR_+Um7mVG$uJ+^TtNntjD-|5(eOHkwxcs7;$ z{1EkwLo0onZoHwUk#2pnFI?ZhqWOMoNn$#~oxbH$?=)XkKFrr_oRQ0Udy4=+K|X~; zbZnh9c1Tr8BxqnIfYI$;Wk~x}Z4oX12;~zOKFg~Mc^@C}kRCU(@$PnUba#dOs`tZJ zA5dR8L@P+&8ZLN0(<`F2mJZ0{-DGF+uNc3hWR5q$RVc!A8a1sdKOvgwl*^W5Hsx@x zsZ!Pm95qm4eD@gAKu&q z181k+_Sz@hz-Pr+jVKru!*r_ai1x)^^V)S|qR&OMv|B1OnBPZXd0umw9Pfl_wyKc> zU}G~ci%DW$S@U`53%fOV#dy5rR@^4wC#+%5*}BC0 zN6xo?{}jvQ}?CO%zE#pgW%*MYJEF|I+cf=I9T1OC$pLnLK-M3U}#KGCSTQu zfVEBdDdL-l!D|PZw?$N-5tMU0zC{n-zk8l7%BgznHBE1TiMyY)7YT#(SAfn!f?g>$ z;8nX*Q*hYvQ1<~AFVntu57bP~`pb*LoXREVpIg@Ii)=D?h<;@M^zmRuJHJ^2=h>-nr>MB5IW zy&uo;2KmBLL6B=95n)s{QWI*!Wr3n!aWY(!x6J0)SeJ;Sefj#eivJ_0?30Sg$c!nJ zg`tZc{Oj&3{{BM#y=Vd?AKfq5+$vDq4!cV1!CQSg4C&z7ieT=~wx?c>F~$+fZQPHC zC5e1A-*lT+!I-HF1Pi71qHL0GF4_eSy}#2e=He$w%*RJ-2es1UJ<7|8`*(5RP{u+8 z^i^R%4TG%SxPmNH3SSrhoZ!hmE+$rDs4C)A_3{sFvx4 zjoQb>CW>Z!0@m1tv^~_9$@{qrTVgg9r8z|1a-lFqcA~RRgc*cI)i>XCO8mnI(PIWo z=ju`S9gY^JWRUKBn)V-RuExMOq_fGIlpHC*U93M~Lrb_}+9#7tN#*V_xsK;U_cV)* zWLwq9Y&UM!#r3kAe_~Y(n}xrgVJ6zoYie|Io(O^Ft-Y#Cd7ArJx?1iYL7w?aO%TsO zB4@Wy0z-JinIqpudmu)7x`fuDOPh5RDYDqYL)Jt^b$3I!N2?FE{BYCDwqxsul@igH zq-H@@34jVaNr;OVMm(T?Gx#u+suY;??e?N$g{#=QrX0gQL(${SPghHKAR8)3TzA&# zW(oYcDQ_@CEB{(D3LcfC8(L#-P%9rQx|gLKeTT2M;eWLE-BC^b*}kYKDjag-jN<45T*B~fPjGX-lW&iJ0iUkihv-21Oy@p@txoN?%X@hT{ExE zT5ru;{&7~|99B-)+2>pKr)-%E0y*sbF4s<|jQm0{1C%iO3nXV7HW>!qdh@~4xAu^}_Gp2zI|JH|#-4{`!`>ca zC#9*fYNz2fG1qa^%Uo@8!IBIS<$f%N`JI`oEP7fMmDTlAcOqSSomgt>n}h9OqXx5i zmj22c9LA{;x*BN3LfFV^Mq6^TXbG?yFjNPxAba*GVAghx#=&W{jbF5EnI2+{jMx*$ z6z)978*cWiuG)Oy$lC7@Zp~;p4f4Cb2p7ZX1>O35D8Tm^=X$0FGjXC3D#~(CdvxXQ z4-Hzqql4U~-p6FxJXAH}7kG2*7l;EUXBu%lT%F^Y!Y~t?#BJ3zWJB~mI5*kx{JaUj z|Chxd+Z^JYP>DYAB!OW!Wt(R@thGtvc5_83->daFlXmZmv+w0jwg>_?0D@izc8-fw z8GfP!ZCpzM+<6LqW_i|6a9ap>?!kESp%eK$?ta-}>bJKE&8JH4Hn*UBSIcMyF3OuZ z z#KAtgOpUV+ZiT1Ek@tEBzwz~Ah$wY;V1BI5DgXgv)2ni~lRQG@Rm=7S%Cgfjd=?>d ztO|^aRSh%g#hr5Jn)#MTK*Ot1f~wJjkPPK&%z?&@F?)LYa=vzw7|+B^CL1AzV5tf< zk~8!X5s@`b2+tOx-IElNJvx`&W8&o$zu%oR}_1J^6269an)gz42Z zdr)eZ|F~76!>8O0uMMZFq$&mj8gj9H=eP~f3!DljTrM!JB#>6z&6$}$ zqhvk4plXoQeD^B$$G++?U)2RFwkjd1(6WJWW(ScIpv4MO@L(a8eH@MGMxCHPekP1! zre8^`t(4<9JxbYGC8yly@q3YBPWhZIW~N7m4qYa)ZufSo{pSp%x9+kRjIG_L_Tt-* zaljSgAHd~LqiZt48|{g_6RkW5GZ+o3JB2;vG&;@B*#dU#YAAAN%GsckqxPW|9M7nr zmR!-6U*C-fES!Q_Jg9SQl?S`L7wbaz_#wsav?{sf#oNvhJuQhX#p1yjUzm!-Z z(6OT5^iPU4!R|S zT0(1U#_zzPUL_0f-j<)pWE|CC{byJTV?RDDUFTf&;JkSnE_-&7a1JNVFWKFaRu@Ll zEH=TtfrL*%CHd@98zTK@*>5_5Ven#i36c`>R~JZHtq+Xdj2l}m4n~1ydKNvBcCLI< zUUh&->2w}4l6*8Ca9T|igoWaIoGue&uw>mjSjT|OG)w$rjBMNo^&@Lb8?B{{lQoE@ z!+KI(Ec;c}B+4*miVwoBa_5OM1m)8rSvCTQ8rKRX{n{#k16!=Mn2sxtm#im3w(bZZ zJZY1j4u7Qis2V8*v0*k~-YoaP)!^A+RBMQcBnXy1xp2^LN>RNDPv3;dmoGNFU`bzB z0a3r9S8PokOmVX=UswTkfx4nugnWa<+Zk z(IgGcA3m`$8jsX_drD;mNydlk~*+Tq$=1Z4l1+i*rT_E2}Bwsh-g=bKM)#j-~#} zFIDfBS4B!s;^)ENzNVFx`W{;#2KzOp78`5w9dr(xj5V=j5q^TO%^u_hY4;VU8~G8f zr#_ z%J-rz5zq``B`L(s*szC+Y7xxZQn)oYl`pzfk25C3G`Z8Y1J6mwBhe$uHwH;)Of+SKxofPPIMnz8ABf{k%9v+%Nnh*ziGK& z(4%&!fjyEVl%gg*Jp^}Mklzdt$;*0_SgExTG#fUxGsUwzN~4XH1@mGoIMOYO;cPfHl&!jwyc#lw-nmm< zPP1eqP&}vfyi)yqTV;LN#w)dEFFlgXg|0i--|RAh+BL{wRy`g}#MG?0t&_y0EUGI8 zsivcfcKKoSN_DEwLK75kuaU>nk~nv}jyArvkV#lXqWb|h^&5#MhkVFw+oh*(&6;YP zd<*L8j-MqQPY(ml?UB`jy?YU@LKD&&%X^e?)rQJ(f`xk)WD6iCcd$#d$!1s;%DhE8 zvrmId$OCA#!3|>PlT1U|m1G?$7g3Q&t4{n2*t9Y(X0^CfQ5CE7)?;eARuVjy12k2= zcc~A|;ezejIDh`g>p8tjl5j=|)sFKrDK5i#^((*VAj^}3r^bf6Q@ve{X?bzJuqJ0$ z*;KlYS#=)nNE20qyh7F5C^lGz0yb?wj8Q?kguKg68iq;1#U1{AuAK z7o=4jE_PYS&&FS3PvenQoiFDzK6Iq=D_-=Afhy##k2scV2u1*WlVVnC?B*I^q(=l1 zr=Akn#tDy2BUaxhCZ!05ri_h_yuNi-)7^gCbY{$m+!BAO-avf7QPBzVGO8a~;Q+Q$ zr&#h{xvQd7!9*5ZLIKj>{l|9!hDYPQf!dvt#fG#?qCA3*Z=UHUN353;Ud1(YXep3` zzazsC=7J%u0Hgo2#vSd6F>Gl(;=Vh!=@7x&)thy4e+V_4OLH5dUlN zUD0M0{B?pX;7C(kP7i)jZeTkXS)oy=k_StDHf}GPeCv$no_*;qTWzyLctOu94|z{` zR0AzHcdLQY2*iX~7u90_HVKSM25h^G9kZ+IzU-BKO9rH(9t*{a-F|%AM9I?+(&w-! z<0@Q|>+&LV8*c{XK#MJo>elk(u?2r5xnD3_WrwpEaeaJn2}eE@KfKT+}th-g;%v@pxw8xwqyL8Lqy z05b>Uq&Vt%+cq?IHe#&s^F$-}#a_dfH9cIM6W#?E-n+6F45uo$)kBbpK1^vE!j7*aUI+$Xg*9()W!XkB+se)LDzV<4K(4TerxH+d zcs4X>mS~lRN29Dp((tIm3>jlwIsOKYc}=`a0|yp$$NFZ3UP)z*;n)aT6R?s86!`u8 zg|x+H?w9-MHr_@pXS_cMUuvGTjm}Bx`Z9MInegT?DKVnqW1+2M%2pkb?1>?v@W(bD zBuN|=mt@ZX<{EQ5#1*l!IavU_?mBr4nv3gZS!GrpMW>nTCudWXU!#gkmiVC`$#jT3 zV}0}&o%Q=@O7juxJ3Jt|dXLtx$K(t) zDWqA(tN?<`f@E9{(*{qy>7Hwry;-B?ViAGYxub+vCOAgrbI-H&$r?IviSxJ)`t$=j z@+VNwZp$WPqaIY#Q{SYe6ogFqmz@(D6> zYN#%$zwArN=JxnW^5nge151yBgep4h;l0M`F%3K&Tr_D#tb92cE+^CTdQ~d0X^L=a zcm!GWduVYJWg^nEv3HC5x&~ix)I)m;1cw`l){ooKf25`-|wZZuanpI{`x8H0k3!t*F?vT&@&s*S?VHG%uv)8J~? zg#C|*X1+0=SARvq{>uOSSH7|`0X-f8fTBDMD4W@hSC`{{lKdAo2VB!UPImOT-gl0?sr=_Brq7v_NA=a;j;s-|r+(A|05snF`?2Ev$KF4`gY*9;IF)-GD|@Dwy?^Mg zLoghnpafe@r5OY2xp|JXMy2a>aZyFQWeGn?KqpD3@R-wUbS6cT|HbQhM$w`~tMzAp zqj2v3d@T|}^z{OaYLm}^8dz&`JDZFU=?OhVQxSs%L`bKPSNorm+L$NQW(n%bAkJ*t zf~sw~h{gbUoH-9rrn(uqu1aZ~9KQp89@W_<$==+SJNO1nYOeuKdC%8_L(q@IkAMIG z5CN>Ydx!sDU(cn_=hwU2?M@On@G?54<BvC3#F!{*ZG3&LEKj1^s;dn)^oJ+94FWa&l{a}KJtE)pNGy+*;DS3lV>>xB zeXU-w40q|q)^rVCO|-er?#yE z^=_=>?4C|*_@{GhXBC{{5^KnwfKd zI2+!F1$9x9wI5S=7u$I&C-E1z(CqI^Fiz(^Fcs#_OT+qfc(;7ltxe~`98M1H(r~|+ z$n=bhc~Hd6j=wB=F6+;`{WXjBf09H(Ydt$PUik^teunF|h zK;k+@Ep;DbwwLz(D1EjBev(@d7D_-B*0G0yo>CGS0475Th@ivtdO}kZATayd-N2~6 zQ4|h|e)xP~TUnm;r98{_BNFa%QQQdL1TJ@)K~S^H#$_8c)l51Gw9c~RrpNgds(;z# zX1AyfR|q%IKReT}9i7k%2E=5-xcg4mq*FG-*rLu@dSWbVi$1usr#?Qjr!&a)*{JWh zkvA!rpaL|^xiVurXEEfN;#lawvYy*z^(CE=R#M`KR~}^8q$Tv1Z=^mWDx(?{gtS86 z1`KIEt8z{N9~bj+5xkUvpx~hzHiMTOmXH(I8gPz{-Ljq%baaCG&a{+av${t{P{G+b z6%EuLdB=pi$=l@*@?E77s}EN*3rdx~6z;}WCj0|3p{LR~`Uf>oj|a=V*87@BWdu=L z-D8AlcZ@9+`nXLV`uNbNsgQn1n10EEH30nJ4KRBTacYGyw6tEH9qZN6FZ<;+>J+tA zl*N=y0jIH#ls|7nV%`dIolEgBZIrnT5TPmHR+wot8D?4?iwG1IL?f+)FRjlm{?W=za|{Vo zt+arxNgUw2U|Kw>0uZNSPQk=h6=GO;%GtyJe1QIVi2ma!X#YvV#xi~CqX;A-Sg$A& zw10OIFZ1<)t&(2hnNGL^ZU}Y3QToRI zR)z!Mj=l{5XGPa^a$XAu^vBZ z7%P=!*m`N}YNtoDiqgCQl0=(neOR3}uU%@Z7;d{^mY#Z4g){cWzBzKiI=;@aCoZM; zEm{xZomsOT2c0c{n3Bvp)2iIR-s2)UJuEA68tcc7LoGu)x0E9&lYo9&o`nVk<#C9v zfwua%J3gpkV$DD?D0OGa$oXardte7VXDr+ii$uJMf5iNCEdI@#*05s0nbN@E1Uf4A z<%HI%YUT5sjdT8nEZ#_sdXjNQ?6tWlUAEPQs5y(<3I zK#oGwO4mD*W%*qAwF$SMBy)DF8DUAvwb7kjtXD>>)p@ao0tYfp+YbCBR^Gg)S?3N& z@;w>bd*8t&PScxxmrvPNh;esFd+Rg5IaxH}6(r>6T56^NaK z)t@9JxcN>kSkK@ZYlhsbW?tfr3S(O*GP&R>PO$-uf!G@gy**ZkZ0o2YO?X(Ml51^i zb184fU5^MPK?+}1QwN^gbJl*F>u2GFvpD{iJ0J@b0Ahe#EC*&t7oF|wRVo}tGJuZ4IV zYte=BUhK$tbbz8J4vVF;VmCX=amv-udn%aCFhTb;!`1DbMI*2Ht!Yhxp^pnv>e6|7 zVV;Se=bUco#Yi=trV?~OJ%ZE*wrs2Js1RxSHEwAQx}^~DVC2Wo#q<5w7JQ3DCp1@t zc&Vx1-0e!G8q##+6;6pBm_O}5J$CShlukU_z5+(z5Ci>iLd}W7w^i9tX>psv`EQ1_ zwR3se>S>1;$L5H}B?DpHefEK7`@s^r5)vgodoe^BOh{m}Od()@oqie6`V!b*s&J$Z zmMb2Cr_FV5PuWN#Wn`U$FN&Y% zQKAy;Z{j)fuF$yC`vxdzvimvm$e^V}v6krc7W@_QsaqwaF4`0rBAVlmUpj zv-CAeoyQKe>7Gk|qEh*ru;La^ljx0j4->?Dd*a4owYTWjVZwBm z2GvZvC;Qw+421_n*=8u?n??6Js-zfchb&xGJH7(T* z4dd+kPPf7>yaZU{%%8={58PEzCRt8+?oAC9f^8n%nS$$;FTgDCx^um*Q9nC&1o^hL z?1QT*=HuZn9m;fWRVcNSdzWqP!-^E5+W};ufdqM=)EvLfDp(l-%o$;a%xPwNwb2&u zAZl#raDlIn>XIB|RT9|Y?{!>yjXen7e(`?bBtKJcu1TO(;UReVz`Dd+iB|LJnLo;D z|AzMOTV>{qM4{GEJ{BDL7?0h^IR36Eo9cN^F*8$3I*cHO7hn<@-NR$U$;84|eb zeqWH9z*45PN>1%`yTB)OC7q7O;)%BX?n@yThV%~^^sQ7jP{!(0nt9#$eUZV0$+;;c*su9ZPKB%KhcFaQjw;b5TlL9d^k*y@aAp_}327O-)vYhM z@~jeXx}mu4E7d)G6#^_d7eoy-cKcsGBfC#(*__>elyck=GyiS*IIL#xRinnv=?YqX z4=(tVgqE0@k5Vc(F;?igSf-ScH%amO?OQIFFSTTYM{;OwJfC$&4bopEiotM_0RHfk zg5j{9Ixe6Mbr-&4EC7>0;@0&`SE8&aH&esRsjXujI*Yh99G?mMLqCu@Oet62niW zdE1TCL1vf&I}Lu(w>C0egU-}$FE%gKODr(@DGj9C0Z@G~+a%&^3mkyp+G_z{%0AI7 zyORb=*6EdDg-u<|VbCNxuu*4yxY=dF0L=U7$VJ$H^j;=+w;J!G`1~6*yl5@VM*Im` zNC`iCOY=nfz><_lbF+nAL{>O$|1BMzJ2QiHPlcb5Z0Xqvk0c;$(7Kze(`cU+ryxJ^ z=8O&`g($Q6ggv2-yH#d_?Imhe^gddV}4qpAGFV44h3pM*>W7fekYSqp#D?xoMl!VC|Oof&mC#O#NK9_cO`X1op~L zY@VB(ckUsN=9 z@t`u}vRD<5`KFE0dzA3!XuU#)HWWQ9pchI;xL*P*R{-b)R$bmvf!fBSuR)7FSxhx25Un~&MQ48uhdBrvb`eBa? zn8@77`CaY_t5PXj0APV9#%$%8+yy_@*{_TUgm)LNhouf+GV z;BY?S4q%x~C4-$x7TD~fG(&W%i+AY?$LO1uw!f{R&k{YJcloMOYug@5lCbh|I|u%@b6-p1NsFk)uK z%>v-Pk+WT3esEs394O%bw@l74gpS|Jo@u`1SnEeNnq|x=Em5i87NDlV=a@)pEBi@0 z%d89G`bEmmM55bHNAQY`;0Ks@M+bO*SZ$|ttztyeF{6=mbWIRx18Q@${Q38*ITJh>I8PU-;q??nudeEX0`YpPfCZ|KMKmk z+wHFTPOap)Z?5;TZ1#X=GrE^JaTw%PX-e#c>GqWB6H*h_mhj`w;^IzcQoUDmV-@9uYFsdmuV>26t2>$sq-B+jN(l-Wzb>JkP`7cQA#52}0lQbV-J`Q8Lt z&J^@RcrQ@yh{GCnqDbi-n}z;to2jslgIX0>mZWfL@Rdp@8ges!2F>AE z0Yb0#Ri)cvsQsJfeJAJEP&=qX*nCf-i3CO&$q)qh$>9oz%LL3mLao!3F%bl6{Qa-3mz$H&l2b_^V1D3PX?fn_(4k=1YP3uV zeqT#>jfBuOHSnlM*HaRO<8lfwmwtg~f^lw~?5;gcTw+d>mFk~qPO5+y4?9q-$3TU- z;|@?B0Hu>s>Pg+`cbtR3&ETo2S< zz#yIcP1E+oB(12Mtdm7_4LoR`W@c7|@_+FBr7a2#!i$T?Y}SqMZH|k8{3Ti5r+jM3 z{EEFUD6QtsDELLaQb(!aq4F(=ThxB+*{v&1jL=By%4M*{^snTc<32nMIq8%UQ@!br^E(?5(I^SW|(9FqEc!b}7b@ z*_3v42co+jP4Dh7x$gTbd?zE(%hVC1gt_##6Im+8+yz9|B`@_bpEw$p6w}DbuJ1Of$wi+v2KZ@}~5a|NU>p574*N1*QkiO`) z{>{7Zj(SKsUI865FObpY%Z6PWEFG#R-Wpk>3P>dKVk{EsLfI#)>uc0+Pi0#*ve{6ypaxOZbB;>+vNDvEVt9j zS*YW-N1C}wpT|3!tYDBUhe;jK;B*#D*}G$5n^wkgf$CJ1`sd@)Pajs^av?ci`=OtN zV)HemMjWE=9rVx3F!7Au;oLd@ZK?jN8nAdWiPs1jx+^=ve;1Lh*fbQ{>SyB|DZH9; z7Qg|&nAHG#VH@Ok?xI7@+VNdPb^Pio%zS?hHfa`^zr?G?UP;{hrdLP;;QxO;&Zu;O zu*(^T2V}(PM=d%v5Y7pHLbWqE7p`)Pb&C@7NshhCHqeqRO1 zZ+~^>{2h(IO`YG-_#KVxf8b&JJ2(D!a-%5vZy{0Eza5Q}Ka@l`7lawGYE(yly!8~} z4l>m^_@EpG6!~BLeIe-YXBTCvAJ=!0-{wH4mkq5iiK=V5^}S87VO5^LVKX^`^G?Y> zt@J~z_n@$ifUTO8?xqPKhb_AXB6#!0r#$Ma{U-)TqO?0f|I7gCFRl*%%xUe}#J|8~ zNL`6u{`aT&(-rS`EdI+O@OLbJ$Kp@7z+b)ce&@yi2YGR&$?M{xfWQ5X3|?P!%=2ef z2ZaTIN7i6s9gmk|e2*u4fJ=sL@h@y-b<7Bp-k8@bpkGY+lV8#E!d=ZjHfn!T6aJSe zN0Q})kW#sCOHQYkdB6DGhby+=td{XC3LSg~mJL(iTSOCA4Bo1^`cgIrJ`bp8kJNi_ zPT?<6)}#XvrVsN^eTM%4){QjK)XOZ;Rq@XHlImM|t@&2PHjO!&s0EqkYrcv5%)!cW zY;j31BVQ?qUAm#uyBc-Wh|HY?Y%}iLqgygi4wO!zPhm}Nt06Zlc?qszZjnahuR0Z z>v7M+=GcupIUm&DCyRZs;>QiA$JTW_ARb^vHxe>u?Idp-Z=v(HmZT=DagG93`_N&A zsoK#;ANgZnnF=)uzu%>}aIe`JF9K`Dx6fGyWhz;iWkvCBT&iwgD>a?bhmP>|9dmAT zH=oLDyhC{2kAlf94YXwYAprz2JzdYkV}b5{Gjh3n5Guu2)>}G^9q)*>mORRH4~>cm z4zzljU>et4$*wuf_<%upPfgn?k=fZHoBv|b0r7JyO&xXs=3Ybora*LThLPb(qaAly zvZp|ElBrx(?_E+;&RAIk<%J5eR~4s;q7YwbTlloM>X^tW-{Tsrdi_$bW`;WR&JdLQ z{KKK{h~zhULwkub50sFN#J48JyzY@~QAN zXLZ-9s+}%4*|lKd5#FFr-(f9Y;cPLD$MTteI=KLXXATbEUTbo5%*m_~0GiJaF|dm~ z@7->HWzI%=wT%(14xs&hvZ~wql-#ty|qFyel z?xsjS-(1039!WfdL#{Ua-JWz0#C(b=-LBX&^es(GARK#)LS|9ErAuma7n5^LKhZtB zd1v&1rK_yDV(t`iC1!Kpy=li{n9sP>4S!Oecsz^VaErp55oBUM(b%3u(ilETJg(Hp zgm&L~2enE(R{eaTHjL0c&)?4G=utW6?AZgVk$Xirw;F+F)0kVgQ<21b* z+u$7OC^13@iUj5ZxC^=fHwd>lHp62@55r7jH#1qhi-KBID{juWxDZ0^$lm z9EYuY3Kja=GSDyatM^e>N=c$_E92#p%!v|5ku3r0B=7s+(9r?O{gN}f?Q`nday4?V zmobw^rS63&o5V(k#g>FITS+B!)DVr4kiNLQh}xZ5@)uCc4n^1f_pS&4hOvxNS&h!e zUg|!~Es^CJ`ySzQmNS`EDuOv|^K>i5ti*og`XcjQ&(8DL?~TQeo=-XP!+(5lqGU55 z&W<{+*QfR})rC-5odqRR-oqHx>PhBfv^=Cs>$RgbLr zPc_?(y1A@Xx$}!1b(iQSz`XnzX_}&nK1${wH%BjBl7M!S=~y`UG`$8zw*(_@_pGTM zz!iNmx0&2rhi~&Z$g1XAZ@ggkdNJgq$PK^nX*?1H9m{P@1=rbqbwKYKa@^}C?Xk{& z0}?JyP#C!Kvin)wC$dL|F0IEvA5h4N`&T9u^c!@aUsH*WF>I^pDX`UmIJOKLs>K6`X*m$|cE{c`**1J~qNdhYlGMF0ow^ByO* zYye#3E*H4lY(XGF6A$_L#pE5_QpR@l1UXJ+# literal 0 HcmV?d00001 diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..f69cabb --- /dev/null +++ b/keywords.txt @@ -0,0 +1,604 @@ +####################################### +# Syntax Coloring Map +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +SFE_UBLOX_GPS KEYWORD1 + +ubxPacket KEYWORD1 + +geofenceState KEYWORD1 + +UBX_ESF_MEAS_sensorData_t KEYWORD1 +UBX_ESF_RAW_sensorData_t KEYWORD1 +UBX_ESF_STATUS_sensorStatus_t KEYWORD1 + +UBX_NAV_POSECEF_data_t KEYWORD1 +UBX_NAV_STATUS_data_t KEYWORD1 +UBX_NAV_DOP_data_t KEYWORD1 +UBX_NAV_ATT_data_t KEYWORD1 +UBX_NAV_PVT_data_t KEYWORD1 +UBX_NAV_ODO_data_t KEYWORD1 +UBX_NAV_VELECEF_data_t KEYWORD1 +UBX_NAV_VELNED_data_t KEYWORD1 +UBX_NAV_HPPOSECEF_data_t KEYWORD1 +UBX_NAV_HPPOSLLH_data_t KEYWORD1 +UBX_NAV_CLOCK_data_t KEYWORD1 +UBX_NAV_RELPOSNED_data_t KEYWORD1 + +UBX_RXM_SFRBX_data_t KEYWORD1 +UBX_RXM_RAWX_data_t KEYWORD1 + +UBX_TIM_TM2_data_t KEYWORD1 + +UBX_ESF_ALG_data_t KEYWORD1 +UBX_ESF_INS_data_t KEYWORD1 +UBX_ESF_MEAS_data_t KEYWORD1 +UBX_ESF_RAW_data_t KEYWORD1 +UBX_ESF_STATUS_data_t KEYWORD1 + +UBX_HNR_PVT_data_t KEYWORD1 +UBX_HNR_ATT_data_t KEYWORD1 +UBX_HNR_INS_data_t KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +setPacketCfgPayloadSize KEYWORD2 +begin KEYWORD2 +setI2CpollingWait KEYWORD2 +setI2CTransactionSize KEYWORD2 +getI2CTransactionSize KEYWORD2 +isConnected KEYWORD2 +enableDebugging KEYWORD2 +disableDebugging KEYWORD2 +debugPrint KEYWORD2 +debugPrintln KEYWORD2 +statusString KEYWORD2 +disableUBX7Fcheck KEYWORD2 + +checkUblox KEYWORD2 +checkUbloxI2C KEYWORD2 +checkUbloxSerial KEYWORD2 + +process KEYWORD2 +processNMEA KEYWORD2 +processRTCMframe KEYWORD2 +processRTCM KEYWORD2 +processUBX KEYWORD2 +processUBXpacket KEYWORD2 + +calcChecksum KEYWORD2 +sendCommand KEYWORD2 +printPacket KEYWORD2 + +checkCallbacks KEYWORD2 + +pushRawData KEYWORD2 + +setFileBufferSize KEYWORD2 +extractFileBufferData KEYWORD2 +fileBufferAvailable KEYWORD2 +getMaxFileBufferAvail KEYWORD2 + +getPortSettings KEYWORD2 +setPortOutput KEYWORD2 +setPortInput KEYWORD2 + +setI2CAddress KEYWORD2 +setSerialRate KEYWORD2 + +setI2COutput KEYWORD2 +setUART1Output KEYWORD2 +setUART2Output KEYWORD2 +setUSBOutput KEYWORD2 +setSPIOutput KEYWORD2 +setNMEAOutputPort KEYWORD2 + +factoryReset KEYWORD2 +hardReset KEYWORD2 +factoryDefault KEYWORD2 + +saveConfiguration KEYWORD2 +saveConfigSelective KEYWORD2 + +configureMessage KEYWORD2 +enableMessage KEYWORD2 +disableMessage KEYWORD2 +enableNMEAMessage KEYWORD2 +disableNMEAMessage KEYWORD2 +enableRTCMmessage KEYWORD2 +disableRTCMmessage KEYWORD2 + +getSurveyMode KEYWORD2 +setSurveyMode KEYWORD2 +enableSurveyMode KEYWORD2 +disableSurveyMode KEYWORD2 +setStaticPosition KEYWORD2 + +getProtocolVersionHigh KEYWORD2 +getProtocolVersionLow KEYWORD2 +getProtocolVersion KEYWORD2 + +addGeofence KEYWORD2 +clearGeofences KEYWORD2 +clearAntPIO KEYWORD2 +getGeofenceState KEYWORD2 + +powerSaveMode KEYWORD2 +getPowerSaveMode KEYWORD2 +powerOff KEYWORD2 +powerOffWithInterrupt KEYWORD2 + +setDynamicModel KEYWORD2 +getDynamicModel KEYWORD2 + +resetOdometer KEYWORD2 + +enableGNSS KEYWORD2 + +createKey KEYWORD2 +getVal KEYWORD2 +getVal8 KEYWORD2 +getVal16 KEYWORD2 +getVal32 KEYWORD2 +setVal KEYWORD2 +setVal8 KEYWORD2 +setVal16 KEYWORD2 +setVal32 KEYWORD2 +newCfgValset8 KEYWORD2 +newCfgValset16 KEYWORD2 +newCfgValset32 KEYWORD2 +addCfgValset8 KEYWORD2 +addCfgValset16 KEYWORD2 +addCfgValset32 KEYWORD2 +sendCfgValset8 KEYWORD2 +sendCfgValset16 KEYWORD2 +sendCfgValset32 KEYWORD2 + +getNAVPOSECEF KEYWORD2 +setAutoNAVPOSECEF KEYWORD2 +setAutoNAVPOSECEF KEYWORD2 +setAutoNAVPOSECEFcallback KEYWORD2 +assumeAutoNAVPOSECEF KEYWORD2 +initPacketUBXNAVPOSECEF KEYWORD2 +flushNAVPOSECEF KEYWORD2 +logNAVPOSECEF KEYWORD2 + +getNAVSTATUS KEYWORD2 +setAutoNAVSTATUS KEYWORD2 +setAutoNAVSTATUS KEYWORD2 +setAutoNAVSTATUScallback KEYWORD2 +assumeAutoNAVSTATUS KEYWORD2 +initPacketUBXNAVSTATUS KEYWORD2 +flushNAVSTATUS KEYWORD2 +logNAVSTATUS KEYWORD2 + +getDOP KEYWORD2 +setAutoDOP KEYWORD2 +setAutoDOP KEYWORD2 +setAutoDOPcallback KEYWORD2 +assumeAutoDOP KEYWORD2 +initPacketUBXNAVDOP KEYWORD2 +flushDOP KEYWORD2 +logNAVDOP KEYWORD2 + +getVehAtt KEYWORD2 +getNAVATT KEYWORD2 +setAutoNAVATT KEYWORD2 +setAutoNAVATT KEYWORD2 +setAutoNAVATTcallback KEYWORD2 +assumeAutoNAVATT KEYWORD2 +initPacketUBXNAVATT KEYWORD2 +flushNAVATT KEYWORD2 +logNAVATT KEYWORD2 + +getPVT KEYWORD2 +setAutoPVT KEYWORD2 +setAutoPVT KEYWORD2 +setAutoPVTcallback KEYWORD2 +assumeAutoPVT KEYWORD2 +initPacketUBXNAVPVT KEYWORD2 +flushPVT KEYWORD2 +logNAVPVT KEYWORD2 + +getNAVODO KEYWORD2 +setAutoNAVODO KEYWORD2 +setAutoNAVODO KEYWORD2 +setAutoNAVODOcallback KEYWORD2 +assumeAutoNAVODO KEYWORD2 +initPacketUBXNAVODO KEYWORD2 +flushNAVODO KEYWORD2 +logNAVODO KEYWORD2 + +getNAVVELECEF KEYWORD2 +setAutoNAVVELECEF KEYWORD2 +setAutoNAVVELECEF KEYWORD2 +setAutoNAVVELECEFcallback KEYWORD2 +assumeAutoNAVVELECEF KEYWORD2 +initPacketUBXNAVVELECEF KEYWORD2 +flushNAVVELECEF KEYWORD2 +logNAVVELECEF KEYWORD2 + +getNAVVELNED KEYWORD2 +setAutoNAVVELNED KEYWORD2 +setAutoNAVVELNEDcallback KEYWORD2 +assumeAutoNAVVELNED KEYWORD2 +initPacketUBXNAVVELNED KEYWORD2 +flushNAVVELNED KEYWORD2 +logNAVVELNED KEYWORD2 + +getNAVHPPOSECEF KEYWORD2 +setAutoNAVHPPOSECEF KEYWORD2 +setAutoNAVHPPOSECEF KEYWORD2 +setAutoNAVHPPOSECEFcallback KEYWORD2 +assumeAutoNAVHPPOSECEF KEYWORD2 +initPacketUBXNAVHPPOSECEF KEYWORD2 +flushNAVHPPOSECEF KEYWORD2 +logNAVHPPOSECEF KEYWORD2 + +getHPPOSLLH KEYWORD2 +setAutoHPPOSLLH KEYWORD2 +setAutoHPPOSLLH KEYWORD2 +setAutoHPPOSLLHcallback KEYWORD2 +assumeAutoHPPOSLLH KEYWORD2 +initPacketUBXNAVHPPOSLLH KEYWORD2 +flushHPPOSLLH KEYWORD2 +logNAVHPPOSLLH KEYWORD2 + +getNAVCLOCK KEYWORD2 +setAutoNAVCLOCK KEYWORD2 +setAutoNAVCLOCK KEYWORD2 +setAutoNAVCLOCKcallback KEYWORD2 +assumeAutoNAVCLOCK KEYWORD2 +initPacketUBXNAVCLOCK KEYWORD2 +flushNAVCLOCK KEYWORD2 +logNAVCLOCK KEYWORD2 + +getSurveyStatus KEYWORD2 +initPacketUBXNAVSVIN KEYWORD2 + +getRELPOSNED KEYWORD2 +setAutoRELPOSNED KEYWORD2 +setAutoRELPOSNEDcallback KEYWORD2 +assumeAutoRELPOSNED KEYWORD2 +initPacketUBXNAVRELPOSNED KEYWORD2 +flushNAVRELPOSNED KEYWORD2 +logNAVRELPOSNED KEYWORD2 + +getRXMSFRBX KEYWORD2 +setAutoRXMSFRBX KEYWORD2 +setAutoRXMSFRBXcallback KEYWORD2 +assumeAutoRXMSFRBX KEYWORD2 +initPacketUBXRXMSFRBX KEYWORD2 +flushRXMSFRBX KEYWORD2 +logRXMSFRBX KEYWORD2 + +getRXMRAWX KEYWORD2 +setAutoRXMRAWX KEYWORD2 +setAutoRXMRAWX KEYWORD2 +setAutoRXMRAWXcallback KEYWORD2 +assumeAutoRXMRAWX KEYWORD2 +initPacketUBXRXMRAWX KEYWORD2 +flushRXMRAWX KEYWORD2 +logRXMRAWX KEYWORD2 + +getTIMTM2 KEYWORD2 +setAutoTIMTM2 KEYWORD2 +setAutoTIMTM2callback KEYWORD2 +assumeAutoTIMTM2 KEYWORD2 +initPacketUBXTIMTM2 KEYWORD2 +flushTIMTM2 KEYWORD2 +logTIMTM2 KEYWORD2 + +getEsfAlignment KEYWORD2 +getESFALG KEYWORD2 +setAutoESFALG KEYWORD2 +setAutoESFALGcallback KEYWORD2 +assumeAutoESFALG KEYWORD2 +initPacketUBXESFALG KEYWORD2 +flushESFALG KEYWORD2 +logESFALG KEYWORD2 + +getEsfInfo KEYWORD2 +getESFSTATUS KEYWORD2 +setAutoESFSTATUS KEYWORD2 +setAutoESFSTATUScallback KEYWORD2 +assumeAutoESFSTATUS KEYWORD2 +initPacketUBXESFSTATUS KEYWORD2 +flushESFSTATUS KEYWORD2 +logESFSTATUS KEYWORD2 + +getEsfIns KEYWORD2 +getESFINS KEYWORD2 +setAutoESFINS KEYWORD2 +setAutoESFINScallback KEYWORD2 +assumeAutoESFINS KEYWORD2 +initPacketUBXESFINS KEYWORD2 +flushESFINS KEYWORD2 +logESFINS KEYWORD2 + +getEsfDataInfo KEYWORD2 +getESFMEAS KEYWORD2 +setAutoESFMEAS KEYWORD2 +setAutoESFMEAScallback KEYWORD2 +assumeAutoESFMEAS KEYWORD2 +initPacketUBXESFMEAS KEYWORD2 +flushESFMEAS KEYWORD2 +logESFMEAS KEYWORD2 + +getEsfRawDataInfo KEYWORD2 +getESFRAW KEYWORD2 +setAutoESFRAW KEYWORD2 +setAutoESFRAWcallback KEYWORD2 +assumeAutoESFRAW KEYWORD2 +initPacketUBXESFRAW KEYWORD2 +flushESFRAW KEYWORD2 +logESFRAW KEYWORD2 + +getHNRAtt KEYWORD2 +getHNRATT KEYWORD2 +setAutoHNRATT KEYWORD2 +setAutoHNRATTcallback KEYWORD2 +assumeAutoHNRATT KEYWORD2 +initPacketUBXHNRATT KEYWORD2 +flushHNRATT KEYWORD2 +logHNRATT KEYWORD2 + +getHNRDyn KEYWORD2 +getHNRINS KEYWORD2 +setAutoHNRINS KEYWORD2 +setAutoHNRINScallback KEYWORD2 +assumeAutoHNRINS KEYWORD2 +initPacketUBXHNRINS KEYWORD2 +flushHNRINS KEYWORD2 +logHNRINS KEYWORD2 + +getHNRPVT KEYWORD2 +setAutoHNRPVT KEYWORD2 +setAutoHNRPVT KEYWORD2 +setAutoHNRPVTcallback KEYWORD2 +assumeAutoHNRPVT KEYWORD2 +initPacketUBXHNRPVT KEYWORD2 +flushHNRPVT KEYWORD2 +logHNRPVT KEYWORD2 + +setNavigationFrequency KEYWORD2 +getNavigationFrequency KEYWORD2 + +getGeometricDOP KEYWORD2 +getPositionDOP KEYWORD2 +getTimeDOP KEYWORD2 +getVerticalDOP KEYWORD2 +getHorizontalDOP KEYWORD2 +getNorthingDOP KEYWORD2 +getEastingDOP KEYWORD2 + +getATTroll KEYWORD2 +getATTpitch KEYWORD2 +getATTheading KEYWORD2 + +getTimeOfWeek KEYWORD2 +getYear KEYWORD2 +getMonth KEYWORD2 +getDay KEYWORD2 +getHour KEYWORD2 +getMinute KEYWORD2 +getSecond KEYWORD2 +getMillisecond KEYWORD2 +getNanosecond KEYWORD2 +getDateValid KEYWORD2 +getTimeValid KEYWORD2 +getFixType KEYWORD2 +getGnssFixOk KEYWORD2 +getDiffSoln KEYWORD2 +getHeadVehValid KEYWORD2 +getCarrierSolutionType KEYWORD2 +getSIV KEYWORD2 +getLongitude KEYWORD2 +getLatitude KEYWORD2 +getAltitude KEYWORD2 +getAltitudeMSL KEYWORD2 +getHorizontalAccEst KEYWORD2 +getVerticalAccEst KEYWORD2 +getNedNorthVel KEYWORD2 +getNedEastVel KEYWORD2 +getNedDownVel KEYWORD2 +getGroundSpeed KEYWORD2 +getHeading KEYWORD2 +getSpeedAccEst KEYWORD2 +getHeadingAccEst KEYWORD2 +getPDOP KEYWORD2 +getInvalidLlh KEYWORD2 +getHeadVeh KEYWORD2 +getMagDec KEYWORD2 +getMagAcc KEYWORD2 +getHeadVehValid KEYWORD2 + +getPositionAccuracy KEYWORD2 + +getTimeOfWeekFromHPPOSLLH KEYWORD2 +getHighResLongitude KEYWORD2 +getHighResLatitude KEYWORD2 +getElipsoid KEYWORD2 +getMeanSeaLevel KEYWORD2 +getHighResLongitudeHp KEYWORD2 +getHighResLatitudeHp KEYWORD2 +getElipsoidHp KEYWORD2 +getMeanSeaLevelHp KEYWORD2 +getHorizontalAccuracy KEYWORD2 +getVerticalAccuracy KEYWORD2 + +getSurveyInActive KEYWORD2 +getSurveyInValid KEYWORD2 +getSurveyInObservationTime KEYWORD2 +getSurveyInMeanAccuracy KEYWORD2 + +getRelPosN KEYWORD2 +getRelPosE KEYWORD2 +getRelPosD KEYWORD2 +getRelPosAccN KEYWORD2 +getRelPosAccE KEYWORD2 +getRelPosAccD KEYWORD2 + +getESFroll KEYWORD2 +getESFpitch KEYWORD2 +getESFyaw KEYWORD2 +getSensorFusionMeasurement KEYWORD2 +getRawSensorMeasurement KEYWORD2 +getSensorFusionStatus KEYWORD2 + +setHNRNavigationRate KEYWORD2 +getHNRNavigationRate KEYWORD2 +getHNRroll KEYWORD2 +getHNRpitch KEYWORD2 +getHNRheading KEYWORD2 + +extractLong KEYWORD2 +extractSignedLong KEYWORD2 +extractInt KEYWORD2 +extractSignedInt KEYWORD2 +extractByte KEYWORD2 +extractSignedChar KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +SFE_UBLOX_STATUS_SUCCESS LITERAL1 +SFE_UBLOX_STATUS_FAIL LITERAL1 +SFE_UBLOX_STATUS_CRC_FAIL LITERAL1 +SFE_UBLOX_STATUS_TIMEOUT LITERAL1 +SFE_UBLOX_STATUS_COMMAND_NACK LITERAL1 +SFE_UBLOX_STATUS_OUT_OF_RANGE LITERAL1 +SFE_UBLOX_STATUS_INVALID_ARG LITERAL1 +SFE_UBLOX_STATUS_INVALID_OPERATION LITERAL1 +SFE_UBLOX_STATUS_MEM_ERR LITERAL1 +SFE_UBLOX_STATUS_HW_ERR LITERAL1 +SFE_UBLOX_STATUS_DATA_SENT LITERAL1 +SFE_UBLOX_STATUS_DATA_RECEIVED LITERAL1 +SFE_UBLOX_STATUS_I2C_COMM_FAILURE LITERAL1 +SFE_UBLOX_STATUS_DATA_OVERWRITTEN LITERAL1 + +COM_TYPE_UBX LITERAL1 +COM_TYPE_NMEA LITERAL1 +COM_TYPE_RTCM3 LITERAL1 + +COM_PORT_I2C LITERAL1 +COM_PORT_UART1 LITERAL1 +COM_PORT_UART2 LITERAL1 +COM_PORT_USB LITERAL1 +COM_PORT_SPI LITERAL1 + +UBX_CLASS_NAV LITERAL1 +UBX_CLASS_RXM LITERAL1 +UBX_CLASS_INF LITERAL1 +UBX_CLASS_ACK LITERAL1 +UBX_CLASS_CFG LITERAL1 +UBX_CLASS_UPD LITERAL1 +UBX_CLASS_MON LITERAL1 +UBX_CLASS_AID LITERAL1 +UBX_CLASS_TIM LITERAL1 +UBX_CLASS_ESF LITERAL1 +UBX_CLASS_MGA LITERAL1 +UBX_CLASS_LOG LITERAL1 +UBX_CLASS_SEC LITERAL1 +UBX_CLASS_HNR LITERAL1 +UBX_CLASS_NMEA LITERAL1 + +UBX_NMEA_MSB LITERAL1 +UBX_NMEA_DTM LITERAL1 +UBX_NMEA_GAQ LITERAL1 +UBX_NMEA_GBQ LITERAL1 +UBX_NMEA_GBS LITERAL1 +UBX_NMEA_GGA LITERAL1 +UBX_NMEA_GLL LITERAL1 +UBX_NMEA_GLQ LITERAL1 +UBX_NMEA_GNQ LITERAL1 +UBX_NMEA_GNS LITERAL1 +UBX_NMEA_GPQ LITERAL1 +UBX_NMEA_GRS LITERAL1 +UBX_NMEA_GSA LITERAL1 +UBX_NMEA_GST LITERAL1 +UBX_NMEA_GSV LITERAL1 +UBX_NMEA_RMC LITERAL1 +UBX_NMEA_TXT LITERAL1 +UBX_NMEA_VLW LITERAL1 +UBX_NMEA_VTG LITERAL1 +UBX_NMEA_ZDA LITERAL1 + +UBX_NAV_ATT LITERAL1 +UBX_NAV_CLOCK LITERAL1 +UBX_NAV_DOP LITERAL1 +UBX_NAV_GEOFENCE LITERAL1 +UBX_NAV_HPPOSECEF LITERAL1 +UBX_NAV_HPPOSLLH LITERAL1 +UBX_NAV_ODO LITERAL1 +UBX_NAV_POSECEF LITERAL1 +UBX_NAV_PVT LITERAL1 +UBX_NAV_RELPOSNED LITERAL1 +UBX_NAV_RESETODO LITERAL1 +UBX_NAV_STATUS LITERAL1 +UBX_NAV_SVIN LITERAL1 +UBX_NAV_VELECEF LITERAL1 +UBX_NAV_VELNED LITERAL1 + +UBX_RXM_RAWX LITERAL1 +UBX_RXM_SFRBX LITERAL1 + +UBX_TIM_TM2 LITERAL1 + +UBX_RTCM_MSB LITERAL1 +UBX_RTCM_1005 LITERAL1 +UBX_RTCM_1074 LITERAL1 +UBX_RTCM_1077 LITERAL1 +UBX_RTCM_1084 LITERAL1 +UBX_RTCM_1087 LITERAL1 +UBX_RTCM_1094 LITERAL1 +UBX_RTCM_1097 LITERAL1 +UBX_RTCM_1124 LITERAL1 +UBX_RTCM_1127 LITERAL1 +UBX_RTCM_1230 LITERAL1 +UBX_RTCM_4072_0 LITERAL1 +UBX_RTCM_4072_1 LITERAL1 + +UBX_ESF_MEAS LITERAL1 +UBX_ESF_RAW LITERAL1 +UBX_ESF_STATUS LITERAL1 +UBX_ESF_ALG LITERAL1 +UBX_ESF_INS LITERAL1 + +VAL_CFG_SUBSEC_IOPORT LITERAL1 +VAL_CFG_SUBSEC_MSGCONF LITERAL1 +VAL_CFG_SUBSEC_INFMSG LITERAL1 +VAL_CFG_SUBSEC_NAVCONF LITERAL1 +VAL_CFG_SUBSEC_RXMCONF LITERAL1 +VAL_CFG_SUBSEC_SENCONF LITERAL1 +VAL_CFG_SUBSEC_RINVCONF LITERAL1 +VAL_CFG_SUBSEC_ANTCONF LITERAL1 +VAL_CFG_SUBSEC_LOGCONF LITERAL1 +VAL_CFG_SUBSEC_FTSCONF LITERAL1 + +DYN_MODEL_PORTABLE LITERAL1 +DYN_MODEL_STATIONARY LITERAL1 +DYN_MODEL_PEDESTRIAN LITERAL1 +DYN_MODEL_AUTOMOTIVE LITERAL1 +DYN_MODEL_SEA LITERAL1 +DYN_MODEL_AIRBORNE1g LITERAL1 +DYN_MODEL_AIRBORNE2g LITERAL1 +DYN_MODEL_AIRBORNE4g LITERAL1 +DYN_MODEL_WRIST LITERAL1 +DYN_MODEL_BIKE LITERAL1 +DYN_MODEL_UNKNOWN LITERAL1 + +SFE_UBLOX_GNSS_ID_GPS LITERAL1 +SFE_UBLOX_GNSS_ID_SBAS LITERAL1 +SFE_UBLOX_GNSS_ID_GALILEO LITERAL1 +SFE_UBLOX_GNSS_ID_BEIDOU LITERAL1 +SFE_UBLOX_GNSS_ID_IMES LITERAL1 +SFE_UBLOX_GNSS_ID_QZSS LITERAL1 +SFE_UBLOX_GNSS_ID_GLONASS LITERAL1 diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..9ccd064 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=SparkFun u-blox Arduino Library +version=2.0.0 +author=SparkFun Electronics +maintainer=SparkFun Electronics +sentence=Library for I2C and Serial Communication with u-blox modules +paragraph=An Arduino Library to enable both I2C and Serial communication for both NMEA reception and binary UBX sending to u-blox modules. Useful for interfacing to the SparkFun GPS-RTK2 ZED-F9P, SparkFun GPS-RTK NEO-M8P-2, the SparkFun SAM-M8Q, and the SparkFun ZEO-M8Q. Library also works with other u-blox based boards.

The ZED-F9P and NEO-M8P-2 modules are top-of-the-line modules for high accuracy GNSS and GPS location solutions including RTK. The ZED-F9P is unique in that it is capable of both rover and base station operations allowing the module to become a base station and produce RTCM 3.x correction data. +category=Sensors +url=https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library +architectures=* diff --git a/src/SparkFun_Ublox_Arduino_Library.cpp b/src/SparkFun_Ublox_Arduino_Library.cpp new file mode 100644 index 0000000..31dfb3b --- /dev/null +++ b/src/SparkFun_Ublox_Arduino_Library.cpp @@ -0,0 +1,9737 @@ +/* + This is a library written for the u-blox ZED-F9P and NEO-M8P-2 + SparkFun sells these at its website: www.sparkfun.com + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/16481 + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Original version by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + v2.0 rework by Paul Clark @ SparkFun Electronics, December 31st, 2020 + + This library handles configuring and handling the responses + from a u-blox GPS module. Works with most modules from u-blox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + Arduino IDE 1.8.13 + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "SparkFun_Ublox_Arduino_Library.h" + +SFE_UBLOX_GPS::SFE_UBLOX_GPS(void) +{ + // Constructor + if (debugPin >= 0) + { + pinMode((uint8_t)debugPin, OUTPUT); + digitalWrite((uint8_t)debugPin, HIGH); + } +} + +//Allow the user to change packetCfgPayloadSize. Handy if you want to process big messages like RAWX +//This can be called before .begin if required / desired +void SFE_UBLOX_GPS::setPacketCfgPayloadSize(size_t payloadSize) +{ + if ((payloadSize == 0) && (payloadCfg != NULL)) + { + // Zero payloadSize? Dangerous! But we'll free the memory anyway... + delete[] payloadCfg; + payloadCfg = NULL; // Redundant? + packetCfg.payload = payloadCfg; + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setPacketCfgPayloadSize: Zero payloadSize! This will end _very_ badly...")); + } + + else if (payloadCfg == NULL) //Memory has not yet been allocated - so use new + { + payloadCfg = new uint8_t[payloadSize]; + packetCfg.payload = payloadCfg; + if (payloadCfg == NULL) + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setPacketCfgPayloadSize: PANIC! RAM allocation failed! This will end _very_ badly...")); + } + + else //Memory has already been allocated - so resize + { + uint8_t *newPayload = new uint8_t[payloadSize]; + for (size_t i = 0; (i < payloadSize) && (i < packetCfgPayloadSize); i++) // Copy as much existing data as we can + newPayload[i] = payloadCfg[i]; + delete[] payloadCfg; + payloadCfg = newPayload; + packetCfg.payload = payloadCfg; + if (payloadCfg == NULL) + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setPacketCfgPayloadSize: PANIC! RAM resize failed! This will end _very_ badly...")); + } + + packetCfgPayloadSize = payloadSize; +} + +//Initialize the I2C port +boolean SFE_UBLOX_GPS::begin(TwoWire &wirePort, uint8_t deviceAddress) +{ + commType = COMM_TYPE_I2C; + _i2cPort = &wirePort; //Grab which port the user wants us to use + + //We expect caller to begin their I2C port, with the speed of their choice external to the library + //But if they forget, we start the hardware here. + + //We're moving away from the practice of starting Wire hardware in a library. This is to avoid cross platform issues. + //ie, there are some platforms that don't handle multiple starts to the wire hardware. Also, every time you start the wire + //hardware the clock speed reverts back to 100kHz regardless of previous Wire.setClocks(). + //_i2cPort->begin(); + + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + + //New in v2.0: allocate memory for the packetCfg payload here - if required. (The user may have called setPacketCfgPayloadSize already) + if (packetCfgPayloadSize == 0) + setPacketCfgPayloadSize(MAX_PAYLOAD_SIZE); + + //New in v2.0: allocate memory for the file buffer - if required. (The user should have called setFileBufferSize already) + createFileBuffer(); + + // Call isConnected up to three times - tests on the NEO-M8U show the CFG RATE poll occasionally being ignored + boolean connected = isConnected(); + + if (!connected) + connected = isConnected(); + + if (!connected) + connected = isConnected(); + + return (connected); +} + +//Initialize the Serial port +boolean SFE_UBLOX_GPS::begin(Stream &serialPort) +{ + commType = COMM_TYPE_SERIAL; + _serialPort = &serialPort; //Grab which port the user wants us to use + + //New in v2.0: allocate memory for the packetCfg payload here - if required. (The user may have called setPacketCfgPayloadSize already) + if (packetCfgPayloadSize == 0) + setPacketCfgPayloadSize(MAX_PAYLOAD_SIZE); + + //New in v2.0: allocate memory for the file buffer - if required. (The user should have called setFileBufferSize already) + createFileBuffer(); + + // Call isConnected up to three times - tests on the NEO-M8U show the CFG RATE poll occasionally being ignored + boolean connected = isConnected(); + + if (!connected) + connected = isConnected(); + + if (!connected) + connected = isConnected(); + + return (connected); +} + +// Allow the user to change I2C polling wait (the minimum interval between I2C data requests - to avoid pounding the bus) +// i2cPollingWait defaults to 100ms and is adjusted automatically when setNavigationFrequency() +// or setHNRNavigationRate() are called. But if the user is using callbacks, it might be advantageous +// to be able to set the polling wait manually. +void SFE_UBLOX_GPS::setI2CpollingWait(uint8_t newPollingWait_ms) +{ + i2cPollingWait = newPollingWait_ms; +} + +//Sets the global size for I2C transactions +//Most platforms use 32 bytes (the default) but this allows users to increase the transaction +//size if the platform supports it +//Note: If the transaction size is set larger than the platforms buffer size, bad things will happen. +void SFE_UBLOX_GPS::setI2CTransactionSize(uint8_t transactionSize) +{ + i2cTransactionSize = transactionSize; +} +uint8_t SFE_UBLOX_GPS::getI2CTransactionSize(void) +{ + return (i2cTransactionSize); +} + +//Returns true if I2C device ack's +boolean SFE_UBLOX_GPS::isConnected(uint16_t maxWait) +{ + if (commType == COMM_TYPE_I2C) + { + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + if (_i2cPort->endTransmission() != 0) + return false; //Sensor did not ack + } + + // Query navigation rate to see whether we get a meaningful response + return (getNavigationFrequencyInternal(maxWait)); +} + +//Enable or disable the printing of sent/response HEX values. +//Use this in conjunction with 'Transport Logging' from the Universal Reader Assistant to see what they're doing that we're not +void SFE_UBLOX_GPS::enableDebugging(Stream &debugPort, boolean printLimitedDebug) +{ + _debugSerial = &debugPort; //Grab which port the user wants us to use for debugging + if (printLimitedDebug == false) + { + _printDebug = true; //Should we print the commands we send? Good for debugging + } + else + { + _printLimitedDebug = true; //Should we print limited debug messages? Good for debugging high navigation rates + } +} +void SFE_UBLOX_GPS::disableDebugging(void) +{ + _printDebug = false; //Turn off extra print statements + _printLimitedDebug = false; +} + +//Safely print messages +void SFE_UBLOX_GPS::debugPrint(char *message) +{ + if (_printDebug == true) + { + _debugSerial->print(message); + } +} +//Safely print messages +void SFE_UBLOX_GPS::debugPrintln(char *message) +{ + if (_printDebug == true) + { + _debugSerial->println(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_NACK: + return "Command not acknowledged (NACK)"; + 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; + case SFE_UBLOX_STATUS_DATA_OVERWRITTEN: + return "Data Packet Overwritten"; + break; + default: + return "Unknown Status"; + break; + } + return "None"; +} + +// Check for the arrival of new I2C/Serial data + +//Allow the user to disable the "7F" check (e.g.) when logging RAWX data +void SFE_UBLOX_GPS::disableUBX7Fcheck(boolean disabled) +{ + ubx7FcheckDisabled = disabled; +} + +//Called regularly to check for available bytes on the user' specified port +boolean SFE_UBLOX_GPS::checkUblox(uint8_t requestedClass, uint8_t requestedID) +{ + return checkUbloxInternal(&packetCfg, requestedClass, requestedID); +} + +//PRIVATE: Called regularly to check for available bytes on the user' specified port +boolean SFE_UBLOX_GPS::checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (commType == COMM_TYPE_I2C) + return (checkUbloxI2C(incomingUBX, requestedClass, requestedID)); + else if (commType == COMM_TYPE_SERIAL) + return (checkUbloxSerial(incomingUBX, requestedClass, requestedID)); + return false; +} + +//Polls I2C for data, passing any new bytes to process() +//Returns true if new bytes are available +boolean SFE_UBLOX_GPS::checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if (millis() - lastCheck >= i2cPollingWait) + { + //Get the number of bytes available from the module + uint16_t bytesAvailable = 0; + _i2cPort->beginTransmission(_gpsI2Caddress); + _i2cPort->write(0xFD); //0xFD (MSB) and 0xFE (LSB) are the registers that contain number of bytes available + if (_i2cPort->endTransmission(false) != 0) //Send a restart command. Do not release bus. + return (false); //Sensor did not ACK + + _i2cPort->requestFrom((uint8_t)_gpsI2Caddress, (uint8_t)2); + if (_i2cPort->available()) + { + uint8_t msb = _i2cPort->read(); + uint8_t lsb = _i2cPort->read(); + if (lsb == 0xFF) + { + //I believe this is a u-blox bug. Device should never present an 0xFF. + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("checkUbloxI2C: u-blox bug, length lsb is 0xFF")); + } + if (debugPin >= 0) + { + digitalWrite((uint8_t)debugPin, LOW); + delay(10); + digitalWrite((uint8_t)debugPin, HIGH); + } + lastCheck = millis(); //Put off checking to avoid I2C bus traffic + return (false); + } + bytesAvailable = (uint16_t)msb << 8 | lsb; + } + + if (bytesAvailable == 0) + { + if (_printDebug == true) + { + _debugSerial->println(F("checkUbloxI2C: OK, zero bytes available")); + } + lastCheck = millis(); //Put off checking to avoid I2C bus traffic + return (false); + } + + //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. 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 + bytesAvailable &= ~((uint16_t)1 << 15); + + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("checkUbloxI2C: Bytes available error: ")); + _debugSerial->println(bytesAvailable); + if (debugPin >= 0) + { + digitalWrite((uint8_t)debugPin, LOW); + delay(10); + digitalWrite((uint8_t)debugPin, HIGH); + } + } + } + + if (bytesAvailable > 100) + { + if (_printDebug == true) + { + _debugSerial->print(F("checkUbloxI2C: Large packet of ")); + _debugSerial->print(bytesAvailable); + _debugSerial->println(F(" bytes received")); + } + } + else + { + if (_printDebug == true) + { + _debugSerial->print(F("checkUbloxI2C: Reading ")); + _debugSerial->print(bytesAvailable); + _debugSerial->println(F(" bytes")); + } + } + + while (bytesAvailable) + { + _i2cPort->beginTransmission(_gpsI2Caddress); + _i2cPort->write(0xFF); //0xFF is the register to read data from + if (_i2cPort->endTransmission(false) != 0) //Send a restart command. Do not release bus. + return (false); //Sensor did not ACK + + //Limit to 32 bytes or whatever the buffer limit is for given platform + uint16_t bytesToRead = bytesAvailable; + if (bytesToRead > i2cTransactionSize) + bytesToRead = i2cTransactionSize; + + TRY_AGAIN: + + _i2cPort->requestFrom((uint8_t)_gpsI2Caddress, (uint8_t)bytesToRead); + if (_i2cPort->available()) + { + for (uint16_t x = 0; x < bytesToRead; x++) + { + uint8_t incoming = _i2cPort->read(); //Grab the actual character + + //Check to see if the first read is 0x7F. If it is, the module is not ready + //to respond. Stop, wait, and try again + if (x == 0) + { + if ((incoming == 0x7F) && (ubx7FcheckDisabled == false)) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("checkUbloxU2C: u-blox error, module not ready with data (7F error)")); + } + delay(5); //In logic analyzation, the module starting responding after 1.48ms + if (debugPin >= 0) + { + digitalWrite((uint8_t)debugPin, LOW); + delay(10); + digitalWrite((uint8_t)debugPin, HIGH); + } + goto TRY_AGAIN; + } + } + + process(incoming, incomingUBX, requestedClass, requestedID); //Process this valid character + } + } + else + return (false); //Sensor did not respond + + bytesAvailable -= bytesToRead; + } + } + + return (true); + +} //end checkUbloxI2C() + +//Checks Serial for data, passing any new bytes to process() +boolean SFE_UBLOX_GPS::checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + while (_serialPort->available()) + { + process(_serialPort->read(), incomingUBX, requestedClass, requestedID); + } + return (true); + +} //end checkUbloxSerial() + +//PRIVATE: Check if we have storage allocated for an incoming "automatic" message +boolean SFE_UBLOX_GPS::checkAutomatic(uint8_t Class, uint8_t ID) +{ + boolean result = false; + switch (Class) + { + case UBX_CLASS_NAV: + { + switch (ID) + { + case UBX_NAV_POSECEF: + if (packetUBXNAVPOSECEF != NULL) result = true; + break; + case UBX_NAV_STATUS: + if (packetUBXNAVSTATUS != NULL) result = true; + break; + case UBX_NAV_DOP: + if (packetUBXNAVDOP != NULL) result = true; + break; + case UBX_NAV_ATT: + if (packetUBXNAVATT != NULL) result = true; + break; + case UBX_NAV_PVT: + if (packetUBXNAVPVT != NULL) result = true; + break; + case UBX_NAV_ODO: + if (packetUBXNAVODO != NULL) result = true; + break; + case UBX_NAV_VELECEF: + if (packetUBXNAVVELECEF != NULL) result = true; + break; + case UBX_NAV_VELNED: + if (packetUBXNAVVELNED != NULL) result = true; + break; + case UBX_NAV_HPPOSECEF: + if (packetUBXNAVHPPOSECEF != NULL) result = true; + break; + case UBX_NAV_HPPOSLLH: + if (packetUBXNAVHPPOSLLH != NULL) result = true; + break; + case UBX_NAV_CLOCK: + if (packetUBXNAVCLOCK != NULL) result = true; + break; + case UBX_NAV_SVIN: + if (packetUBXNAVSVIN != NULL) result = true; + break; + case UBX_NAV_RELPOSNED: + if (packetUBXNAVRELPOSNED != NULL) result = true; + break; + } + } + break; + case UBX_CLASS_RXM: + { + switch (ID) + { + case UBX_RXM_SFRBX: + if (packetUBXRXMSFRBX != NULL) result = true; + break; + case UBX_RXM_RAWX: + if (packetUBXRXMRAWX != NULL) result = true; + break; + } + } + break; + case UBX_CLASS_CFG: + { + switch (ID) + { + case UBX_CFG_RATE: + if (packetUBXCFGRATE != NULL) result = true; + break; + } + } + break; + case UBX_CLASS_TIM: + { + switch (ID) + { + case UBX_TIM_TM2: + if (packetUBXTIMTM2 != NULL) result = true; + break; + } + } + break; + case UBX_CLASS_ESF: + { + switch (ID) + { + case UBX_ESF_ALG: + if (packetUBXESFALG != NULL) result = true; + break; + case UBX_ESF_INS: + if (packetUBXESFINS != NULL) result = true; + break; + case UBX_ESF_MEAS: + if (packetUBXESFMEAS != NULL) result = true; + break; + case UBX_ESF_RAW: + if (packetUBXESFRAW != NULL) result = true; + break; + case UBX_ESF_STATUS: + if (packetUBXESFSTATUS != NULL) result = true; + break; + } + } + break; + case UBX_CLASS_HNR: + { + switch (ID) + { + case UBX_HNR_PVT: + if (packetUBXHNRPVT != NULL) result = true; + break; + case UBX_HNR_ATT: + if (packetUBXHNRATT != NULL) result = true; + break; + case UBX_HNR_INS: + if (packetUBXHNRINS != NULL) result = true; + break; + } + } + break; + } + return (result); +} + +//PRIVATE: Calculate how much RAM is needed to store the payload for a given automatic message +uint16_t SFE_UBLOX_GPS::getMaxPayloadSize(uint8_t Class, uint8_t ID) +{ + uint16_t maxSize = 0; + switch (Class) + { + case UBX_CLASS_NAV: + { + switch (ID) + { + case UBX_NAV_POSECEF: + maxSize = UBX_NAV_POSECEF_LEN; + break; + case UBX_NAV_STATUS: + maxSize = UBX_NAV_STATUS_LEN; + break; + case UBX_NAV_DOP: + maxSize = UBX_NAV_DOP_LEN; + break; + case UBX_NAV_ATT: + maxSize = UBX_NAV_ATT_LEN; + break; + case UBX_NAV_PVT: + maxSize = UBX_NAV_PVT_LEN; + break; + case UBX_NAV_ODO: + maxSize = UBX_NAV_ODO_LEN; + break; + case UBX_NAV_VELECEF: + maxSize = UBX_NAV_VELECEF_LEN; + break; + case UBX_NAV_VELNED: + maxSize = UBX_NAV_VELNED_LEN; + break; + case UBX_NAV_HPPOSECEF: + maxSize = UBX_NAV_HPPOSECEF_LEN; + break; + case UBX_NAV_HPPOSLLH: + maxSize = UBX_NAV_HPPOSLLH_LEN; + break; + case UBX_NAV_CLOCK: + maxSize = UBX_NAV_CLOCK_LEN; + break; + case UBX_NAV_SVIN: + maxSize = UBX_NAV_SVIN_LEN; + break; + case UBX_NAV_RELPOSNED: + maxSize = UBX_NAV_RELPOSNED_LEN_F9; + break; + } + } + break; + case UBX_CLASS_RXM: + { + switch (ID) + { + case UBX_RXM_SFRBX: + maxSize = UBX_RXM_SFRBX_MAX_LEN; + break; + case UBX_RXM_RAWX: + maxSize = UBX_RXM_RAWX_MAX_LEN; + break; + } + } + break; + case UBX_CLASS_CFG: + { + switch (ID) + { + case UBX_CFG_RATE: + maxSize = UBX_CFG_RATE_LEN; + break; + } + } + break; + case UBX_CLASS_TIM: + { + switch (ID) + { + case UBX_TIM_TM2: + maxSize = UBX_TIM_TM2_LEN; + break; + } + } + break; + case UBX_CLASS_ESF: + { + switch (ID) + { + case UBX_ESF_ALG: + maxSize = UBX_ESF_ALG_LEN; + break; + case UBX_ESF_INS: + maxSize = UBX_ESF_INS_LEN; + break; + case UBX_ESF_MEAS: + maxSize = UBX_ESF_MEAS_MAX_LEN; + break; + case UBX_ESF_RAW: + maxSize = UBX_ESF_RAW_MAX_LEN; + break; + case UBX_ESF_STATUS: + maxSize = UBX_ESF_STATUS_MAX_LEN; + break; + } + } + break; + case UBX_CLASS_HNR: + { + switch (ID) + { + case UBX_HNR_PVT: + maxSize = UBX_HNR_PVT_LEN; + break; + case UBX_HNR_ATT: + maxSize = UBX_HNR_ATT_LEN; + break; + case UBX_HNR_INS: + maxSize = UBX_HNR_INS_LEN; + break; + } + } + break; + } + return (maxSize); +} + +//Processes NMEA and UBX binary sentences one byte at a time +//Take a given byte and file it into the proper array +void SFE_UBLOX_GPS::process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + if ((currentSentence == NONE) || (currentSentence == NMEA)) + { + if (incoming == 0xB5) //UBX binary frames start with 0xB5, aka μ + { + //This is the start of a binary sentence. Reset flags. + //We still don't know the response class + ubxFrameCounter = 0; + currentSentence = UBX; + //Reset the packetBuf.counter even though we will need to reset it again when ubxFrameCounter == 2 + packetBuf.counter = 0; + ignoreThisPayload = false; //We should not ignore this payload - yet + //Store data in packetBuf until we know if we have a requested class and ID match + activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + } + else if (incoming == '$') + { + currentSentence = NMEA; + } + else if (incoming == 0xD3) //RTCM frames start with 0xD3 + { + rtcmFrameCounter = 0; + currentSentence = RTCM; + } + else + { + //This character is unknown or we missed the previous start of a sentence + } + } + + //Depending on the sentence, pass the character to the individual processor + if (currentSentence == UBX) + { + //Decide what type of response this is + if ((ubxFrameCounter == 0) && (incoming != 0xB5)) //ISO 'μ' + currentSentence = NONE; //Something went wrong. Reset. + else if ((ubxFrameCounter == 1) && (incoming != 0x62)) //ASCII 'b' + currentSentence = NONE; //Something went wrong. Reset. + // Note to future self: + // There may be some duplication / redundancy in the next few lines as processUBX will also + // load information into packetBuf, but we'll do it here too for clarity + else if (ubxFrameCounter == 2) //Class + { + // Record the class in packetBuf until we know what to do with it + packetBuf.cls = incoming; // (Duplication) + rollingChecksumA = 0; //Reset our rolling checksums here (not when we receive the 0xB5) + rollingChecksumB = 0; + packetBuf.counter = 0; //Reset the packetBuf.counter (again) + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // Reset the packet validity (redundant?) + packetBuf.startingSpot = incomingUBX->startingSpot; //Copy the startingSpot + } + else if (ubxFrameCounter == 3) //ID + { + // Record the ID in packetBuf until we know what to do with it + packetBuf.id = incoming; // (Duplication) + //We can now identify the type of response + //If the packet we are receiving is not an ACK then check for a class and ID match + if (packetBuf.cls != UBX_CLASS_ACK) + { + //This is not an ACK so check for a class and ID match + if ((packetBuf.cls == requestedClass) && (packetBuf.id == requestedID)) + { + //This is not an ACK and we have a class and ID match + //So start diverting data into incomingUBX (usually packetCfg) + activePacketBuffer = SFE_UBLOX_PACKET_PACKETCFG; + incomingUBX->cls = packetBuf.cls; //Copy the class and ID into incomingUBX (usually packetCfg) + incomingUBX->id = packetBuf.id; + incomingUBX->counter = packetBuf.counter; //Copy over the .counter too + } + //This is not an ACK and we do not have a complete class and ID match + //So let's check if this is an "automatic" message which has its own storage defined + else if (checkAutomatic(packetBuf.cls, packetBuf.id)) + { + //This is not the message we were expecting but it has its own storage and so we should process it anyway. + //We'll try to use packetAuto to buffer the message (so it can't overwrite anything in packetCfg). + //We need to allocate memory for the packetAuto payload (payloadAuto) - and delete it once + //reception is complete. + uint16_t maxPayload = getMaxPayloadSize(packetBuf.cls, packetBuf.id); // Calculate how much RAM we need + if (maxPayload == 0) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("process: getMaxPayloadSize returned ZERO!! Class: 0x")); + _debugSerial->print(packetBuf.cls); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->println(packetBuf.id); + } + } + if (payloadAuto != NULL) // Check if memory is already allocated - this should be impossible! + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("process: memory is already allocated for payloadAuto! Deleting...")); + } + delete[] payloadAuto; + payloadAuto = NULL; // Redundant? + packetAuto.payload = payloadAuto; + } + payloadAuto = new uint8_t[maxPayload]; // Allocate RAM for payloadAuto + packetAuto.payload = payloadAuto; + if (payloadAuto == NULL) // Check if the alloc failed + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("process: memory allocation failed for \"automatic\" message: Class: 0x")); + _debugSerial->print(packetBuf.cls, HEX); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->println(packetBuf.id, HEX); + _debugSerial->println(F("process: \"automatic\" message could overwrite data")); + } + // The RAM allocation failed so fall back to using incomingUBX (usually packetCfg) even though we risk overwriting data + activePacketBuffer = SFE_UBLOX_PACKET_PACKETCFG; + incomingUBX->cls = packetBuf.cls; //Copy the class and ID into incomingUBX (usually packetCfg) + incomingUBX->id = packetBuf.id; + incomingUBX->counter = packetBuf.counter; //Copy over the .counter too + } + else + { + //The RAM allocation was successful so we start diverting data into packetAuto and process it + activePacketBuffer = SFE_UBLOX_PACKET_PACKETAUTO; + packetAuto.cls = packetBuf.cls; //Copy the class and ID into packetAuto + packetAuto.id = packetBuf.id; + packetAuto.counter = packetBuf.counter; //Copy over the .counter too + packetAuto.startingSpot = packetBuf.startingSpot; //And the starting spot? (Probably redundant) + if (_printDebug == true) + { + _debugSerial->print(F("process: incoming \"automatic\" message: Class: 0x")); + _debugSerial->print(packetBuf.cls, HEX); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->println(packetBuf.id, HEX); + } + } + } + else + { + //This is not an ACK and we do not have a class and ID match + //so we should keep diverting data into packetBuf and ignore the payload + ignoreThisPayload = true; + } + } + else + { + // This is an ACK so it is to early to do anything with it + // We need to wait until we have received the length and data bytes + // So we should keep diverting data into packetBuf + } + } + else if (ubxFrameCounter == 4) //Length LSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len = incoming; // (Duplication) + } + else if (ubxFrameCounter == 5) //Length MSB + { + //We should save the length in packetBuf even if activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG + packetBuf.len |= incoming << 8; // (Duplication) + } + else if (ubxFrameCounter == 6) //This should be the first byte of the payload unless .len is zero + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("process: ZERO LENGTH packet received: Class: 0x")); + _debugSerial->print(packetBuf.cls, HEX); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->println(packetBuf.id, HEX); + } + //If length is zero (!) this will be the first byte of the checksum so record it + packetBuf.checksumA = incoming; + } + else + { + //The length is not zero so record this byte in the payload + packetBuf.payload[0] = incoming; + } + } + else if (ubxFrameCounter == 7) //This should be the second byte of the payload unless .len is zero or one + { + if (packetBuf.len == 0) // Check if length is zero (hopefully this is impossible!) + { + //If length is zero (!) this will be the second byte of the checksum so record it + packetBuf.checksumB = incoming; + } + else if (packetBuf.len == 1) // Check if length is one + { + //The length is one so this is the first byte of the checksum + packetBuf.checksumA = incoming; + } + else // Length is >= 2 so this must be a payload byte + { + packetBuf.payload[1] = incoming; + } + // Now that we have received two payload bytes, we can check for a matching ACK/NACK + if ((activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) // If we are not already processing a data packet + && (packetBuf.cls == UBX_CLASS_ACK) // and if this is an ACK/NACK + && (packetBuf.payload[0] == requestedClass) // and if the class matches + && (packetBuf.payload[1] == requestedID)) // and if the ID matches + { + if (packetBuf.len == 2) // Check if .len is 2 + { + // Then this is a matching ACK so copy it into packetAck + activePacketBuffer = SFE_UBLOX_PACKET_PACKETACK; + packetAck.cls = packetBuf.cls; + packetAck.id = packetBuf.id; + packetAck.len = packetBuf.len; + packetAck.counter = packetBuf.counter; + packetAck.payload[0] = packetBuf.payload[0]; + packetAck.payload[1] = packetBuf.payload[1]; + } + else // Length is not 2 (hopefully this is impossible!) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("process: ACK received with .len != 2: Class: 0x")); + _debugSerial->print(packetBuf.payload[0], HEX); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->print(packetBuf.payload[1], HEX); + _debugSerial->print(F(" len: ")); + _debugSerial->println(packetBuf.len); + } + } + } + } + + //Divert incoming into the correct buffer + if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETACK) + processUBX(incoming, &packetAck, requestedClass, requestedID); + else if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG) + processUBX(incoming, incomingUBX, requestedClass, requestedID); + else if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETBUF) + processUBX(incoming, &packetBuf, requestedClass, requestedID); + else // if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETAUTO) + processUBX(incoming, &packetAuto, requestedClass, requestedID); + + //Finally, increment the frame counter + ubxFrameCounter++; + } + else if (currentSentence == NMEA) + { + processNMEA(incoming); //Process each NMEA character + } + else if (currentSentence == RTCM) + { + processRTCMframe(incoming); //Deal with RTCM bytes + } +} + +//This is the default or generic NMEA processor. We're only going to pipe the data to serial port so we can see it. +//User could overwrite this function to pipe characters to nmea.process(c) of tinyGPS or MicroNMEA +//Or user could pipe each character to a buffer, radio, etc. +void SFE_UBLOX_GPS::processNMEA(char incoming) +{ + //If user has assigned an output port then pipe the characters there + if (_nmeaOutputPort != NULL) + _nmeaOutputPort->write(incoming); //Echo this byte to the serial port +} + +//We need to be able to identify an RTCM packet and then the length +//so that we know when the RTCM message is completely received and we then start +//listening for other sentences (like NMEA or UBX) +//RTCM packet structure is very odd. I never found RTCM STANDARD 10403.2 but +//http://d1.amobbs.com/bbs_upload782111/files_39/ourdev_635123CK0HJT.pdf is good +//https://dspace.cvut.cz/bitstream/handle/10467/65205/F3-BP-2016-Shkalikava-Anastasiya-Prenos%20polohove%20informace%20prostrednictvim%20datove%20site.pdf?sequence=-1 +//Lead me to: https://forum.u-blox.com/index.php/4348/how-to-read-rtcm-messages-from-neo-m8p +//RTCM 3.2 bytes look like this: +//Byte 0: Always 0xD3 +//Byte 1: 6-bits of zero +//Byte 2: 10-bits of length of this packet including the first two-ish header bytes, + 6. +//byte 3 + 4 bits: Msg type 12 bits +//Example: D3 00 7C 43 F0 ... / 0x7C = 124+6 = 130 bytes in this packet, 0x43F = Msg type 1087 +void SFE_UBLOX_GPS::processRTCMframe(uint8_t incoming) +{ + if (rtcmFrameCounter == 1) + { + rtcmLen = (incoming & 0x03) << 8; //Get the last two bits of this byte. Bits 8&9 of 10-bit length + } + else if (rtcmFrameCounter == 2) + { + rtcmLen |= incoming; //Bits 0-7 of packet length + rtcmLen += 6; //There are 6 additional bytes of what we presume is header, msgType, CRC, and stuff + } + /*else if (rtcmFrameCounter == 3) + { + rtcmMsgType = incoming << 4; //Message Type, MS 4 bits + } + else if (rtcmFrameCounter == 4) + { + rtcmMsgType |= (incoming >> 4); //Message Type, bits 0-7 + }*/ + + rtcmFrameCounter++; + + processRTCM(incoming); //Here is where we expose this byte to the user + + if (rtcmFrameCounter == rtcmLen) + { + //We're done! + currentSentence = NONE; //Reset and start looking for next sentence type + } +} + +//This function is called for each byte of an RTCM frame +//Ths user can overwrite this function and process the RTCM frame as they please +//Bytes can be piped to Serial or other interface. The consumer could be a radio or the internet (Ntrip broadcaster) +void SFE_UBLOX_GPS::processRTCM(uint8_t incoming) +{ + //Radio.sendReliable((String)incoming); //An example of passing this byte to a radio + + //_debugSerial->write(incoming); //An example of passing this byte out the serial port + + //Debug printing + // _debugSerial->print(F(" ")); + // if(incoming < 0x10) _debugSerial->print(F("0")); + // if(incoming < 0x10) _debugSerial->print(F("0")); + // _debugSerial->print(incoming, HEX); + // if(rtcmFrameCounter % 16 == 0) _debugSerial->println(); +} + +//Given a character, file it away into the uxb packet structure +//Set valid to VALID or NOT_VALID once sentence is completely received and passes or fails CRC +//The payload portion of the packet can be 100s of bytes but the max array size is packetCfgPayloadSize bytes. +//startingSpot can be set so we only record a subset of bytes within a larger packet. +void SFE_UBLOX_GPS::processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID) +{ + //If incomingUBX is a user-defined custom packet, then the payload size could be different to packetCfgPayloadSize. + //TO DO: update this to prevent an overrun when receiving an automatic message + // and the incomingUBX payload size is smaller than packetCfgPayloadSize. + size_t maximum_payload_size; + if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETCFG) + maximum_payload_size = packetCfgPayloadSize; + else if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETAUTO) + { + // Calculate maximum payload size once Class and ID have been received + // (This check is probably redundant as activePacketBuffer can only be SFE_UBLOX_PACKET_PACKETAUTO + // when ubxFrameCounter >= 3) + //if (incomingUBX->counter >= 2) + //{ + maximum_payload_size = getMaxPayloadSize(incomingUBX->cls, incomingUBX->id); + if (maximum_payload_size == 0) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->print(F("processUBX: getMaxPayloadSize returned ZERO!! Class: 0x")); + _debugSerial->print(incomingUBX->cls); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->println(incomingUBX->id); + } + } + //} + //else + // maximum_payload_size = 2; + } + else + maximum_payload_size = 2; + + bool overrun = false; + + //Add all incoming bytes to the rolling checksum + //Stop at len+4 as this is the checksum bytes to that should not be added to the rolling checksum + if (incomingUBX->counter < incomingUBX->len + 4) + addToChecksum(incoming); + + if (incomingUBX->counter == 0) + { + incomingUBX->cls = incoming; + } + else if (incomingUBX->counter == 1) + { + incomingUBX->id = incoming; + } + else if (incomingUBX->counter == 2) //Len LSB + { + incomingUBX->len = incoming; + } + else if (incomingUBX->counter == 3) //Len MSB + { + incomingUBX->len |= incoming << 8; + } + else if (incomingUBX->counter == incomingUBX->len + 4) //ChecksumA + { + incomingUBX->checksumA = incoming; + } + else if (incomingUBX->counter == incomingUBX->len + 5) //ChecksumB + { + incomingUBX->checksumB = incoming; + + currentSentence = NONE; //We're done! Reset the sentence to being looking for a new start char + + //Validate this sentence + if ((incomingUBX->checksumA == rollingChecksumA) && (incomingUBX->checksumB == rollingChecksumB)) + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_VALID; // Flag the packet as valid + + // Let's check if the class and ID match the requestedClass and requestedID + // Remember - this could be a data packet or an ACK packet + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_VALID; // If we have a match, set the classAndIDmatch flag to valid + } + + // If this is a NACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->id == UBX_ACK_NACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_NOTACKNOWLEDGED; // If we have a match, set the classAndIDmatch flag to NOTACKNOWLEDGED + if (_printDebug == true) + { + _debugSerial->print(F("processUBX: NACK received: Requested Class: 0x")); + _debugSerial->print(incomingUBX->payload[0], HEX); + _debugSerial->print(F(" Requested ID: 0x")); + _debugSerial->println(incomingUBX->payload[1], HEX); + } + } + + //This is not an ACK and we do not have a complete class and ID match + //So let's check for an "automatic" message arriving + else if (checkAutomatic(incomingUBX->cls, incomingUBX->id)) + { + // This isn't the message we are looking for... + // Let's say so and leave incomingUBX->classAndIDmatch _unchanged_ + if (_printDebug == true) + { + _debugSerial->print(F("processUBX: incoming \"automatic\" message: Class: 0x")); + _debugSerial->print(incomingUBX->cls, HEX); + _debugSerial->print(F(" ID: 0x")); + _debugSerial->println(incomingUBX->id, HEX); + } + } + + if (_printDebug == true) + { + _debugSerial->print(F("Incoming: Size: ")); + _debugSerial->print(incomingUBX->len); + _debugSerial->print(F(" Received: ")); + printPacket(incomingUBX); + + if (incomingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetCfg now valid")); + } + if (packetAck.valid == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetAck now valid")); + } + if (incomingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetCfg classAndIDmatch")); + } + if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) + { + _debugSerial->println(F("packetAck classAndIDmatch")); + } + } + + //We've got a valid packet, now do something with it but only if ignoreThisPayload is false + if (ignoreThisPayload == false) + { + processUBXpacket(incomingUBX); + } + } + else // Checksum failure + { + incomingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; + + // Let's check if the class and ID match the requestedClass and requestedID. + // This is potentially risky as we are saying that we saw the requested Class and ID + // but that the packet checksum failed. Potentially it could be the class or ID bytes + // that caused the checksum error! + if ((incomingUBX->cls == requestedClass) && (incomingUBX->id == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + // If this is an ACK then let's check if the class and ID match the requestedClass and requestedID + else if ((incomingUBX->cls == UBX_CLASS_ACK) && (incomingUBX->payload[0] == requestedClass) && (incomingUBX->payload[1] == requestedID)) + { + incomingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_VALID; // If we have a match, set the classAndIDmatch flag to not valid + } + + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + //Drive an external pin to allow for easier logic analyzation + if (debugPin >= 0) + { + digitalWrite((uint8_t)debugPin, LOW); + delay(10); + digitalWrite((uint8_t)debugPin, HIGH); + } + + _debugSerial->print(F("Checksum failed:")); + _debugSerial->print(F(" checksumA: ")); + _debugSerial->print(incomingUBX->checksumA); + _debugSerial->print(F(" checksumB: ")); + _debugSerial->print(incomingUBX->checksumB); + + _debugSerial->print(F(" rollingChecksumA: ")); + _debugSerial->print(rollingChecksumA); + _debugSerial->print(F(" rollingChecksumB: ")); + _debugSerial->print(rollingChecksumB); + _debugSerial->println(); + } + } + + // Now that the packet is complete and has been processed, we need to delete the memory + // allocated for packetAuto + if (activePacketBuffer == SFE_UBLOX_PACKET_PACKETAUTO) + { + delete[] payloadAuto; + payloadAuto = NULL; // Redundant? + packetAuto.payload = payloadAuto; + } + } + else //Load this byte into the payload array + { + //If an automatic packet comes in asynchronously, we need to fudge the startingSpot + uint16_t startingSpot = incomingUBX->startingSpot; + if (checkAutomatic(incomingUBX->cls, incomingUBX->id)) + startingSpot = 0; + // Check if this is payload data which should be ignored + if (ignoreThisPayload == false) + { + //Begin recording if counter goes past startingSpot + if ((incomingUBX->counter - 4) >= startingSpot) + { + //Check to see if we have room for this byte + if (((incomingUBX->counter - 4) - startingSpot) < maximum_payload_size) //If counter = 208, starting spot = 200, we're good to record. + { + incomingUBX->payload[(incomingUBX->counter - 4) - startingSpot] = incoming; //Store this byte into payload array + } + else + { + overrun = true; + } + } + } + } + + // incomingUBX->counter should never reach maximum_payload_size + class + id + len[2] + checksum[2] + if (overrun || ((incomingUBX->counter == maximum_payload_size + 6) && (ignoreThisPayload == false))) + { + //Something has gone very wrong + currentSentence = NONE; //Reset the sentence to being looking for a new start char + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + if (overrun) + _debugSerial->print(F("processUBX: buffer overrun detected!")); + else + _debugSerial->print(F("processUBX: counter hit maximum_payload_size + 6!")); + _debugSerial->print(F(" activePacketBuffer: ")); + _debugSerial->print(activePacketBuffer); + _debugSerial->print(F(" maximum_payload_size: ")); + _debugSerial->println(maximum_payload_size); + } + } + + //Increment the counter + incomingUBX->counter++; +} + +//Once a packet has been received and validated, identify this packet's class/id and update internal flags +void SFE_UBLOX_GPS::processUBXpacket(ubxPacket *msg) +{ + switch (msg->cls) + { + case UBX_CLASS_NAV: + if (msg->id == UBX_NAV_POSECEF && msg->len == UBX_NAV_POSECEF_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVPOSECEF != NULL) + { + packetUBXNAVPOSECEF->data.iTOW = extractLong(msg, 0); + packetUBXNAVPOSECEF->data.ecefX = extractSignedLong(msg, 4); + packetUBXNAVPOSECEF->data.ecefY = extractSignedLong(msg, 8); + packetUBXNAVPOSECEF->data.ecefZ = extractSignedLong(msg, 12); + packetUBXNAVPOSECEF->data.pAcc = extractLong(msg, 16); + + //Mark all datums as fresh (not read before) + packetUBXNAVPOSECEF->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVPOSECEF->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVPOSECEF->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVPOSECEF->callbackData->iTOW, &packetUBXNAVPOSECEF->data.iTOW, sizeof(UBX_NAV_POSECEF_data_t)); + packetUBXNAVPOSECEF->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVPOSECEF->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_NAV_STATUS && msg->len == UBX_NAV_STATUS_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVSTATUS != NULL) + { + packetUBXNAVSTATUS->data.iTOW = extractLong(msg, 0); + packetUBXNAVSTATUS->data.gpsFix = extractByte(msg, 4); + packetUBXNAVSTATUS->data.flags.all = extractByte(msg, 5); + packetUBXNAVSTATUS->data.fixStat.all = extractByte(msg, 6); + packetUBXNAVSTATUS->data.flags2.all = extractByte(msg, 7); + packetUBXNAVSTATUS->data.ttff = extractLong(msg, 8); + packetUBXNAVSTATUS->data.msss = extractLong(msg, 12); + + //Mark all datums as fresh (not read before) + packetUBXNAVSTATUS->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVSTATUS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVSTATUS->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVSTATUS->callbackData->iTOW, &packetUBXNAVSTATUS->data.iTOW, sizeof(UBX_NAV_STATUS_data_t)); + packetUBXNAVSTATUS->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVSTATUS->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_NAV_DOP && msg->len == UBX_NAV_DOP_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVDOP != NULL) + { + packetUBXNAVDOP->data.iTOW = extractLong(msg, 0); + packetUBXNAVDOP->data.gDOP = extractInt(msg, 4); + packetUBXNAVDOP->data.pDOP = extractInt(msg, 6); + packetUBXNAVDOP->data.tDOP = extractInt(msg, 8); + packetUBXNAVDOP->data.vDOP = extractInt(msg, 10); + packetUBXNAVDOP->data.hDOP = extractInt(msg, 12); + packetUBXNAVDOP->data.nDOP = extractInt(msg, 14); + packetUBXNAVDOP->data.eDOP = extractInt(msg, 16); + + //Mark all datums as fresh (not read before) + packetUBXNAVDOP->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVDOP->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVDOP->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVDOP->callbackData->iTOW, &packetUBXNAVDOP->data.iTOW, sizeof(UBX_NAV_DOP_data_t)); + packetUBXNAVDOP->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVDOP->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_NAV_ATT && msg->len == UBX_NAV_ATT_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVATT != NULL) + { + packetUBXNAVATT->data.iTOW = extractLong(msg, 0); + packetUBXNAVATT->data.version = extractByte(msg, 4); + packetUBXNAVATT->data.roll = extractSignedLong(msg, 8); + packetUBXNAVATT->data.pitch = extractSignedLong(msg, 12); + packetUBXNAVATT->data.heading = extractSignedLong(msg, 16); + packetUBXNAVATT->data.accRoll = extractLong(msg, 20); + packetUBXNAVATT->data.accPitch = extractLong(msg, 24); + packetUBXNAVATT->data.accHeading = extractLong(msg, 28); + + //Mark all datums as fresh (not read before) + packetUBXNAVATT->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVATT->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVATT->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVATT->callbackData->iTOW, &packetUBXNAVATT->data.iTOW, sizeof(UBX_NAV_ATT_data_t)); + packetUBXNAVATT->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVATT->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_NAV_PVT && msg->len == UBX_NAV_PVT_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVPVT != NULL) + { + packetUBXNAVPVT->data.iTOW = extractLong(msg, 0); + packetUBXNAVPVT->data.year = extractInt(msg, 4); + packetUBXNAVPVT->data.month = extractByte(msg, 6); + packetUBXNAVPVT->data.day = extractByte(msg, 7); + packetUBXNAVPVT->data.hour = extractByte(msg, 8); + packetUBXNAVPVT->data.min = extractByte(msg, 9); + packetUBXNAVPVT->data.sec = extractByte(msg, 10); + packetUBXNAVPVT->data.valid.all = extractByte(msg, 11); + packetUBXNAVPVT->data.tAcc = extractLong(msg, 12); + packetUBXNAVPVT->data.nano = extractSignedLong(msg, 16); //Includes milliseconds + packetUBXNAVPVT->data.fixType = extractByte(msg, 20); + packetUBXNAVPVT->data.flags.all = extractByte(msg, 21); + packetUBXNAVPVT->data.flags2.all = extractByte(msg, 22); + packetUBXNAVPVT->data.numSV = extractByte(msg, 23); + packetUBXNAVPVT->data.lon = extractSignedLong(msg, 24); + packetUBXNAVPVT->data.lat = extractSignedLong(msg, 28); + packetUBXNAVPVT->data.height = extractSignedLong(msg, 32); + packetUBXNAVPVT->data.hMSL = extractSignedLong(msg, 36); + packetUBXNAVPVT->data.hAcc = extractLong(msg, 40); + packetUBXNAVPVT->data.vAcc = extractLong(msg, 44); + packetUBXNAVPVT->data.velN = extractSignedLong(msg, 48); + packetUBXNAVPVT->data.velE = extractSignedLong(msg, 52); + packetUBXNAVPVT->data.velD = extractSignedLong(msg, 56); + packetUBXNAVPVT->data.gSpeed = extractSignedLong(msg, 60); + packetUBXNAVPVT->data.headMot = extractSignedLong(msg, 64); + packetUBXNAVPVT->data.sAcc = extractLong(msg, 68); + packetUBXNAVPVT->data.headAcc = extractLong(msg, 72); + packetUBXNAVPVT->data.pDOP = extractInt(msg, 76); + packetUBXNAVPVT->data.flags3.all = extractByte(msg, 78); + packetUBXNAVPVT->data.headVeh = extractSignedLong(msg, 84); + packetUBXNAVPVT->data.magDec = extractSignedInt(msg, 88); + packetUBXNAVPVT->data.magAcc = extractInt(msg, 90); + + //Mark all datums as fresh (not read before) + packetUBXNAVPVT->moduleQueried.moduleQueried1.all = 0xFFFFFFFF; + packetUBXNAVPVT->moduleQueried.moduleQueried2.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVPVT->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVPVT->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVPVT->callbackData->iTOW, &packetUBXNAVPVT->data.iTOW, sizeof(UBX_NAV_PVT_data_t)); + packetUBXNAVPVT->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVPVT->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_NAV_ODO && msg->len == UBX_NAV_ODO_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVODO != NULL) + { + packetUBXNAVODO->data.version = extractByte(msg, 0); + packetUBXNAVODO->data.iTOW = extractLong(msg, 4); + packetUBXNAVODO->data.distance = extractLong(msg, 8); + packetUBXNAVODO->data.totalDistance = extractLong(msg, 12); + packetUBXNAVODO->data.distanceStd = extractLong(msg, 16); + + //Mark all datums as fresh (not read before) + packetUBXNAVODO->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVODO->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVODO->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVODO->callbackData->version, &packetUBXNAVODO->data.version, sizeof(UBX_NAV_ODO_data_t)); + packetUBXNAVODO->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVODO->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_NAV_VELECEF && msg->len == UBX_NAV_VELECEF_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVVELECEF != NULL) + { + packetUBXNAVVELECEF->data.iTOW = extractLong(msg, 0); + packetUBXNAVVELECEF->data.ecefVX = extractSignedLong(msg, 4); + packetUBXNAVVELECEF->data.ecefVY = extractSignedLong(msg, 8); + packetUBXNAVVELECEF->data.ecefVZ = extractSignedLong(msg, 12); + packetUBXNAVVELECEF->data.sAcc = extractLong(msg, 16); + + //Mark all datums as fresh (not read before) + packetUBXNAVVELECEF->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVVELECEF->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVVELECEF->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVVELECEF->callbackData->iTOW, &packetUBXNAVVELECEF->data.iTOW, sizeof(UBX_NAV_VELECEF_data_t)); + packetUBXNAVVELECEF->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVVELECEF->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_NAV_VELNED && msg->len == UBX_NAV_VELNED_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVVELNED != NULL) + { + packetUBXNAVVELNED->data.iTOW = extractLong(msg, 0); + packetUBXNAVVELNED->data.velN = extractSignedLong(msg, 4); + packetUBXNAVVELNED->data.velE = extractSignedLong(msg, 8); + packetUBXNAVVELNED->data.velD = extractSignedLong(msg, 12); + packetUBXNAVVELNED->data.speed = extractLong(msg, 16); + packetUBXNAVVELNED->data.gSpeed = extractLong(msg, 20); + packetUBXNAVVELNED->data.heading = extractSignedLong(msg, 24); + packetUBXNAVVELNED->data.sAcc = extractLong(msg, 28); + packetUBXNAVVELNED->data.cAcc = extractLong(msg, 32); + + //Mark all datums as fresh (not read before) + packetUBXNAVVELNED->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVVELNED->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVVELNED->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVVELNED->callbackData->iTOW, &packetUBXNAVVELNED->data.iTOW, sizeof(UBX_NAV_VELNED_data_t)); + packetUBXNAVVELNED->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVVELNED->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_NAV_HPPOSECEF && msg->len == UBX_NAV_HPPOSECEF_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVHPPOSECEF != NULL) + { + packetUBXNAVHPPOSECEF->data.version = extractByte(msg, 0); + packetUBXNAVHPPOSECEF->data.iTOW = extractLong(msg, 4); + packetUBXNAVHPPOSECEF->data.ecefX = extractSignedLong(msg, 8); + packetUBXNAVHPPOSECEF->data.ecefY = extractSignedLong(msg, 12); + packetUBXNAVHPPOSECEF->data.ecefZ = extractSignedLong(msg, 16); + packetUBXNAVHPPOSECEF->data.ecefXHp = extractSignedChar(msg, 20); + packetUBXNAVHPPOSECEF->data.ecefYHp = extractSignedChar(msg, 21); + packetUBXNAVHPPOSECEF->data.ecefZHp = extractSignedChar(msg, 22); + packetUBXNAVHPPOSECEF->data.flags.all = extractByte(msg, 23); + packetUBXNAVHPPOSECEF->data.pAcc = extractLong(msg, 24); + + //Mark all datums as fresh (not read before) + packetUBXNAVHPPOSECEF->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVHPPOSECEF->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVHPPOSECEF->callbackData->version, &packetUBXNAVHPPOSECEF->data.version, sizeof(UBX_NAV_HPPOSECEF_data_t)); + packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_NAV_HPPOSLLH && msg->len == UBX_NAV_HPPOSLLH_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVHPPOSLLH != NULL) + { + packetUBXNAVHPPOSLLH->data.version = extractByte(msg, 0); + packetUBXNAVHPPOSLLH->data.flags.all = extractByte(msg, 3); + packetUBXNAVHPPOSLLH->data.iTOW = extractLong(msg, 4); + packetUBXNAVHPPOSLLH->data.lon = extractSignedLong(msg, 8); + packetUBXNAVHPPOSLLH->data.lat = extractSignedLong(msg, 12); + packetUBXNAVHPPOSLLH->data.height = extractSignedLong(msg, 16); + packetUBXNAVHPPOSLLH->data.hMSL = extractSignedLong(msg, 20); + packetUBXNAVHPPOSLLH->data.lonHp = extractSignedChar(msg, 24); + packetUBXNAVHPPOSLLH->data.latHp = extractSignedChar(msg, 25); + packetUBXNAVHPPOSLLH->data.heightHp = extractSignedChar(msg, 26); + packetUBXNAVHPPOSLLH->data.hMSLHp = extractSignedChar(msg, 27); + packetUBXNAVHPPOSLLH->data.hAcc = extractLong(msg, 28); + packetUBXNAVHPPOSLLH->data.vAcc = extractLong(msg, 32); + + //Mark all datums as fresh (not read before) + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVHPPOSLLH->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVHPPOSLLH->callbackData->version, &packetUBXNAVHPPOSLLH->data.version, sizeof(UBX_NAV_HPPOSLLH_data_t)); + packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_NAV_CLOCK && msg->len == UBX_NAV_CLOCK_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVCLOCK != NULL) + { + packetUBXNAVCLOCK->data.iTOW = extractLong(msg, 0); + packetUBXNAVCLOCK->data.clkB = extractSignedLong(msg, 4); + packetUBXNAVCLOCK->data.clkD = extractSignedLong(msg, 8); + packetUBXNAVCLOCK->data.tAcc = extractLong(msg, 12); + packetUBXNAVCLOCK->data.fAcc = extractLong(msg, 16); + + //Mark all datums as fresh (not read before) + packetUBXNAVCLOCK->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVCLOCK->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVCLOCK->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVCLOCK->callbackData->iTOW, &packetUBXNAVCLOCK->data.iTOW, sizeof(UBX_NAV_CLOCK_data_t)); + packetUBXNAVCLOCK->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVCLOCK->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_NAV_SVIN && msg->len == UBX_NAV_SVIN_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVSVIN != NULL) + { + packetUBXNAVSVIN->data.version = extractByte(msg, 0); + packetUBXNAVSVIN->data.iTOW = extractLong(msg, 4); + packetUBXNAVSVIN->data.dur = extractLong(msg, 8); + packetUBXNAVSVIN->data.meanX = extractSignedLong(msg, 12); + packetUBXNAVSVIN->data.meanY = extractSignedLong(msg, 16); + packetUBXNAVSVIN->data.meanZ = extractSignedLong(msg, 20); + packetUBXNAVSVIN->data.meanXHP = extractSignedChar(msg, 24); + packetUBXNAVSVIN->data.meanYHP = extractSignedChar(msg, 25); + packetUBXNAVSVIN->data.meanZHP = extractSignedChar(msg, 26); + packetUBXNAVSVIN->data.meanAcc = extractLong(msg, 28); + packetUBXNAVSVIN->data.obs = extractLong(msg, 32); + packetUBXNAVSVIN->data.valid = extractSignedChar(msg, 36); + packetUBXNAVSVIN->data.active = extractSignedChar(msg, 37); + + //Mark all datums as fresh (not read before) + packetUBXNAVSVIN->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + } + } + else if (msg->id == UBX_NAV_RELPOSNED && ((msg->len == UBX_NAV_RELPOSNED_LEN) || (msg->len == UBX_NAV_RELPOSNED_LEN_F9))) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXNAVRELPOSNED != NULL) + { + //Note: + // RELPOSNED on the M8 is only 40 bytes long + // RELPOSNED on the F9 is 64 bytes long and contains much more information + + packetUBXNAVRELPOSNED->data.version = extractByte(msg, 0); + packetUBXNAVRELPOSNED->data.refStationId = extractInt(msg, 2); + packetUBXNAVRELPOSNED->data.iTOW = extractLong(msg, 4); + packetUBXNAVRELPOSNED->data.relPosN = extractSignedLong(msg, 8); + packetUBXNAVRELPOSNED->data.relPosE = extractSignedLong(msg, 12); + packetUBXNAVRELPOSNED->data.relPosD = extractSignedLong(msg, 16); + + if (msg->len == UBX_NAV_RELPOSNED_LEN) + { + // The M8 version does not contain relPosLength or relPosHeading + packetUBXNAVRELPOSNED->data.relPosLength = 0; + packetUBXNAVRELPOSNED->data.relPosHeading = 0; + packetUBXNAVRELPOSNED->data.relPosHPN = extractSignedChar(msg, 20); + packetUBXNAVRELPOSNED->data.relPosHPE = extractSignedChar(msg, 21); + packetUBXNAVRELPOSNED->data.relPosHPD = extractSignedChar(msg, 22); + packetUBXNAVRELPOSNED->data.relPosHPLength = 0; // The M8 version does not contain relPosHPLength + packetUBXNAVRELPOSNED->data.accN = extractLong(msg, 24); + packetUBXNAVRELPOSNED->data.accE = extractLong(msg, 28); + packetUBXNAVRELPOSNED->data.accD = extractLong(msg, 32); + // The M8 version does not contain accLength or accHeading + packetUBXNAVRELPOSNED->data.accLength = 0; + packetUBXNAVRELPOSNED->data.accHeading = 0; + packetUBXNAVRELPOSNED->data.flags.all = extractLong(msg, 36); + } + else + { + packetUBXNAVRELPOSNED->data.relPosLength = extractSignedLong(msg, 20); + packetUBXNAVRELPOSNED->data.relPosHeading = extractSignedLong(msg, 24); + packetUBXNAVRELPOSNED->data.relPosHPN = extractSignedChar(msg, 32); + packetUBXNAVRELPOSNED->data.relPosHPE = extractSignedChar(msg, 33); + packetUBXNAVRELPOSNED->data.relPosHPD = extractSignedChar(msg, 34); + packetUBXNAVRELPOSNED->data.relPosHPLength = extractSignedChar(msg, 35); + packetUBXNAVRELPOSNED->data.accN = extractLong(msg, 36); + packetUBXNAVRELPOSNED->data.accE = extractLong(msg, 40); + packetUBXNAVRELPOSNED->data.accD = extractLong(msg, 44); + packetUBXNAVRELPOSNED->data.accLength = extractLong(msg, 48); + packetUBXNAVRELPOSNED->data.accHeading = extractLong(msg, 52); + packetUBXNAVRELPOSNED->data.flags.all = extractLong(msg, 60); + } + + //Mark all datums as fresh (not read before) + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXNAVRELPOSNED->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVRELPOSNED->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXNAVRELPOSNED->callbackData->version, &packetUBXNAVRELPOSNED->data.version, sizeof(UBX_NAV_RELPOSNED_data_t)); + packetUBXNAVRELPOSNED->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXNAVRELPOSNED->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + break; + case UBX_CLASS_RXM: + if (msg->id == UBX_RXM_SFRBX) + // Note: length is variable + // Note: on protocol version 17: numWords is (0..16) + // on protocol version 18+: numWords is (0..10) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXRXMSFRBX != NULL) + { + packetUBXRXMSFRBX->data.gnssId = extractByte(msg, 0); + packetUBXRXMSFRBX->data.svId = extractByte(msg, 1); + packetUBXRXMSFRBX->data.freqId = extractByte(msg, 3); + packetUBXRXMSFRBX->data.numWords = extractByte(msg, 4); + packetUBXRXMSFRBX->data.chn = extractByte(msg, 5); + packetUBXRXMSFRBX->data.version = extractByte(msg, 6); + + for (uint8_t i = 0; (i < UBX_RXM_SFRBX_MAX_WORDS) && (i < packetUBXRXMSFRBX->data.numWords) + && ((i * 4) < (msg->len - 8)); i++) + { + packetUBXRXMSFRBX->data.dwrd[i] = extractLong(msg, 8 + (i * 4)); + } + + //Mark all datums as fresh (not read before) + packetUBXRXMSFRBX->moduleQueried = true; + + //Check if we need to copy the data for the callback + if ((packetUBXRXMSFRBX->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXRXMSFRBX->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXRXMSFRBX->callbackData->gnssId, &packetUBXRXMSFRBX->data.gnssId, sizeof(UBX_RXM_SFRBX_data_t)); + packetUBXRXMSFRBX->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXRXMSFRBX->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_RXM_RAWX) + // Note: length is variable + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXRXMRAWX != NULL) + { + for (uint8_t i = 0; i < 8; i++) + { + packetUBXRXMRAWX->data.header.rcvTow[i] = extractByte(msg, i); + } + packetUBXRXMRAWX->data.header.week = extractInt(msg, 8); + packetUBXRXMRAWX->data.header.leapS = extractSignedChar(msg, 10); + packetUBXRXMRAWX->data.header.numMeas = extractByte(msg, 11); + packetUBXRXMRAWX->data.header.recStat.all = extractByte(msg, 12); + packetUBXRXMRAWX->data.header.version = extractByte(msg, 13); + + for (uint8_t i = 0; (i < UBX_RXM_RAWX_MAX_BLOCKS) && (i < packetUBXRXMRAWX->data.header.numMeas) + && ((((uint16_t)i) * 32) < (msg->len - 16)); i++) + { + uint16_t offset = (((uint16_t)i) * 32) + 16; + for (uint8_t j = 0; j < 8; j++) + { + packetUBXRXMRAWX->data.blocks[i].prMes[j] = extractByte(msg, offset + j); + packetUBXRXMRAWX->data.blocks[i].cpMes[j] = extractByte(msg, offset + 8 + j); + if (j < 4) + packetUBXRXMRAWX->data.blocks[i].doMes[j] = extractByte(msg, offset + 16 + j); + } + packetUBXRXMRAWX->data.blocks[i].gnssId = extractByte(msg, offset + 20); + packetUBXRXMRAWX->data.blocks[i].svId = extractByte(msg, offset + 21); + packetUBXRXMRAWX->data.blocks[i].sigId = extractByte(msg, offset + 22); + packetUBXRXMRAWX->data.blocks[i].freqId = extractByte(msg, offset + 23); + packetUBXRXMRAWX->data.blocks[i].lockTime = extractInt(msg, offset + 24); + packetUBXRXMRAWX->data.blocks[i].cno = extractByte(msg, offset + 26); + packetUBXRXMRAWX->data.blocks[i].prStdev = extractByte(msg, offset + 27); + packetUBXRXMRAWX->data.blocks[i].cpStdev = extractByte(msg, offset + 28); + packetUBXRXMRAWX->data.blocks[i].doStdev = extractByte(msg, offset + 29); + packetUBXRXMRAWX->data.blocks[i].trkStat.all = extractByte(msg, offset + 30); + } + + //Mark all datums as fresh (not read before) + packetUBXRXMRAWX->moduleQueried = true; + + //Check if we need to copy the data for the callback + if ((packetUBXRXMRAWX->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXRXMRAWX->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXRXMRAWX->callbackData->header.rcvTow[0], &packetUBXRXMRAWX->data.header.rcvTow[0], sizeof(UBX_RXM_RAWX_data_t)); + packetUBXRXMRAWX->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXRXMRAWX->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + break; + case UBX_CLASS_CFG: + if (msg->id == UBX_CFG_RATE && msg->len == UBX_CFG_RATE_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXCFGRATE != NULL) + { + packetUBXCFGRATE->data.measRate = extractInt(msg, 0); + packetUBXCFGRATE->data.navRate = extractInt(msg, 2); + packetUBXCFGRATE->data.timeRef = extractInt(msg, 4); + + //Mark all datums as fresh (not read before) + packetUBXCFGRATE->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + } + } + break; + case UBX_CLASS_TIM: + if (msg->id == UBX_TIM_TM2 && msg->len == UBX_TIM_TM2_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXTIMTM2 != NULL) + { + packetUBXTIMTM2->data.ch = extractByte(msg, 0); + packetUBXTIMTM2->data.flags.all = extractByte(msg, 1); + packetUBXTIMTM2->data.count = extractInt(msg, 2); + packetUBXTIMTM2->data.wnR = extractInt(msg, 4); + packetUBXTIMTM2->data.wnF = extractInt(msg, 6); + packetUBXTIMTM2->data.towMsR = extractLong(msg, 8); + packetUBXTIMTM2->data.towSubMsR = extractLong(msg, 12); + packetUBXTIMTM2->data.towMsF = extractLong(msg, 16); + packetUBXTIMTM2->data.towSubMsF = extractLong(msg, 20); + packetUBXTIMTM2->data.accEst = extractLong(msg, 24); + + //Mark all datums as fresh (not read before) + packetUBXTIMTM2->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + 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)); + packetUBXTIMTM2->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXTIMTM2->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + break; + case UBX_CLASS_ESF: + if (msg->id == UBX_ESF_ALG && msg->len == UBX_ESF_ALG_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXESFALG != NULL) + { + packetUBXESFALG->data.iTOW = extractLong(msg, 0); + packetUBXESFALG->data.version = extractByte(msg, 4); + packetUBXESFALG->data.flags.all = extractByte(msg, 5); + packetUBXESFALG->data.error.all = extractByte(msg, 6); + packetUBXESFALG->data.yaw = extractLong(msg, 8); + packetUBXESFALG->data.pitch = extractSignedInt(msg, 12); + packetUBXESFALG->data.roll = extractSignedInt(msg, 14); + + //Mark all datums as fresh (not read before) + packetUBXESFALG->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXESFALG->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXESFALG->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXESFALG->callbackData->iTOW, &packetUBXESFALG->data.iTOW, sizeof(UBX_ESF_ALG_data_t)); + packetUBXESFALG->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXESFALG->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_ESF_INS && msg->len == UBX_ESF_INS_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXESFINS != NULL) + { + packetUBXESFINS->data.bitfield0.all = extractLong(msg, 0); + packetUBXESFINS->data.iTOW = extractLong(msg, 8); + packetUBXESFINS->data.xAngRate = extractSignedLong(msg, 12); + packetUBXESFINS->data.yAngRate = extractSignedLong(msg, 16); + packetUBXESFINS->data.zAngRate = extractSignedLong(msg, 20); + packetUBXESFINS->data.xAccel = extractSignedLong(msg, 24); + packetUBXESFINS->data.yAccel = extractSignedLong(msg, 28); + packetUBXESFINS->data.zAccel = extractSignedLong(msg, 32); + + //Mark all datums as fresh (not read before) + packetUBXESFINS->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXESFINS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXESFINS->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXESFINS->callbackData->bitfield0.all, &packetUBXESFINS->data.bitfield0.all, sizeof(UBX_ESF_INS_data_t)); + packetUBXESFINS->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXESFINS->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_ESF_MEAS) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXESFMEAS != NULL) + { + packetUBXESFMEAS->data.timeTag = extractLong(msg, 0); + packetUBXESFMEAS->data.flags.all = extractInt(msg, 4); + packetUBXESFMEAS->data.id = extractInt(msg, 6); + for (int i = 0; (i < DEF_NUM_SENS) && (i < packetUBXESFMEAS->data.flags.bits.numMeas) + && ((i * 4) < (msg->len - 8)); i++) + { + packetUBXESFMEAS->data.data[i].data.all = extractLong(msg, 8 + (i * 4)); + } + if (msg->len > (8 + (packetUBXESFMEAS->data.flags.bits.numMeas * 4))) + packetUBXESFMEAS->data.calibTtag = extractLong(msg, 8 + (packetUBXESFMEAS->data.flags.bits.numMeas * 4)); + + //Mark all datums as fresh (not read before) + packetUBXESFMEAS->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXESFMEAS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXESFMEAS->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXESFMEAS->callbackData->timeTag, &packetUBXESFMEAS->data.timeTag, sizeof(UBX_ESF_MEAS_data_t)); + packetUBXESFMEAS->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXESFMEAS->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_ESF_RAW) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXESFRAW != NULL) + { + for (int i = 0; (i < DEF_NUM_SENS) && ((i * 8) < (msg->len - 4)); i++) + { + packetUBXESFRAW->data.data[i].data.all = extractLong(msg, 8 + (i * 8)); + packetUBXESFRAW->data.data[i].sTag = extractLong(msg, 8 + (i * 8) + 4); + } + + //Mark all datums as fresh (not read before) + packetUBXESFRAW->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXESFRAW->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXESFRAW->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXESFRAW->callbackData->data[0].data.all, &packetUBXESFRAW->data.data[0].data.all, sizeof(UBX_ESF_RAW_data_t)); + packetUBXESFRAW->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXESFRAW->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_ESF_STATUS) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXESFSTATUS != NULL) + { + packetUBXESFSTATUS->data.iTOW = extractLong(msg, 0); + packetUBXESFSTATUS->data.version = extractByte(msg, 4); + packetUBXESFSTATUS->data.fusionMode = extractByte(msg, 12); + packetUBXESFSTATUS->data.numSens = extractByte(msg, 15); + for (int i = 0; (i < DEF_NUM_SENS) && (i < packetUBXESFSTATUS->data.numSens) + && ((i * 4) < (msg->len - 16)); i++) + { + packetUBXESFSTATUS->data.status[i].sensStatus1.all = extractByte(msg, 16 + (i * 4) + 0); + packetUBXESFSTATUS->data.status[i].sensStatus2.all = extractByte(msg, 16 + (i * 4) + 1); + packetUBXESFSTATUS->data.status[i].freq = extractByte(msg, 16 + (i * 4) + 2); + packetUBXESFSTATUS->data.status[i].faults.all = extractByte(msg, 16 + (i * 4) + 3); + } + + //Mark all datums as fresh (not read before) + packetUBXESFSTATUS->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXESFSTATUS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXESFSTATUS->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXESFSTATUS->callbackData->iTOW, &packetUBXESFSTATUS->data.iTOW, sizeof(UBX_ESF_STATUS_data_t)); + packetUBXESFSTATUS->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXESFSTATUS->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + break; + case UBX_CLASS_HNR: + if (msg->id == UBX_HNR_PVT && msg->len == UBX_HNR_PVT_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXHNRPVT != NULL) + { + packetUBXHNRPVT->data.iTOW = extractLong(msg, 0); + packetUBXHNRPVT->data.year = extractInt(msg, 4); + packetUBXHNRPVT->data.month = extractByte(msg, 6); + packetUBXHNRPVT->data.day = extractByte(msg, 7); + packetUBXHNRPVT->data.hour = extractByte(msg, 8); + packetUBXHNRPVT->data.min = extractByte(msg, 9); + packetUBXHNRPVT->data.sec = extractByte(msg, 10); + packetUBXHNRPVT->data.valid.all = extractByte(msg, 11); + packetUBXHNRPVT->data.nano = extractSignedLong(msg, 12); + packetUBXHNRPVT->data.gpsFix = extractByte(msg, 16); + packetUBXHNRPVT->data.flags.all = extractByte(msg, 17); + packetUBXHNRPVT->data.lon = extractSignedLong(msg, 20); + packetUBXHNRPVT->data.lat = extractSignedLong(msg, 24); + packetUBXHNRPVT->data.height = extractSignedLong(msg, 28); + packetUBXHNRPVT->data.hMSL = extractSignedLong(msg, 32); + packetUBXHNRPVT->data.gSpeed = extractSignedLong(msg, 36); + packetUBXHNRPVT->data.speed = extractSignedLong(msg, 40); + packetUBXHNRPVT->data.headMot = extractSignedLong(msg, 44); + packetUBXHNRPVT->data.headVeh = extractSignedLong(msg, 48); + packetUBXHNRPVT->data.hAcc = extractLong(msg, 52); + packetUBXHNRPVT->data.vAcc = extractLong(msg, 56); + packetUBXHNRPVT->data.sAcc = extractLong(msg, 60); + packetUBXHNRPVT->data.headAcc = extractLong(msg, 64); + + //Mark all datums as fresh (not read before) + packetUBXHNRPVT->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXHNRPVT->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXHNRPVT->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXHNRPVT->callbackData->iTOW, &packetUBXHNRPVT->data.iTOW, sizeof(UBX_HNR_PVT_data_t)); + packetUBXHNRPVT->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXHNRPVT->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_HNR_ATT && msg->len == UBX_HNR_ATT_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXHNRATT != NULL) + { + packetUBXHNRATT->data.iTOW = extractLong(msg, 0); + packetUBXHNRATT->data.version = extractByte(msg, 4); + packetUBXHNRATT->data.roll = extractSignedLong(msg, 8); + packetUBXHNRATT->data.pitch = extractSignedLong(msg, 12); + packetUBXHNRATT->data.heading = extractSignedLong(msg, 16); + packetUBXHNRATT->data.accRoll = extractLong(msg, 20); + packetUBXHNRATT->data.accPitch = extractLong(msg, 24); + packetUBXHNRATT->data.accHeading = extractLong(msg, 28); + + //Mark all datums as fresh (not read before) + packetUBXHNRATT->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXHNRATT->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXHNRATT->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXHNRATT->callbackData->iTOW, &packetUBXHNRATT->data.iTOW, sizeof(UBX_HNR_ATT_data_t)); + packetUBXHNRATT->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXHNRATT->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + else if (msg->id == UBX_HNR_INS && msg->len == UBX_HNR_INS_LEN) + { + //Parse various byte fields into storage - but only if we have memory allocated for it + if (packetUBXHNRINS != NULL) + { + packetUBXHNRINS->data.bitfield0.all = extractLong(msg, 0); + packetUBXHNRINS->data.iTOW = extractLong(msg, 8); + packetUBXHNRINS->data.xAngRate = extractSignedLong(msg, 12); + packetUBXHNRINS->data.yAngRate = extractSignedLong(msg, 16); + packetUBXHNRINS->data.zAngRate = extractSignedLong(msg, 20); + packetUBXHNRINS->data.xAccel = extractSignedLong(msg, 24); + packetUBXHNRINS->data.yAccel = extractSignedLong(msg, 28); + packetUBXHNRINS->data.zAccel = extractSignedLong(msg, 32); + + //Mark all datums as fresh (not read before) + packetUBXHNRINS->moduleQueried.moduleQueried.all = 0xFFFFFFFF; + + //Check if we need to copy the data for the callback + if ((packetUBXHNRINS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXHNRINS->automaticFlags.flags.bits.callbackCopyValid == false)) // AND the data is stale + { + memcpy(&packetUBXHNRINS->callbackData->bitfield0.all, &packetUBXHNRINS->data.bitfield0.all, sizeof(UBX_HNR_INS_data_t)); + packetUBXHNRINS->automaticFlags.flags.bits.callbackCopyValid = true; + } + + //Check if we need to copy the data into the file buffer + if (packetUBXHNRINS->automaticFlags.flags.bits.addToFileBuffer) + { + storePacket(msg); + } + } + } + break; + } +} + +//Given a message, calc and store the two byte "8-Bit Fletcher" checksum over the entirety of the message +//This is called before we send a command message +void SFE_UBLOX_GPS::calcChecksum(ubxPacket *msg) +{ + msg->checksumA = 0; + msg->checksumB = 0; + + msg->checksumA += msg->cls; + msg->checksumB += msg->checksumA; + + msg->checksumA += msg->id; + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len & 0xFF); + msg->checksumB += msg->checksumA; + + msg->checksumA += (msg->len >> 8); + msg->checksumB += msg->checksumA; + + for (uint16_t i = 0; i < msg->len; i++) + { + msg->checksumA += msg->payload[i]; + msg->checksumB += msg->checksumA; + } +} + +//Given a message and a byte, add to rolling "8-Bit Fletcher" checksum +//This is used when receiving messages from module +void SFE_UBLOX_GPS::addToChecksum(uint8_t incoming) +{ + rollingChecksumA += incoming; + rollingChecksumB += rollingChecksumA; +} + +//Given a packet and payload, send everything including CRC bytes via I2C port +sfe_ublox_status_e SFE_UBLOX_GPS::sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait, boolean expectACKonly) +{ + 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("\nSending: ")); + printPacket(outgoingUBX, true); // Always print payload + } + + if (commType == COMM_TYPE_I2C) + { + retVal = sendI2cCommand(outgoingUBX, maxWait); + if (retVal != SFE_UBLOX_STATUS_SUCCESS) + { + if (_printDebug == true) + { + _debugSerial->println(F("Send I2C Command failed")); + } + return retVal; + } + } + else if (commType == COMM_TYPE_SERIAL) + { + sendSerialCommand(outgoingUBX); + } + + if (maxWait > 0) + { + //Depending on what we just sent, either we need to look for an ACK or not + if ((outgoingUBX->cls == UBX_CLASS_CFG) || (expectACKonly == true)) + { + if (_printDebug == true) + { + _debugSerial->println(F("sendCommand: Waiting for ACK response")); + } + retVal = waitForACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + else + { + if (_printDebug == true) + { + _debugSerial->println(F("sendCommand: Waiting for No ACK response")); + } + retVal = waitForNoACKResponse(outgoingUBX, outgoingUBX->cls, outgoingUBX->id, maxWait); //Wait for Ack response + } + } + return retVal; +} + +//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(false) != 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 + _i2cPort->write(UBX_SYNCH_1); //μ - oh ublox, you're funny. I will call you micro-blox from now on. + _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 (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 + uint16_t bytesToSend = outgoingUBX->len; + + //"The number of data bytes must be at least 2 to properly distinguish + //from the write access to set the address counter in random read accesses." + uint16_t startSpot = 0; + while (bytesToSend > 1) + { + uint8_t len = bytesToSend; + if (len > i2cTransactionSize) + len = i2cTransactionSize; + + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + //_i2cPort->write(outgoingUBX->payload, len); //Write a portion of the payload to the bus + + 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 (SFE_UBLOX_STATUS_I2C_COMM_FAILURE); //Sensor did not ACK + + //*outgoingUBX->payload += len; //Move the pointer forward + startSpot += len; //Move the pointer forward + bytesToSend -= len; + } + + //Write checksum + _i2cPort->beginTransmission((uint8_t)_gpsI2Caddress); + if (bytesToSend == 1) + _i2cPort->write(outgoingUBX->payload, 1); + _i2cPort->write(outgoingUBX->checksumA); + _i2cPort->write(outgoingUBX->checksumB); + + //All done transmitting bytes. Release bus. + if (_i2cPort->endTransmission() != 0) + 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 +void SFE_UBLOX_GPS::sendSerialCommand(ubxPacket *outgoingUBX) +{ + //Write header bytes + _serialPort->write(UBX_SYNCH_1); //μ - oh ublox, you're funny. I will call you micro-blox from now on. + _serialPort->write(UBX_SYNCH_2); //b + _serialPort->write(outgoingUBX->cls); + _serialPort->write(outgoingUBX->id); + _serialPort->write(outgoingUBX->len & 0xFF); //LSB + _serialPort->write(outgoingUBX->len >> 8); //MSB + + //Write payload. + for (int i = 0; i < outgoingUBX->len; i++) + { + _serialPort->write(outgoingUBX->payload[i]); + } + + //Write checksum + _serialPort->write(outgoingUBX->checksumA); + _serialPort->write(outgoingUBX->checksumB); +} + +//Pretty prints the current ubxPacket +void SFE_UBLOX_GPS::printPacket(ubxPacket *packet, boolean alwaysPrintPayload) +{ + if (_printDebug == true) + { + _debugSerial->print(F("CLS:")); + if (packet->cls == UBX_CLASS_NAV) //1 + _debugSerial->print(F("NAV")); + else if (packet->cls == UBX_CLASS_ACK) //5 + _debugSerial->print(F("ACK")); + else if (packet->cls == UBX_CLASS_CFG) //6 + _debugSerial->print(F("CFG")); + else if (packet->cls == UBX_CLASS_MON) //0x0A + _debugSerial->print(F("MON")); + else + { + _debugSerial->print(F("0x")); + _debugSerial->print(packet->cls, HEX); + } + + _debugSerial->print(F(" ID:")); + if (packet->cls == UBX_CLASS_NAV && packet->id == UBX_NAV_PVT) + _debugSerial->print(F("PVT")); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_RATE) + _debugSerial->print(F("RATE")); + else if (packet->cls == UBX_CLASS_CFG && packet->id == UBX_CFG_CFG) + _debugSerial->print(F("SAVE")); + else + { + _debugSerial->print(F("0x")); + _debugSerial->print(packet->id, HEX); + } + + _debugSerial->print(F(" Len: 0x")); + _debugSerial->print(packet->len, HEX); + + // Only print the payload is ignoreThisPayload is false otherwise + // we could be printing gibberish from beyond the end of packetBuf + if ((alwaysPrintPayload == true) || (ignoreThisPayload == false)) + { + _debugSerial->print(F(" Payload:")); + + for (int x = 0; x < packet->len; x++) + { + _debugSerial->print(F(" ")); + _debugSerial->print(packet->payload[x], HEX); + } + } + else + { + _debugSerial->print(F(" Payload: IGNORED")); + } + _debugSerial->println(); + } +} + +//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. + +//When we poll or get a setting, we will receive _both_ a config packet and an ACK +//If the poll or get request is not valid, we will receive _only_ a NACK + +//If we are trying to get or poll a setting, then packetCfg.len will be 0 or 1 when the packetCfg is _sent_. +//If we poll the setting for a particular port using UBX-CFG-PRT then .len will be 1 initially +//For all other gets or polls, .len will be 0 initially +//(It would be possible for .len to be 2 _if_ we were using UBX-CFG-MSG to poll the settings for a particular message - but we don't use that (currently)) + +//If the get or poll _fails_, i.e. is NACK'd, then packetCfg.len could still be 0 or 1 after the NACK is received +//But if the get or poll is ACK'd, then packetCfg.len will have been updated by the incoming data and will always be at least 2 + +//If we are going to set the value for a setting, then packetCfg.len will be at least 3 when the packetCfg is _sent_. +//(UBX-CFG-MSG appears to have the shortest set length of 3 bytes) + +//We need to think carefully about how interleaved PVT packets affect things. +//It is entirely possible that our packetCfg and packetAck were received successfully +//but while we are still in the "if (checkUblox() == true)" loop a PVT packet is processed +//or _starts_ to arrive (remember that Serial data can arrive very slowly). + +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got an ACK and a valid packetCfg (module is responding with register content) +//Returns SFE_UBLOX_STATUS_DATA_SENT if we got an ACK and no packetCfg (no valid packetCfg needed, module absorbs new register data) +//Returns SFE_UBLOX_STATUS_FAIL if something very bad happens (e.g. a double checksum failure) +//Returns SFE_UBLOX_STATUS_COMMAND_NACK if the packet was not-acknowledged (NACK) +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we had a checksum failure +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an ACK and a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetAuto.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetAuto.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = millis(); + while (millis() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: valid data and valid ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and a correct ACK! + } + + // We can be confident that the data packet (if we are going to get one) will always arrive + // before the matching ACK. So if we sent a config packet which only produces an ACK + // then outgoingUBX->classAndIDmatch will be NOT_DEFINED and the packetAck.classAndIDmatch will VALID. + // We should not check outgoingUBX->valid, outgoingUBX->cls or outgoingUBX->id + // as these may have been changed by an automatic packet. + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: no data and valid ACK after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_SENT); //We got an ACK but no data... + } + + // If both the outgoingUBX->classAndIDmatch and packetAck.classAndIDmatch are VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by an automatic packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && ((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: data being OVERWRITTEN after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If packetAck.classAndIDmatch is VALID but both outgoingUBX->valid and outgoingUBX->classAndIDmatch + // are NOT_VALID then we can be confident we have had a checksum failure on the data packet + else if ((packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: CRC failed after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //Checksum fail + } + + // If our packet was not-acknowledged (NACK) we do not receive a data packet - we only get the NACK. + // So you would expect outgoingUBX->valid and outgoingUBX->classAndIDmatch to still be NOT_DEFINED + // But if a full PVT packet arrives afterwards outgoingUBX->valid could be VALID (or just possibly NOT_VALID) + // but outgoingUBX->cls and outgoingUBX->id would not match... + // So I think this is telling us we need a special state for packetAck.classAndIDmatch to tell us + // the packet was definitely NACK'd otherwise we are possibly just guessing... + // Note: the addition of packetBuf changes the logic of this, but we'll leave the code as is for now. + else if (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_NOTACKNOWLEDGED) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: data was NOTACKNOWLEDGED (NACK) after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_COMMAND_NACK); //We received a NACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID but the packetAck.classAndIDmatch is NOT_VALID + // then the ack probably had a checksum error. We will take a gamble and return DATA_RECEIVED. + // If we were playing safe, we should return FAIL instead + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: VALID data and INVALID ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID and the packetAck.classAndIDmatch is NOT_VALID + // then we return a FAIL. This must be a double checksum failure? + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: INVALID data and INVALID ACK received after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_FAIL); //We received invalid data and an invalid ACK! + } + + // If the outgoingUBX->classAndIDmatch is VALID and the packetAck.classAndIDmatch is NOT_DEFINED + // then the ACK has not yet been received and we should keep waiting for it + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED)) + { + // if (_printDebug == true) + // { + // _debugSerial->print(F("waitForACKResponse: valid data after ")); + // _debugSerial->print(millis() - startTime); + // _debugSerial->println(F(" msec. Waiting for ACK.")); + // } + } + + } //checkUbloxInternal == true + + delayMicroseconds(500); + } //while (millis() - startTime < maxTime) + + // We have timed out... + // If the outgoingUBX->classAndIDmatch is VALID then we can take a gamble and return DATA_RECEIVED + // even though we did not get an ACK + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (packetAck.classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: TIMEOUT with valid data after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. ")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data... But no ACK! + } + + if (_printDebug == true) + { + _debugSerial->print(F("waitForACKResponse: TIMEOUT after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec.")); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +//For non-CFG queries no ACK is sent so we use this function +//Returns SFE_UBLOX_STATUS_DATA_RECEIVED if we got a config packet full of response data that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_CRC_FAIL if we got a corrupt config packet that has CLS/ID match to our query packet +//Returns SFE_UBLOX_STATUS_TIMEOUT if we timed out +//Returns SFE_UBLOX_STATUS_DATA_OVERWRITTEN if we got an a valid packetCfg but that the packetCfg has been +// or is currently being overwritten (remember that Serial data can arrive very slowly) +sfe_ublox_status_e SFE_UBLOX_GPS::waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime) +{ + outgoingUBX->valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; //This will go VALID (or NOT_VALID) when we receive a response to the packet we sent + packetAck.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetAuto.valid = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + outgoingUBX->classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; // This will go VALID (or NOT_VALID) when we receive a packet that matches the requested class and ID + packetAck.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetBuf.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + packetAuto.classAndIDmatch = SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED; + + unsigned long startTime = millis(); + while (millis() - startTime < maxTime) + { + if (checkUbloxInternal(outgoingUBX, requestedClass, requestedID) == true) //See if new data is available. Process bytes as they come in. + { + + // If outgoingUBX->classAndIDmatch is VALID + // and outgoingUBX->valid is _still_ VALID and the class and ID _still_ match + // then we can be confident that the data in outgoingUBX is valid + if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID) && (outgoingUBX->cls == requestedClass) && (outgoingUBX->id == requestedID)) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: valid data with CLS/ID match after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_RECEIVED); //We received valid data! + } + + // If the outgoingUBX->classAndIDmatch is VALID + // but the outgoingUBX->cls or ID no longer match then we can be confident that we had + // valid data but it has been or is currently being overwritten by another packet (e.g. PVT). + // If (e.g.) a PVT packet is _being_ received: outgoingUBX->valid will be NOT_DEFINED + // If (e.g.) a PVT packet _has been_ received: outgoingUBX->valid will be VALID (or just possibly NOT_VALID) + // So we cannot use outgoingUBX->valid as part of this check. + // Note: the addition of packetBuf should make this check redundant! + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_VALID) && ((outgoingUBX->cls != requestedClass) || (outgoingUBX->id != requestedID))) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: data being OVERWRITTEN after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_DATA_OVERWRITTEN); // Data was valid but has been or is being overwritten + } + + // If outgoingUBX->classAndIDmatch is NOT_DEFINED + // and outgoingUBX->valid is VALID then this must be (e.g.) a PVT packet + else if ((outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED) && (outgoingUBX->valid == SFE_UBLOX_PACKET_VALIDITY_VALID)) + { + // if (_printDebug == true) + // { + // _debugSerial->print(F("waitForNoACKResponse: valid but UNWANTED data after ")); + // _debugSerial->print(millis() - startTime); + // _debugSerial->print(F(" msec. Class: ")); + // _debugSerial->print(outgoingUBX->cls); + // _debugSerial->print(F(" ID: ")); + // _debugSerial->print(outgoingUBX->id); + // } + } + + // If the outgoingUBX->classAndIDmatch is NOT_VALID then we return CRC failure + else if (outgoingUBX->classAndIDmatch == SFE_UBLOX_PACKET_VALIDITY_NOT_VALID) + { + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: CLS/ID match but failed CRC after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec")); + } + return (SFE_UBLOX_STATUS_CRC_FAIL); //We received invalid data + } + } + + delayMicroseconds(500); + } + + if (_printDebug == true) + { + _debugSerial->print(F("waitForNoACKResponse: TIMEOUT after ")); + _debugSerial->print(millis() - startTime); + _debugSerial->println(F(" msec. No packet received.")); + } + + return (SFE_UBLOX_STATUS_TIMEOUT); +} + +// Check if any callbacks are waiting to be processed +void SFE_UBLOX_GPS::checkCallbacks(void) +{ + if (checkCallbacksReentrant == true) // Check for reentry (i.e. checkCallbacks has been called from inside a callback) + return; + + checkCallbacksReentrant = true; + + if ((packetUBXNAVPOSECEF != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVPOSECEF->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVPOSECEF->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVPOSECEF->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV POSECEF")); + packetUBXNAVPOSECEF->callbackPointer(*packetUBXNAVPOSECEF->callbackData); // Call the callback + packetUBXNAVPOSECEF->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXNAVSTATUS != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVSTATUS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVSTATUS->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVSTATUS->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV STATUS")); + packetUBXNAVSTATUS->callbackPointer(*packetUBXNAVSTATUS->callbackData); // Call the callback + packetUBXNAVSTATUS->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXNAVDOP != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVDOP->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVDOP->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVDOP->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV DOP")); + packetUBXNAVDOP->callbackPointer(*packetUBXNAVDOP->callbackData); // Call the callback + packetUBXNAVDOP->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXNAVATT != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVATT->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVATT->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVATT->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV ATT")); + packetUBXNAVATT->callbackPointer(*packetUBXNAVATT->callbackData); // Call the callback + packetUBXNAVATT->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXNAVPVT != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVPVT->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVPVT->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVPVT->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + //if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV PVT")); + packetUBXNAVPVT->callbackPointer(*packetUBXNAVPVT->callbackData); // Call the callback + packetUBXNAVPVT->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXNAVODO != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVODO->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVODO->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVODO->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV ODO")); + packetUBXNAVODO->callbackPointer(*packetUBXNAVODO->callbackData); // Call the callback + packetUBXNAVODO->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXNAVVELECEF != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVVELECEF->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVVELECEF->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVVELECEF->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV VELECEF")); + packetUBXNAVVELECEF->callbackPointer(*packetUBXNAVVELECEF->callbackData); // Call the callback + packetUBXNAVVELECEF->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXNAVVELNED != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVVELNED->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVVELNED->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVVELNED->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV VELNED")); + packetUBXNAVVELNED->callbackPointer(*packetUBXNAVVELNED->callbackData); // Call the callback + packetUBXNAVVELNED->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXNAVHPPOSECEF != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVHPPOSECEF->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVHPPOSECEF->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV HPPOSECEF")); + packetUBXNAVHPPOSECEF->callbackPointer(*packetUBXNAVHPPOSECEF->callbackData); // Call the callback + packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXNAVHPPOSLLH != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVHPPOSLLH->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVHPPOSLLH->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV HPPOSLLH")); + packetUBXNAVHPPOSLLH->callbackPointer(*packetUBXNAVHPPOSLLH->callbackData); // Call the callback + packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXNAVCLOCK != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVCLOCK->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVCLOCK->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVCLOCK->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV CLOCK")); + packetUBXNAVCLOCK->callbackPointer(*packetUBXNAVCLOCK->callbackData); // Call the callback + packetUBXNAVCLOCK->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXNAVRELPOSNED != NULL) // If RAM has been allocated for message storage + && (packetUBXNAVRELPOSNED->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXNAVRELPOSNED->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXNAVRELPOSNED->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for NAV RELPOSNED")); + packetUBXNAVRELPOSNED->callbackPointer(*packetUBXNAVRELPOSNED->callbackData); // Call the callback + packetUBXNAVRELPOSNED->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXRXMSFRBX != NULL) // If RAM has been allocated for message storage + && (packetUBXRXMSFRBX->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXRXMSFRBX->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXRXMSFRBX->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for RXM SFRBX")); + packetUBXRXMSFRBX->callbackPointer(*packetUBXRXMSFRBX->callbackData); // Call the callback + packetUBXRXMSFRBX->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXRXMRAWX != NULL) // If RAM has been allocated for message storage + && (packetUBXRXMRAWX->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXRXMRAWX->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXRXMRAWX->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for RXM RAWX")); + packetUBXRXMRAWX->callbackPointer(*packetUBXRXMRAWX->callbackData); // Call the callback + packetUBXRXMRAWX->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXTIMTM2 != NULL) // If RAM has been allocated for message storage + && (packetUBXTIMTM2->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXTIMTM2->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXTIMTM2->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for TIM TM2")); + packetUBXTIMTM2->callbackPointer(*packetUBXTIMTM2->callbackData); // Call the callback + packetUBXTIMTM2->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->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXESFALG->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for ESF ALG")); + packetUBXESFALG->callbackPointer(*packetUBXESFALG->callbackData); // Call the callback + packetUBXESFALG->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXESFINS != NULL) // If RAM has been allocated for message storage + && (packetUBXESFINS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXESFINS->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXESFINS->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for ESF INS")); + packetUBXESFINS->callbackPointer(*packetUBXESFINS->callbackData); // Call the callback + packetUBXESFINS->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXESFMEAS != NULL) // If RAM has been allocated for message storage + && (packetUBXESFMEAS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXESFMEAS->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXESFMEAS->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for ESF MEAS")); + packetUBXESFMEAS->callbackPointer(*packetUBXESFMEAS->callbackData); // Call the callback + packetUBXESFMEAS->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXESFRAW != NULL) // If RAM has been allocated for message storage + && (packetUBXESFRAW->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXESFRAW->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXESFRAW->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for ESF RAW")); + packetUBXESFRAW->callbackPointer(*packetUBXESFRAW->callbackData); // Call the callback + packetUBXESFRAW->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXESFSTATUS != NULL) // If RAM has been allocated for message storage + && (packetUBXESFSTATUS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXESFSTATUS->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXESFSTATUS->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for ESF STATUS")); + packetUBXESFSTATUS->callbackPointer(*packetUBXESFSTATUS->callbackData); // Call the callback + packetUBXESFSTATUS->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXHNRATT != NULL) // If RAM has been allocated for message storage + && (packetUBXHNRATT->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXHNRATT->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXHNRATT->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for HNR ATT")); + packetUBXHNRATT->callbackPointer(*packetUBXHNRATT->callbackData); // Call the callback + packetUBXHNRATT->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXHNRINS != NULL) // If RAM has been allocated for message storage + && (packetUBXHNRINS->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXHNRINS->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXHNRINS->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for HNR INS")); + packetUBXHNRINS->callbackPointer(*packetUBXHNRINS->callbackData); // Call the callback + packetUBXHNRINS->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + if ((packetUBXHNRPVT != NULL) // If RAM has been allocated for message storage + && (packetUBXHNRPVT->callbackData != NULL) // If RAM has been allocated for the copy of the data + && (packetUBXHNRPVT->callbackPointer != NULL) // If the pointer to the callback has been defined + && (packetUBXHNRPVT->automaticFlags.flags.bits.callbackCopyValid == true)) // If the copy of the data is valid + { + // if (_printDebug == true) + // _debugSerial->println(F("checkCallbacks: calling callback for HNR PVT")); + packetUBXHNRPVT->callbackPointer(*packetUBXHNRPVT->callbackData); // Call the callback + packetUBXHNRPVT->automaticFlags.flags.bits.callbackCopyValid = false; // Mark the data as stale + } + + checkCallbacksReentrant = false; +} + +// Push (e.g.) RTCM data directly to the module +// Returns true if all numDataBytes were pushed successfully +// Warning: this function does not check that the data is valid. It is the user's responsibility to ensure the data is valid before pushing. +boolean SFE_UBLOX_GPS::pushRawData(uint8_t *dataBytes, size_t numDataBytes) +{ + if (commType == COMM_TYPE_SERIAL) + { + // Serial: write all the bytes in one go + size_t bytesWritten = _serialPort->write(dataBytes, numDataBytes); + return (bytesWritten == numDataBytes); + } + else + { + // I2C: split the data up into packets of i2cTransactionSize + size_t bytesLeftToWrite = numDataBytes; + size_t bytesWrittenTotal = 0; + + while (bytesLeftToWrite > 0) + { + size_t bytesToWrite; // Limit bytesToWrite to i2cTransactionSize + if (bytesLeftToWrite > i2cTransactionSize) + bytesToWrite = i2cTransactionSize; + else + bytesToWrite = bytesLeftToWrite; + + _i2cPort->beginTransmission(_gpsI2Caddress); + size_t bytesWritten = _i2cPort->write(dataBytes, bytesToWrite); // Write the bytes + + bytesWrittenTotal += bytesWritten; // Update the totals + bytesLeftToWrite -= bytesToWrite; + dataBytes += bytesToWrite; // Point to fresh data + + if (bytesLeftToWrite > 0) + { + if (_i2cPort->endTransmission(false) != 0) //Send a restart command. Do not release bus. + return (false); //Sensor did not ACK + } + else + { + if (_i2cPort->endTransmission() != 0) //We're done. Release bus. + return (false); //Sensor did not ACK + } + } + + return (bytesWrittenTotal == numDataBytes); + } +} + +// Support for data logging + +//Set the file buffer size. This must be called _before_ .begin +void SFE_UBLOX_GPS::setFileBufferSize(uint16_t bufferSize) +{ + fileBufferSize = bufferSize; +} + +// Extract numBytes of data from the file buffer. Copy it to destination. +// It is the user's responsibility to ensure destination is large enough. +// Returns the number of bytes extracted - which may be less than numBytes. +uint16_t SFE_UBLOX_GPS::extractFileBufferData(uint8_t *destination, uint16_t numBytes) +{ + // Check how many bytes are available in the buffer + uint16_t bytesAvailable = fileBufferSpaceUsed(); + if (numBytes > bytesAvailable) // Limit numBytes if required + numBytes = bytesAvailable; + + // Start copying at fileBufferTail. Wrap-around if required. + uint16_t bytesBeforeWrapAround = fileBufferSize - fileBufferTail; // How much space is available 'above' Tail? + if (bytesBeforeWrapAround > numBytes) // Will we need to wrap-around? + { + bytesBeforeWrapAround = numBytes; // We need to wrap-around + } + memcpy(destination, &ubxFileBuffer[fileBufferTail], bytesBeforeWrapAround); // Copy the data out of the buffer + + // Is there any data leftover which we need to copy from the 'bottom' of the buffer? + uint16_t bytesLeftToCopy = numBytes - bytesBeforeWrapAround; // Calculate if there are any bytes left to copy + if (bytesLeftToCopy > 0) // If there are bytes left to copy + { + memcpy(&destination[bytesBeforeWrapAround], &ubxFileBuffer[0], bytesLeftToCopy); // Copy the remaining data out of the buffer + fileBufferTail = bytesLeftToCopy; // Update Tail. The next byte to be read will be read from here. + } + else + { + fileBufferTail += numBytes; // Only update Tail. The next byte to be read will be read from here. + } + + return (numBytes); // Return the number of bytes extracted +} + +// Returns the number of bytes available in file buffer which are waiting to be read +uint16_t SFE_UBLOX_GPS::fileBufferAvailable(void) +{ + return (fileBufferSpaceUsed()); +} + +// Returns the maximum number of bytes which the file buffer contained. +// Handy for checking the buffer is large enough to handle all the incoming data. +uint16_t SFE_UBLOX_GPS::getMaxFileBufferAvail(void) +{ + return (fileBufferMaxAvail); +} + +// PRIVATE: Create the file buffer. Called by .begin +boolean SFE_UBLOX_GPS::createFileBuffer(void) +{ + if (fileBufferSize == 0) // Bail if the user has not called setFileBufferSize + { + if (_printDebug == true) + { + _debugSerial->println(F("createFileBuffer: Warning. FileBufferSize is zero. Data logging is not possible.")); + } + return(false); + } + + ubxFileBuffer = new uint8_t[fileBufferSize]; // Allocate RAM for the buffer + + if (ubxFileBuffer == NULL) // Check if the new (alloc) was successful + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("createFileBuffer: RAM alloc failed!")); + } + return(false); + } + + fileBufferHead = 0; // Initialize head and tail + fileBufferTail = 0; + + return (true); +} + +// PRIVATE: Check how much space is available in the buffer +uint16_t SFE_UBLOX_GPS::fileBufferSpaceAvailable(void) +{ + return (fileBufferSize - fileBufferSpaceUsed()); +} + +// PRIVATE: Check how much space is used in the buffer +uint16_t SFE_UBLOX_GPS::fileBufferSpaceUsed(void) +{ + if (fileBufferHead >= fileBufferTail) // Check if wrap-around has occurred + { + // Wrap-around has not occurred so do a simple subtraction + return (fileBufferHead - fileBufferTail); + } + else + { + // Wrap-around has occurred so do a simple subtraction but add in the fileBufferSize + return ((uint16_t)(((uint32_t)fileBufferHead + (uint32_t)fileBufferSize) - (uint32_t)fileBufferTail)); + } +} + +// PRIVATE: Add a UBX packet to the file buffer +boolean SFE_UBLOX_GPS::storePacket(ubxPacket *msg) +{ + // First, check that the file buffer has been created + if ((ubxFileBuffer == NULL) || (fileBufferSize == 0)) + { + if (_printDebug == true) + { + _debugSerial->println(F("storePacket: file buffer not available!")); + } + return(false); + } + + // Now, check if there is enough space in the buffer for all of the data + uint16_t totalLength = msg->len + 8; // Total length. Include sync chars, class, id, length and checksum bytes + if (totalLength > fileBufferSpaceAvailable()) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("storePacket: insufficient space available! Data will be lost!")); + } + return(false); + } + + //Store the two sync chars + uint8_t sync_chars[] = {0xB5, 0x62}; + writeToFileBuffer(sync_chars, 2); + + //Store the Class & ID + writeToFileBuffer(&msg->cls, 1); + writeToFileBuffer(&msg->id, 1); + + //Store the length. Ensure length is little-endian + uint8_t msg_length[2]; + msg_length[0] = msg->len & 0xFF; + msg_length[1] = msg->len >> 8; + writeToFileBuffer(msg_length, 2); + + //Store the payload + writeToFileBuffer(msg->payload, msg->len); + + //Store the checksum + writeToFileBuffer(&msg->checksumA, 1); + writeToFileBuffer(&msg->checksumB, 1); + + return (true); +} + +// PRIVATE: Add theBytes to the file buffer +boolean SFE_UBLOX_GPS::storeFileBytes(uint8_t *theBytes, uint16_t numBytes) +{ + // First, check that the file buffer has been created + if ((ubxFileBuffer == NULL) || (fileBufferSize == 0)) + { + if (_printDebug == true) + { + _debugSerial->println(F("storeFileBytes: file buffer not available!")); + } + return(false); + } + + // Now, check if there is enough space in the buffer for all of the data + if (numBytes > fileBufferSpaceAvailable()) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("storeFileBytes: insufficient space available! Data will be lost!")); + } + return(false); + } + + // There is room for all the data in the buffer so copy the data into the buffer + writeToFileBuffer(theBytes, numBytes); + + return (true); +} + +// PRIVATE: Write theBytes to the file buffer +void SFE_UBLOX_GPS::writeToFileBuffer(uint8_t *theBytes, uint16_t numBytes) +{ + // Start writing at fileBufferHead. Wrap-around if required. + uint16_t bytesBeforeWrapAround = fileBufferSize - fileBufferHead; // How much space is available 'above' Head? + if (bytesBeforeWrapAround > numBytes) // Is there enough room for all the data? + { + bytesBeforeWrapAround = numBytes; // There is enough room for all the data + } + memcpy(&ubxFileBuffer[fileBufferHead], theBytes, bytesBeforeWrapAround); // Copy the data into the buffer + + // Is there any data leftover which we need to copy to the 'bottom' of the buffer? + uint16_t bytesLeftToCopy = numBytes - bytesBeforeWrapAround; // Calculate if there are any bytes left to copy + if (bytesLeftToCopy > 0) // If there are bytes left to copy + { + memcpy(&ubxFileBuffer[0], &theBytes[bytesBeforeWrapAround], bytesLeftToCopy); // Copy the remaining data into the buffer + fileBufferHead = bytesLeftToCopy; // Update Head. The next byte written will be written here. + } + else + { + fileBufferHead += numBytes; // Only update Head. The next byte written will be written here. + } + + //Update fileBufferMaxAvail if required + uint16_t bytesInBuffer = fileBufferSpaceUsed(); + if (bytesInBuffer > fileBufferMaxAvail) + fileBufferMaxAvail = bytesInBuffer; +} + +//=-=-=-=-=-=-=-= Specific commands =-=-=-=-=-=-=-==-=-=-=-=-=-=-= +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//Loads the payloadCfg array with the current protocol bits located the UBX-CFG-PRT register for a given port +boolean SFE_UBLOX_GPS::getPortSettings(uint8_t portID, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 1; + packetCfg.startingSpot = 0; + + payloadCfg[0] = portID; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +boolean SFE_UBLOX_GPS::setPortOutput(uint8_t portID, uint8_t outStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[14] = outStreamSettings; //OutProtocolMask LSB - Set outStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof +//Port 0=I2c, 1=UART1, 2=UART2, 3=USB, 4=SPI +//Bit:0 = UBX, :1=NMEA, :5=RTCM3 +boolean SFE_UBLOX_GPS::setPortInput(uint8_t portID, uint8_t inStreamSettings, uint16_t maxWait) +{ + //Get the current config values for this port ID + //This will load the payloadCfg array with current port settings + if (getPortSettings(portID, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[12] = inStreamSettings; //InProtocolMask LSB - Set inStream bits + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Changes the I2C address that the u-blox module responds to +//0x42 is the default but can be changed with this command +boolean SFE_UBLOX_GPS::setI2CAddress(uint8_t deviceAddress, uint16_t maxWait) +{ + //Get the current config values for the I2C port + //This will load the payloadCfg array with current port settings + if (getPortSettings(COM_PORT_I2C, maxWait) == false) + return (false); //Something went wrong. Bail. + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[4] = deviceAddress << 1; //DDC mode LSB + + if (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT) // We are only expecting an ACK + { + //Success! Now change our internal global. + _gpsI2Caddress = deviceAddress; //Store the I2C address from user + return (true); + } + return (false); +} + +//Changes the serial baud rate of the u-blox module, can't return success/fail 'cause ACK from modem +//is lost due to baud rate change +void SFE_UBLOX_GPS::setSerialRate(uint32_t baudrate, uint8_t uartPort, uint16_t maxWait) +{ + //Get the current config values for the UART port + //This will load the payloadCfg array with current port settings + if (getPortSettings(uartPort, maxWait) == false) + return; //Something went wrong. Bail. + + if (_printDebug == true) + { + _debugSerial->print(F("Current baud rate: ")); + _debugSerial->println(((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_PRT; + packetCfg.len = 20; + packetCfg.startingSpot = 0; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[8] = baudrate; + payloadCfg[9] = baudrate >> 8; + payloadCfg[10] = baudrate >> 16; + payloadCfg[11] = baudrate >> 24; + + if (_printDebug == true) + { + _debugSerial->print(F("New baud rate:")); + _debugSerial->println(((uint32_t)payloadCfg[10] << 16) | ((uint32_t)payloadCfg[9] << 8) | payloadCfg[8]); + } + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + _debugSerial->print(F("setSerialRate: sendCommand returned: ")); + _debugSerial->println(statusString(retVal)); + } +} + +//Configure a port to output UBX, NMEA, RTCM3 or a combination thereof +boolean SFE_UBLOX_GPS::setI2COutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_I2C, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUART1Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART1, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUART2Output(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_UART2, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setUSBOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_USB, comSettings, maxWait)); +} +boolean SFE_UBLOX_GPS::setSPIOutput(uint8_t comSettings, uint16_t maxWait) +{ + return (setPortOutput(COM_PORT_SPI, comSettings, maxWait)); +} + +//Want to see the NMEA messages on the Serial port? Here's how +void SFE_UBLOX_GPS::setNMEAOutputPort(Stream &nmeaOutputPort) +{ + _nmeaOutputPort = &nmeaOutputPort; //Store the port from user +} + +// Reset to defaults + +void SFE_UBLOX_GPS::factoryReset() +{ + // Copy default settings to permanent + // Note: this does not load the permanent configuration into the current configuration. Calling factoryDefault() will do that. + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 13; + packetCfg.startingSpot = 0; + for (uint8_t i = 0; i < 4; i++) + { + payloadCfg[0 + i] = 0xff; // clear mask: copy default config to permanent config + payloadCfg[4 + i] = 0x00; // save mask: don't save current to permanent + payloadCfg[8 + i] = 0x00; // load mask: don't copy permanent config to current + } + payloadCfg[12] = 0xff; // all forms of permanent memory + sendCommand(&packetCfg, 0); // don't expect ACK + hardReset(); // cause factory default config to actually be loaded and used cleanly +} + +void SFE_UBLOX_GPS::hardReset() +{ + // Issue hard reset + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RST; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + payloadCfg[0] = 0xff; // cold start + payloadCfg[1] = 0xff; // cold start + payloadCfg[2] = 0; // 0=HW reset + payloadCfg[3] = 0; // reserved + sendCommand(&packetCfg, 0); // don't expect ACK +} + +//Reset module to factory defaults +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::factoryDefault(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + memset(payloadCfg, 0, packetCfg.len); + + packetCfg.payload[0] = 0xFF; //Set any bit in the clearMask field to clear saved config + packetCfg.payload[1] = 0xFF; + packetCfg.payload[8] = 0xFF; //Set any bit in the loadMask field to discard current config and rebuild from lower non-volatile memory layers + packetCfg.payload[9] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Save configuration to BBR / Flash + +//Save current configuration to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::saveConfiguration(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + memset(payloadCfg, 0, packetCfg.len); + + packetCfg.payload[4] = 0xFF; //Set any bit in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Save the selected configuration sub-sections to flash and BBR (battery backed RAM) +//This still works but it is the old way of configuring ublox modules. See getVal and setVal for the new methods +boolean SFE_UBLOX_GPS::saveConfigSelective(uint32_t configMask, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_CFG; + packetCfg.len = 12; + packetCfg.startingSpot = 0; + + //Clear packet payload + memset(payloadCfg, 0, packetCfg.len); + + packetCfg.payload[4] = configMask & 0xFF; //Set the appropriate bits in the saveMask field to save current config to Flash and BBR + packetCfg.payload[5] = (configMask >> 8) & 0xFF; + packetCfg.payload[6] = (configMask >> 16) & 0xFF; + packetCfg.payload[7] = (configMask >> 24) & 0xFF; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Configure a given message type for a given port (UART1, I2C, SPI, etc) +boolean SFE_UBLOX_GPS::configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + //Poll for the current settings for a given message + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + payloadCfg[0] = msgClass; + payloadCfg[1] = msgID; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + //Now send it back with new mods + packetCfg.len = 8; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[2 + portID] = sendRate; //Send rate is relative to the event a message is registered on. For example, if the rate of a navigation message is set to 2, the message is sent every 2nd navigation solution. + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Enable a given message type, default of 1 per update rate (usually 1 per second) +boolean SFE_UBLOX_GPS::enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, rate, maxWait)); +} +//Disable a given message type on a given port +boolean SFE_UBLOX_GPS::disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (configureMessage(msgClass, msgID, portID, 0, maxWait)); +} + +boolean SFE_UBLOX_GPS::enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t rate, uint16_t maxWait) +{ + return (configureMessage(UBX_CLASS_NMEA, msgID, portID, rate, maxWait)); +} +boolean SFE_UBLOX_GPS::disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait) +{ + return (enableNMEAMessage(msgID, portID, 0, maxWait)); +} + +//Given a message number turns on a message ID for output over a given portID (UART, I2C, SPI, USB, etc) +//To disable a message, set secondsBetween messages to 0 +//Note: This function will return false if the message is already enabled +//For base station RTK output we need to enable various sentences + +//NEO-M8P has four: +//1005 = 0xF5 0x05 - Stationary RTK reference ARP +//1077 = 0xF5 0x4D - GPS MSM7 +//1087 = 0xF5 0x57 - GLONASS MSM7 +//1230 = 0xF5 0xE6 - GLONASS code-phase biases, set to once every 10 seconds + +//ZED-F9P has six: +//1005, 1074, 1084, 1094, 1124, 1230 + +//Much of this configuration is not documented and instead discerned from u-center binary console +boolean SFE_UBLOX_GPS::enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait) +{ + return (configureMessage(UBX_RTCM_MSB, messageNumber, portID, sendRate, maxWait)); +} + +//Disable a given message on a given port by setting secondsBetweenMessages to zero +boolean SFE_UBLOX_GPS::disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait) +{ + return (enableRTCMmessage(messageNumber, portID, 0, maxWait)); +} + +//Functions used for RTK and base station setup + +//Get the current TimeMode3 settings - these contain survey in statuses +boolean SFE_UBLOX_GPS::getSurveyMode(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_RECEIVED); // We are expecting data and an ACK +} + +//Control Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + if (getSurveyMode(maxWait) == false) //Ask module for the current TimeMode3 settings. Loads into payloadCfg. + return (false); + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 40; + packetCfg.startingSpot = 0; + + //payloadCfg should be loaded with poll response. Now modify only the bits we care about + payloadCfg[2] = mode; //Set mode. Survey-In and Disabled are most common. Use ECEF (not LAT/LON/ALT). + + //svinMinDur is U4 (uint32_t) but we'll only use a uint16_t (waiting more than 65535 seconds seems excessive!) + payloadCfg[24] = observationTime & 0xFF; //svinMinDur in seconds + payloadCfg[25] = observationTime >> 8; //svinMinDur in seconds + payloadCfg[26] = 0; //Truncate to 16 bits + payloadCfg[27] = 0; //Truncate to 16 bits + + //svinAccLimit is U4 (uint32_t) in 0.1mm. + uint32_t svinAccLimit = (uint32_t)(requiredAccuracy * 10000.0); //Convert m to 0.1mm + payloadCfg[28] = svinAccLimit & 0xFF; //svinAccLimit in 0.1mm increments + payloadCfg[29] = svinAccLimit >> 8; + payloadCfg[30] = svinAccLimit >> 16; + payloadCfg[31] = svinAccLimit >> 24; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Begin Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_ENABLE, observationTime, requiredAccuracy, maxWait)); +} + +//Stop Survey-In for NEO-M8P +boolean SFE_UBLOX_GPS::disableSurveyMode(uint16_t maxWait) +{ + return (setSurveyMode(SVIN_MODE_DISABLE, 0, 0, maxWait)); +} + +//Set the ECEF or Lat/Long coordinates of a receiver +//This imediately puts the receiver in TIME mode (fixed) and will begin outputting RTCM sentences if enabled +//This is helpful once an antenna's position has been established. See this tutorial: https://learn.sparkfun.com/tutorials/how-to-build-a-diy-gnss-reference-station#gather-raw-gnss-data +// For ECEF the units are: cm, 0.1mm, cm, 0.1mm, cm, 0.1mm +// For Lat/Lon/Alt the units are: degrees^-7, degrees^-9, degrees^-7, degrees^-9, cm, 0.1mm +bool SFE_UBLOX_GPS::setStaticPosition(int32_t ecefXOrLat, int8_t ecefXOrLatHP, int32_t ecefYOrLon, int8_t ecefYOrLonHP, int32_t ecefZOrAlt, int8_t ecefZOrAltHP, bool latLong, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_TMODE3; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current TimeMode3 settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); + + packetCfg.len = 40; + + //customCfg should be loaded with poll response. Now modify only the bits we care about + payloadCfg[2] = 2; //Set mode to fixed. Use ECEF (not LAT/LON/ALT). + + if (latLong == true) + payloadCfg[3] = (uint8_t)(1 << 0); //Set mode to fixed. Use LAT/LON/ALT. + + //Set ECEF X or Lat + payloadCfg[4] = (ecefXOrLat >> 8 * 0) & 0xFF; //LSB + payloadCfg[5] = (ecefXOrLat >> 8 * 1) & 0xFF; + payloadCfg[6] = (ecefXOrLat >> 8 * 2) & 0xFF; + payloadCfg[7] = (ecefXOrLat >> 8 * 3) & 0xFF; //MSB + + //Set ECEF Y or Long + payloadCfg[8] = (ecefYOrLon >> 8 * 0) & 0xFF; //LSB + payloadCfg[9] = (ecefYOrLon >> 8 * 1) & 0xFF; + payloadCfg[10] = (ecefYOrLon >> 8 * 2) & 0xFF; + payloadCfg[11] = (ecefYOrLon >> 8 * 3) & 0xFF; //MSB + + //Set ECEF Z or Altitude + payloadCfg[12] = (ecefZOrAlt >> 8 * 0) & 0xFF; //LSB + payloadCfg[13] = (ecefZOrAlt >> 8 * 1) & 0xFF; + payloadCfg[14] = (ecefZOrAlt >> 8 * 2) & 0xFF; + payloadCfg[15] = (ecefZOrAlt >> 8 * 3) & 0xFF; //MSB + + //Set high precision parts + payloadCfg[16] = ecefXOrLatHP; + payloadCfg[17] = ecefYOrLonHP; + payloadCfg[18] = ecefZOrAltHP; + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +bool SFE_UBLOX_GPS::setStaticPosition(int32_t ecefXOrLat, int32_t ecefYOrLon, int32_t ecefZOrAlt, bool latlong, uint16_t maxWait) +{ + return (setStaticPosition(ecefXOrLat, 0, ecefYOrLon, 0, ecefZOrAlt, 0, latlong, maxWait)); +} + +// Module Protocol Version + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionHigh(uint16_t maxWait) +{ + if (moduleSWVersion == NULL) initModuleSWVersion(); //Check that RAM has been allocated for the SW version + if (moduleSWVersion == NULL) //Bail if the RAM allocation failed + return (false); + + if (moduleSWVersion->moduleQueried == false) + getProtocolVersion(maxWait); + return (moduleSWVersion->versionHigh); +} + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +uint8_t SFE_UBLOX_GPS::getProtocolVersionLow(uint16_t maxWait) +{ + if (moduleSWVersion == NULL) initModuleSWVersion(); //Check that RAM has been allocated for the SW version + if (moduleSWVersion == NULL) //Bail if the RAM allocation failed + return (false); + + if (moduleSWVersion->moduleQueried == false) + getProtocolVersion(maxWait); + return (moduleSWVersion->versionLow); +} + +//Get the current protocol version of the u-blox module we're communicating with +//This is helpful when deciding if we should call the high-precision Lat/Long (HPPOSLLH) or the regular (POSLLH) +boolean SFE_UBLOX_GPS::getProtocolVersion(uint16_t maxWait) +{ + if (moduleSWVersion == NULL) initModuleSWVersion(); //Check that RAM has been allocated for the SW version + if (moduleSWVersion == NULL) //Bail if the RAM allocation failed + return (false); + + //Send packet with only CLS and ID, length of zero. This will cause the module to respond with the contents of that CLS/ID. + packetCfg.cls = UBX_CLASS_MON; + packetCfg.id = UBX_MON_VER; + + packetCfg.len = 0; + packetCfg.startingSpot = 40; //Start at first "extended software information" string + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are only expecting data (no ACK) + return (false); //If command send fails then bail + + //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(); + // } + + //We will step through the payload looking at each extension field of 30 bytes + for (uint8_t extensionNumber = 0; extensionNumber < 10; extensionNumber++) + { + //Now we need to find "PROTVER=18.00" in the incoming byte stream + if ((payloadCfg[(30 * extensionNumber) + 0] == 'P') && (payloadCfg[(30 * extensionNumber) + 6] == 'R')) + { + moduleSWVersion->versionHigh = (payloadCfg[(30 * extensionNumber) + 8] - '0') * 10 + (payloadCfg[(30 * extensionNumber) + 9] - '0'); //Convert '18' to 18 + moduleSWVersion->versionLow = (payloadCfg[(30 * extensionNumber) + 11] - '0') * 10 + (payloadCfg[(30 * extensionNumber) + 12] - '0'); //Convert '00' to 00 + moduleSWVersion->moduleQueried = true; // Mark this data as new + + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version: ")); + _debugSerial->print(moduleSWVersion->versionHigh); + _debugSerial->print(F(".")); + _debugSerial->println(moduleSWVersion->versionLow); + } + return (true); //Success! + } + } + + return (false); //We failed +} + +// PRIVATE: Allocate RAM for moduleSWVersion and initialize it +boolean SFE_UBLOX_GPS::initModuleSWVersion() +{ + moduleSWVersion = new moduleSWVersion_t; //Allocate RAM for the main struct + if (moduleSWVersion == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initModuleSWVersion: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + moduleSWVersion->versionHigh = 0; + moduleSWVersion->versionLow = 0; + moduleSWVersion->moduleQueried = false; + return (true); +} + +// Geofences + +//Add a new geofence using UBX-CFG-GEOFENCE +boolean SFE_UBLOX_GPS::addGeofence(int32_t latitude, int32_t longitude, uint32_t radius, byte confidence, byte pinPolarity, byte pin, uint16_t maxWait) +{ + if (currentGeofenceParams == NULL) initGeofenceParams(); // Check if RAM has been allocated for currentGeofenceParams + if (currentGeofenceParams == NULL) // Abort if the RAM allocation failed + return (false); + + if (currentGeofenceParams->numFences >= 4) + return (false); // Quit if we already have four geofences defined + + // Store the new geofence parameters + currentGeofenceParams->lats[currentGeofenceParams->numFences] = latitude; + currentGeofenceParams->longs[currentGeofenceParams->numFences] = longitude; + currentGeofenceParams->rads[currentGeofenceParams->numFences] = radius; + currentGeofenceParams->numFences = currentGeofenceParams->numFences + 1; // Increment the number of fences + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = (currentGeofenceParams->numFences * 12) + 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = currentGeofenceParams->numFences; // numFences + payloadCfg[2] = confidence; // confLvl = Confidence level 0-4 (none, 68%, 95%, 99.7%, 99.99%) + payloadCfg[3] = 0; // reserved1 + if (pin > 0) + { + payloadCfg[4] = 1; // enable PIO combined fence state + } + else + { + payloadCfg[4] = 0; // disable PIO combined fence state + } + payloadCfg[5] = pinPolarity; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = pin; // PIO pin + payloadCfg[7] = 0; //reserved2 + payloadCfg[8] = currentGeofenceParams->lats[0] & 0xFF; + payloadCfg[9] = currentGeofenceParams->lats[0] >> 8; + payloadCfg[10] = currentGeofenceParams->lats[0] >> 16; + payloadCfg[11] = currentGeofenceParams->lats[0] >> 24; + payloadCfg[12] = currentGeofenceParams->longs[0] & 0xFF; + payloadCfg[13] = currentGeofenceParams->longs[0] >> 8; + payloadCfg[14] = currentGeofenceParams->longs[0] >> 16; + payloadCfg[15] = currentGeofenceParams->longs[0] >> 24; + payloadCfg[16] = currentGeofenceParams->rads[0] & 0xFF; + payloadCfg[17] = currentGeofenceParams->rads[0] >> 8; + payloadCfg[18] = currentGeofenceParams->rads[0] >> 16; + payloadCfg[19] = currentGeofenceParams->rads[0] >> 24; + if (currentGeofenceParams->numFences >= 2) + { + payloadCfg[20] = currentGeofenceParams->lats[1] & 0xFF; + payloadCfg[21] = currentGeofenceParams->lats[1] >> 8; + payloadCfg[22] = currentGeofenceParams->lats[1] >> 16; + payloadCfg[23] = currentGeofenceParams->lats[1] >> 24; + payloadCfg[24] = currentGeofenceParams->longs[1] & 0xFF; + payloadCfg[25] = currentGeofenceParams->longs[1] >> 8; + payloadCfg[26] = currentGeofenceParams->longs[1] >> 16; + payloadCfg[27] = currentGeofenceParams->longs[1] >> 24; + payloadCfg[28] = currentGeofenceParams->rads[1] & 0xFF; + payloadCfg[29] = currentGeofenceParams->rads[1] >> 8; + payloadCfg[30] = currentGeofenceParams->rads[1] >> 16; + payloadCfg[31] = currentGeofenceParams->rads[1] >> 24; + } + if (currentGeofenceParams->numFences >= 3) + { + payloadCfg[32] = currentGeofenceParams->lats[2] & 0xFF; + payloadCfg[33] = currentGeofenceParams->lats[2] >> 8; + payloadCfg[34] = currentGeofenceParams->lats[2] >> 16; + payloadCfg[35] = currentGeofenceParams->lats[2] >> 24; + payloadCfg[36] = currentGeofenceParams->longs[2] & 0xFF; + payloadCfg[37] = currentGeofenceParams->longs[2] >> 8; + payloadCfg[38] = currentGeofenceParams->longs[2] >> 16; + payloadCfg[39] = currentGeofenceParams->longs[2] >> 24; + payloadCfg[40] = currentGeofenceParams->rads[2] & 0xFF; + payloadCfg[41] = currentGeofenceParams->rads[2] >> 8; + payloadCfg[42] = currentGeofenceParams->rads[2] >> 16; + payloadCfg[43] = currentGeofenceParams->rads[2] >> 24; + } + if (currentGeofenceParams->numFences >= 4) + { + payloadCfg[44] = currentGeofenceParams->lats[3] & 0xFF; + payloadCfg[45] = currentGeofenceParams->lats[3] >> 8; + payloadCfg[46] = currentGeofenceParams->lats[3] >> 16; + payloadCfg[47] = currentGeofenceParams->lats[3] >> 24; + payloadCfg[48] = currentGeofenceParams->longs[3] & 0xFF; + payloadCfg[49] = currentGeofenceParams->longs[3] >> 8; + payloadCfg[50] = currentGeofenceParams->longs[3] >> 16; + payloadCfg[51] = currentGeofenceParams->longs[3] >> 24; + payloadCfg[52] = currentGeofenceParams->rads[3] & 0xFF; + payloadCfg[53] = currentGeofenceParams->rads[3] >> 8; + payloadCfg[54] = currentGeofenceParams->rads[3] >> 16; + payloadCfg[55] = currentGeofenceParams->rads[3] >> 24; + } + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear all geofences using UBX-CFG-GEOFENCE +boolean SFE_UBLOX_GPS::clearGeofences(uint16_t maxWait) +{ + if (currentGeofenceParams == NULL) initGeofenceParams(); // Check if RAM has been allocated for currentGeofenceParams + if (currentGeofenceParams == NULL) // Abort if the RAM allocation failed + return (false); + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GEOFENCE; + packetCfg.len = 8; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0; // Message version = 0x00 + payloadCfg[1] = 0; // numFences + payloadCfg[2] = 0; // confLvl + payloadCfg[3] = 0; // reserved1 + payloadCfg[4] = 0; // disable PIO combined fence state + payloadCfg[5] = 0; // PIO pin polarity (0 = low means inside, 1 = low means outside (or unknown)) + payloadCfg[6] = 0; // PIO pin + payloadCfg[7] = 0; //reserved2 + + currentGeofenceParams->numFences = 0; // Zero the number of geofences currently in use + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Clear the antenna control settings using UBX-CFG-ANT +//This function is hopefully redundant but may be needed to release +//any PIO pins pre-allocated for antenna functions +boolean SFE_UBLOX_GPS::clearAntPIO(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_ANT; + packetCfg.len = 4; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0x10; // Antenna flag mask: set the recovery bit + payloadCfg[1] = 0; + payloadCfg[2] = 0xFF; // Antenna pin configuration: set pinSwitch and pinSCD to 31 + payloadCfg[3] = 0xFF; // Antenna pin configuration: set pinOCD to 31, set reconfig bit + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Returns the combined geofence state using UBX-NAV-GEOFENCE +boolean SFE_UBLOX_GPS::getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_GEOFENCE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the geofence status. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + currentGeofenceState.status = payloadCfg[5]; // Extract the status + currentGeofenceState.numFences = payloadCfg[6]; // Extract the number of geofences + currentGeofenceState.combState = payloadCfg[7]; // Extract the combined state of all geofences + if (currentGeofenceState.numFences > 0) + currentGeofenceState.states[0] = payloadCfg[8]; // Extract geofence 1 state + if (currentGeofenceState.numFences > 1) + currentGeofenceState.states[1] = payloadCfg[10]; // Extract geofence 2 state + if (currentGeofenceState.numFences > 2) + currentGeofenceState.states[2] = payloadCfg[12]; // Extract geofence 3 state + if (currentGeofenceState.numFences > 3) + currentGeofenceState.states[3] = payloadCfg[14]; // Extract geofence 4 state + + return (true); +} + +// PRIVATE: Allocate RAM for currentGeofenceParams and initialize it +boolean SFE_UBLOX_GPS::initGeofenceParams() +{ + currentGeofenceParams = new geofenceParams_t; //Allocate RAM for the main struct + if (currentGeofenceParams == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initGeofenceParams: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + currentGeofenceParams->numFences = 0; + return (true); +} + +//Power Save Mode +//Enables/Disables Low Power Mode using UBX-CFG-RXM +boolean SFE_UBLOX_GPS::powerSaveMode(bool power_save, uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version is ")); + _debugSerial->println(protVer); + } + */ + if (protVer >= 27) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version")); + } + return (false); + } + + // Now let's change the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + if (power_save) + { + payloadCfg[1] = 1; // Power Save Mode + } + else + { + payloadCfg[1] = 0; // Continuous Mode + } + + packetCfg.len = 2; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +// Get Power Save Mode +// Returns the current Low Power Mode using UBX-CFG-RXM +// Returns 255 if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getPowerSaveMode(uint16_t maxWait) +{ + // Let's begin by checking the Protocol Version as UBX_CFG_RXM is not supported on the ZED (protocol >= 27) + uint8_t protVer = getProtocolVersionHigh(maxWait); + /* + if (_printDebug == true) + { + _debugSerial->print(F("Protocol version is ")); + _debugSerial->println(protVer); + } + */ + if (protVer >= 27) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + { + _debugSerial->println(F("powerSaveMode (UBX-CFG-RXM) is not supported by this protocol version")); + } + return (255); + } + + // Now let's read the power setting using UBX-CFG-RXM + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RXM; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current power management settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (255); + + return (payloadCfg[1]); // Return the low power mode +} + +// Powers off the GPS device for a given duration to reduce power consumption. +// NOTE: Querying the device before the duration is complete, for example by "getLatitude()" will wake it up! +// Returns true if command has not been not acknowledged. +// Returns false if command has not been acknowledged or maxWait = 0. +boolean SFE_UBLOX_GPS::powerOff(uint32_t durationInMs, uint16_t maxWait) +{ + // use durationInMs = 0 for infinite duration + if (_printDebug == true) + { + _debugSerial->print(F("Powering off for ")); + _debugSerial->print(durationInMs); + _debugSerial->println(" ms"); + } + + // Power off device using UBX-RXM-PMREQ + packetCfg.cls = UBX_CLASS_RXM; // 0x02 + packetCfg.id = UBX_RXM_PMREQ; // 0x41 + packetCfg.len = 8; + packetCfg.startingSpot = 0; + + // duration + // big endian to little endian, switch byte order + payloadCfg[0] = (durationInMs >> (8 * 0)) & 0xff; + payloadCfg[1] = (durationInMs >> (8 * 1)) & 0xff; + payloadCfg[2] = (durationInMs >> (8 * 2)) & 0xff; + payloadCfg[3] = (durationInMs >> (8 * 3)) & 0xff; + + payloadCfg[4] = 0x02; //Flags : set the backup bit + payloadCfg[5] = 0x00; //Flags + payloadCfg[6] = 0x00; //Flags + payloadCfg[7] = 0x00; //Flags + + if (maxWait != 0) + { + // check for "not acknowledged" command + return (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_COMMAND_NACK); + } + else + { + sendCommand(&packetCfg, maxWait); + return false; // can't tell if command not acknowledged if maxWait = 0 + } +} + +// Powers off the GPS device for a given duration to reduce power consumption. +// While powered off it can be woken up by creating a falling or rising voltage edge on the specified pin. +// NOTE: The GPS seems to be sensitve to signals on the pins while powered off. Works best when Microcontroller is in deepsleep. +// NOTE: Querying the device before the duration is complete, for example by "getLatitude()" will wake it up! +// Returns true if command has not been not acknowledged. +// Returns false if command has not been acknowledged or maxWait = 0. +boolean SFE_UBLOX_GPS::powerOffWithInterrupt(uint32_t durationInMs, uint32_t wakeupSources, boolean forceWhileUsb, uint16_t maxWait) +{ + // use durationInMs = 0 for infinite duration + if (_printDebug == true) + { + _debugSerial->print(F("Powering off for ")); + _debugSerial->print(durationInMs); + _debugSerial->println(" ms"); + } + + // Power off device using UBX-RXM-PMREQ + packetCfg.cls = UBX_CLASS_RXM; // 0x02 + packetCfg.id = UBX_RXM_PMREQ; // 0x41 + packetCfg.len = 16; + packetCfg.startingSpot = 0; + + payloadCfg[0] = 0x00; // message version + + // bytes 1-3 are reserved - and must be set to zero + payloadCfg[1] = 0x00; + payloadCfg[2] = 0x00; + payloadCfg[3] = 0x00; + + // duration + // big endian to little endian, switch byte order + payloadCfg[4] = (durationInMs >> (8 * 0)) & 0xff; + payloadCfg[5] = (durationInMs >> (8 * 1)) & 0xff; + payloadCfg[6] = (durationInMs >> (8 * 2)) & 0xff; + payloadCfg[7] = (durationInMs >> (8 * 3)) & 0xff; + + // flags + + // disables USB interface when powering off, defaults to true + if (forceWhileUsb) + { + payloadCfg[8] = 0x06; // force | backup + } + else + { + payloadCfg[8] = 0x02; // backup only (leave the force bit clear - module will stay on if USB is connected) + } + + payloadCfg[9] = 0x00; + payloadCfg[10] = 0x00; + payloadCfg[11] = 0x00; + + // wakeUpSources + + // wakeupPin mapping, defaults to VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 + + // Possible values are: + // VAL_RXM_PMREQ_WAKEUPSOURCE_UARTRX + // VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 + // VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT1 + // VAL_RXM_PMREQ_WAKEUPSOURCE_SPICS + + payloadCfg[12] = (wakeupSources >> (8 * 0)) & 0xff; + payloadCfg[13] = (wakeupSources >> (8 * 1)) & 0xff; + payloadCfg[14] = (wakeupSources >> (8 * 2)) & 0xff; + payloadCfg[15] = (wakeupSources >> (8 * 3)) & 0xff; + + if (maxWait != 0) + { + // check for "not acknowledged" command + return (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_COMMAND_NACK); + } + else + { + sendCommand(&packetCfg, maxWait); + return false; // can't tell if command not acknowledged if maxWait = 0 + } +} + +//Dynamic Platform Model + +//Change the dynamic platform model using UBX-CFG-NAV5 +//Possible values are: +//PORTABLE,STATIONARY,PEDESTRIAN,AUTOMOTIVE,SEA, +//AIRBORNE1g,AIRBORNE2g,AIRBORNE4g,WRIST,BIKE +//WRIST is not supported in protocol versions less than 18 +//BIKE is supported in protocol versions 19.2 +boolean SFE_UBLOX_GPS::setDynamicModel(dynModel newDynamicModel, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + payloadCfg[0] = 0x01; // mask: set only the dyn bit (0) + payloadCfg[1] = 0x00; // mask + payloadCfg[2] = newDynamicModel; // dynModel + + packetCfg.len = 36; + packetCfg.startingSpot = 0; + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the dynamic platform model using UBX-CFG-NAV5 +//Returns DYN_MODEL_UNKNOWN (255) if the sendCommand fails +uint8_t SFE_UBLOX_GPS::getDynamicModel(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_NAV5; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current navigation model settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (DYN_MODEL_UNKNOWN); + + return (payloadCfg[2]); // Return the dynamic model +} + +//Reset the odometer +boolean SFE_UBLOX_GPS::resetOdometer(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_RESETODO; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + // This is a special case as we are only expecting an ACK but this is not a CFG message + return (sendCommand(&packetCfg, maxWait, true) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Enable/Disable individual GNSS systems using UBX-CFG-GNSS +boolean SFE_UBLOX_GPS::enableGNSS(boolean enable, sfe_ublox_gnss_ids_e id, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GNSS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + uint8_t numConfigBlocks = payloadCfg[3]; // Extract the numConfigBlocks + + for (uint8_t block = 0; block < numConfigBlocks; block++) // Check each configuration block + { + if (payloadCfg[(block * 8) + 4] == (uint8_t)id) // Check the gnssId for this block. Do we have a match? + { + // We have a match so set/clear the enable bit in flags + if (enable) + payloadCfg[(block * 8) + 4 + 4] |= 0x01; // Set the enable bit in flags (Little Endian) + else + payloadCfg[(block * 8) + 4 + 4] &= 0xFE; // Clear the enable bit in flags (Little Endian) + } + } + + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Check if an individual GNSS system is enabled +boolean SFE_UBLOX_GPS::isGNSSenabled(sfe_ublox_gnss_ids_e id, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_GNSS; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); + + boolean retVal = false; + + uint8_t numConfigBlocks = payloadCfg[3]; // Extract the numConfigBlocks + + for (uint8_t block = 0; block < numConfigBlocks; block++) // Check each configuration block + { + if (payloadCfg[(block * 8) + 4] == (uint8_t)id) // Check the gnssId for this block. Do we have a match? + { + // We have a match so check the enable bit in flags + if ((payloadCfg[(block * 8) + 4 + 4] & 0x01) > 0) // Check the enable bit in flags (Little Endian) + retVal = true; + } + } + + return (retVal); +} + +//Reset ESF automatic IMU-mount alignment +boolean SFE_UBLOX_GPS::resetIMUalignment(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_RESETALG; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + // This is a special case as we are only expecting an ACK but this is not a CFG message + return (sendCommand(&packetCfg, maxWait, true) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +// CONFIGURATION INTERFACE (protocol v27 and above) + +//Form 32-bit key from group/id/size +uint32_t SFE_UBLOX_GPS::createKey(uint16_t group, uint16_t id, uint8_t size) +{ + uint32_t key = 0; + key |= (uint32_t)id; + key |= (uint32_t)group << 16; + key |= (uint32_t)size << 28; + return (key); +} + +//Given a key, load the payload with data that can then be extracted to 8, 16, or 32 bits +//This function takes a full 32-bit key +//Default layer is RAM +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +sfe_ublox_status_e SFE_UBLOX_GPS::getVal(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALGET; + packetCfg.len = 4 + 4 * 1; //While multiple keys are allowed, we will send only one key at a time + packetCfg.startingSpot = 0; + + //Clear packet payload + memset(payloadCfg, 0, packetCfg.len); + + //VALGET uses different memory layer definitions to VALSET + //because it can only return the value for one layer. + //So we need to fiddle the layer here. + //And just to complicate things further, the ZED-F9P only responds + //correctly to layer 0 (RAM) and layer 7 (Default)! + uint8_t getLayer = 7; // 7 is the "Default Layer" + if ((layer & VAL_LAYER_RAM) == VAL_LAYER_RAM) // Did the user request the RAM layer? + { + getLayer = 0; // Layer 0 is RAM + } + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = getLayer; //Layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + if (_printDebug == true) + { + _debugSerial->print(F("key: 0x")); + _debugSerial->print(key, HEX); + _debugSerial->println(); + } + + //Send VALGET command with this key + + sfe_ublox_status_e retVal = sendCommand(&packetCfg, maxWait); + if (_printDebug == true) + { + _debugSerial->print(F("getVal: sendCommand returned: ")); + _debugSerial->println(statusString(retVal)); + } + + //Verify the response is the correct length as compared to what the user called (did the module respond with 8-bits but the user called getVal32?) + //Response is 8 bytes plus cfg data + //if(packet->len > 8+1) + + //The response is now sitting in payload, ready for extraction + return (retVal); +} + +//Given a key, return its value +//This function takes a full 32-bit key +//Default layer is RAM +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + if (getVal(key, layer, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (0); + + return (extractByte(&packetCfg, 8)); +} +uint16_t SFE_UBLOX_GPS::getVal16(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + if (getVal(key, layer, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (0); + + return (extractInt(&packetCfg, 8)); +} +uint32_t SFE_UBLOX_GPS::getVal32(uint32_t key, uint8_t layer, uint16_t maxWait) +{ + if (getVal(key, layer, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (0); + + return (extractLong(&packetCfg, 8)); +} + +//Given a group, ID and size, return the value of this config spot +//The 32-bit key is put together from group/ID/size. See other getVal to send key directly. +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer, uint16_t maxWait) +{ + uint32_t key = createKey(group, id, size); + return getVal8(key, layer, maxWait); +} +uint16_t SFE_UBLOX_GPS::getVal16(uint16_t group, uint16_t id, uint8_t size, uint8_t layer, uint16_t maxWait) +{ + uint32_t key = createKey(group, id, size); + return getVal16(key, layer, maxWait); +} +uint32_t SFE_UBLOX_GPS::getVal32(uint16_t group, uint16_t id, uint8_t size, uint8_t layer, uint16_t maxWait) +{ + uint32_t key = createKey(group, id, size); + return getVal32(key, layer, maxWait); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is all: RAM+BBR+Flash +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + return setVal16(key, value, layer, maxWait); +} + +//Given a key, set a 16-bit value +//This function takes a full 32-bit key +//Default layer is all: RAM+BBR+Flash +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal16(uint32_t key, uint16_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + memset(payloadCfg, 0, packetCfg.len); + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set an 8-bit value +//This function takes a full 32-bit key +//Default layer is all: RAM+BBR+Flash +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal8(uint32_t key, uint8_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear packet payload + memset(payloadCfg, 0, packetCfg.len); + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Given a key, set a 32-bit value +//This function takes a full 32-bit key +//Default layer is all: RAM+BBR+Flash +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::setVal32(uint32_t key, uint32_t value, uint8_t layer, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear packet payload + memset(payloadCfg, 0, packetCfg.len); + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset32(uint32_t key, uint32_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 4; //4 byte header, 4 byte key ID, 4 bytes of value + packetCfg.startingSpot = 0; + + //Clear all of packet payload + memset(payloadCfg, 0, packetCfgPayloadSize); + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + payloadCfg[10] = value >> 8 * 2; + payloadCfg[11] = value >> 8 * 3; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset16(uint32_t key, uint16_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 2; //4 byte header, 4 byte key ID, 2 bytes of value + packetCfg.startingSpot = 0; + + //Clear all of packet payload + memset(payloadCfg, 0, packetCfgPayloadSize); + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value >> 8 * 0; //Value LSB + payloadCfg[9] = value >> 8 * 1; + + //All done + return (true); +} + +//Start defining a new UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +//Default layer is BBR +//Configuration of modern u-blox modules is now done via getVal/setVal/delVal, ie protocol v27 and above found on ZED-F9P +uint8_t SFE_UBLOX_GPS::newCfgValset8(uint32_t key, uint8_t value, uint8_t layer) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_VALSET; + packetCfg.len = 4 + 4 + 1; //4 byte header, 4 byte key ID, 1 byte value + packetCfg.startingSpot = 0; + + //Clear all of packet payload + memset(payloadCfg, 0, packetCfgPayloadSize); + + payloadCfg[0] = 0; //Message Version - set to 0 + payloadCfg[1] = layer; //By default we ask for the BBR layer + + //Load key into outgoing payload + payloadCfg[4] = key >> 8 * 0; //Key LSB + payloadCfg[5] = key >> 8 * 1; + payloadCfg[6] = key >> 8 * 2; + payloadCfg[7] = key >> 8 * 3; + + //Load user's value + payloadCfg[8] = value; //Value + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset32(uint32_t key, uint32_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + payloadCfg[packetCfg.len + 6] = value >> 8 * 2; + payloadCfg[packetCfg.len + 7] = value >> 8 * 3; + + //Update packet length: 4 byte key ID, 4 bytes of value + packetCfg.len = packetCfg.len + 4 + 4; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset16(uint32_t key, uint16_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value >> 8 * 0; //Value LSB + payloadCfg[packetCfg.len + 5] = value >> 8 * 1; + + //Update packet length: 4 byte key ID, 2 bytes of value + packetCfg.len = packetCfg.len + 4 + 2; + + //All done + return (true); +} + +//Add another keyID and value to an existing UBX-CFG-VALSET ubxPacket +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::addCfgValset8(uint32_t key, uint8_t value) +{ + //Load key into outgoing payload + payloadCfg[packetCfg.len + 0] = key >> 8 * 0; //Key LSB + payloadCfg[packetCfg.len + 1] = key >> 8 * 1; + payloadCfg[packetCfg.len + 2] = key >> 8 * 2; + payloadCfg[packetCfg.len + 3] = key >> 8 * 3; + + //Load user's value + payloadCfg[packetCfg.len + 4] = value; //Value + + //Update packet length: 4 byte key ID, 1 byte value + packetCfg.len = packetCfg.len + 4 + 1; + + //All done + return (true); +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 32-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset32(uint32_t key, uint32_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset32(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 16-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset16(uint32_t key, uint16_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset16(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Add a final keyID and value to an existing UBX-CFG-VALSET ubxPacket and send it +//This function takes a full 32-bit key and 8-bit value +uint8_t SFE_UBLOX_GPS::sendCfgValset8(uint32_t key, uint8_t value, uint16_t maxWait) +{ + //Load keyID and value into outgoing payload + addCfgValset8(key, value); + + //Send VALSET command with this key and value + return (sendCommand(&packetCfg, maxWait) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//=-=-=-=-=-=-=-= "Automatic" Messages =-=-=-=-=-=-=-==-=-=-=-=-=-=-= +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + +// ***** NAV POSECEF automatic support + +boolean SFE_UBLOX_GPS::getNAVPOSECEF(uint16_t maxWait) +{ + if (packetUBXNAVPOSECEF == NULL) initPacketUBXNAVPOSECEF(); //Check that RAM has been allocated for the POSECEF data + if (packetUBXNAVPOSECEF == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVPOSECEF->automaticFlags.flags.bits.automatic && packetUBXNAVPOSECEF->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_POSECEF); + return packetUBXNAVPOSECEF->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVPOSECEF->automaticFlags.flags.bits.automatic && !packetUBXNAVPOSECEF->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_NAV; + packetCfg.id = UBX_NAV_POSECEF; + 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 navigation message generation by the GNSS. This changes the way getPOSECEF +//works. +boolean SFE_UBLOX_GPS::setAutoNAVPOSECEF(boolean enable, uint16_t maxWait) +{ + return setAutoNAVPOSECEF(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getPOSECEF +//works. +boolean SFE_UBLOX_GPS::setAutoNAVPOSECEF(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVPOSECEF == NULL) initPacketUBXNAVPOSECEF(); //Check that RAM has been allocated for the data + if (packetUBXNAVPOSECEF == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_POSECEF; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVPOSECEF->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVPOSECEF->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVPOSECEF->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoNAVPOSECEFcallback(void (*callbackPointer)(UBX_NAV_POSECEF_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoNAVPOSECEF(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVPOSECEF->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVPOSECEF->callbackData = new UBX_NAV_POSECEF_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVPOSECEF->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoNAVPOSECEFcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVPOSECEF->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and POSECEF is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoNAVPOSECEF(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVPOSECEF == NULL) initPacketUBXNAVPOSECEF(); //Check that RAM has been allocated for the data + if (packetUBXNAVPOSECEF == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXNAVPOSECEF->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVPOSECEF->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVPOSECEF->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVPOSECEF->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVPOSECEF and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVPOSECEF() +{ + packetUBXNAVPOSECEF = new UBX_NAV_POSECEF_t; //Allocate RAM for the main struct + if (packetUBXNAVPOSECEF == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVPOSECEF: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVPOSECEF->automaticFlags.flags.all = 0; + packetUBXNAVPOSECEF->callbackPointer = NULL; + packetUBXNAVPOSECEF->callbackData = NULL; + packetUBXNAVPOSECEF->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale. This is handy to get data alignment after CRC failure +//or if there are no helper functions and the user wants to request fresh data +void SFE_UBLOX_GPS::flushNAVPOSECEF() +{ + if (packetUBXNAVPOSECEF == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVPOSECEF->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVPOSECEF(boolean enabled) +{ + if (packetUBXNAVPOSECEF == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVPOSECEF->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** NAV STATUS automatic support + +boolean SFE_UBLOX_GPS::getNAVSTATUS(uint16_t maxWait) +{ + if (packetUBXNAVSTATUS == NULL) initPacketUBXNAVSTATUS(); //Check that RAM has been allocated for the STATUS data + if (packetUBXNAVSTATUS == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVSTATUS->automaticFlags.flags.bits.automatic && packetUBXNAVSTATUS->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_STATUS); + return packetUBXNAVSTATUS->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVSTATUS->automaticFlags.flags.bits.automatic && !packetUBXNAVSTATUS->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_NAV; + packetCfg.id = UBX_NAV_STATUS; + 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 navigation message generation by the GNSS. This changes the way getSTATUS +//works. +boolean SFE_UBLOX_GPS::setAutoNAVSTATUS(boolean enable, uint16_t maxWait) +{ + return setAutoNAVSTATUS(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getSTATUS +//works. +boolean SFE_UBLOX_GPS::setAutoNAVSTATUS(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVSTATUS == NULL) initPacketUBXNAVSTATUS(); //Check that RAM has been allocated for the data + if (packetUBXNAVSTATUS == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_STATUS; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVSTATUS->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVSTATUS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVSTATUS->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoNAVSTATUScallback(void (*callbackPointer)(UBX_NAV_STATUS_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoNAVSTATUS(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVSTATUS->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVSTATUS->callbackData = new UBX_NAV_STATUS_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVSTATUS->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoNAVSTATUScallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVSTATUS->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and STATUS is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoNAVSTATUS(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVSTATUS == NULL) initPacketUBXNAVSTATUS(); //Check that RAM has been allocated for the data + if (packetUBXNAVSTATUS == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXNAVSTATUS->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVSTATUS->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVSTATUS->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVSTATUS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVSTATUS and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVSTATUS() +{ + packetUBXNAVSTATUS = new UBX_NAV_STATUS_t; //Allocate RAM for the main struct + if (packetUBXNAVSTATUS == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVSTATUS: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVSTATUS->automaticFlags.flags.all = 0; + packetUBXNAVSTATUS->callbackPointer = NULL; + packetUBXNAVSTATUS->callbackData = NULL; + packetUBXNAVSTATUS->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale. This is handy to get data alignment after CRC failure +//or if there are no helper functions and the user wants to request fresh data +void SFE_UBLOX_GPS::flushNAVSTATUS() +{ + if (packetUBXNAVSTATUS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVSTATUS->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVSTATUS(boolean enabled) +{ + if (packetUBXNAVSTATUS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVSTATUS->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** DOP automatic support + +boolean SFE_UBLOX_GPS::getDOP(uint16_t maxWait) +{ + if (packetUBXNAVDOP == NULL) initPacketUBXNAVDOP(); //Check that RAM has been allocated for the DOP data + if (packetUBXNAVDOP == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVDOP->automaticFlags.flags.bits.automatic && packetUBXNAVDOP->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getDOP: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_DOP); + return packetUBXNAVDOP->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVDOP->automaticFlags.flags.bits.automatic && !packetUBXNAVDOP->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getDOP: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getDOP: Polling")); + // } + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_DOP; + 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) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getDOP: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getDOP retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getDOP +//works. +boolean SFE_UBLOX_GPS::setAutoDOP(boolean enable, uint16_t maxWait) +{ + return setAutoDOP(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getDOP +//works. +boolean SFE_UBLOX_GPS::setAutoDOP(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVDOP == NULL) initPacketUBXNAVDOP(); //Check that RAM has been allocated for the data + if (packetUBXNAVDOP == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_DOP; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVDOP->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVDOP->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoDOPcallback(void (*callbackPointer)(UBX_NAV_DOP_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoDOP(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVDOP->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVDOP->callbackData = new UBX_NAV_DOP_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVDOP->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoDOPcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVDOP->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and DOP is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoDOP(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVDOP == NULL) initPacketUBXNAVDOP(); //Check that RAM has been allocated for the data + if (packetUBXNAVDOP == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXNAVDOP->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVDOP->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVDOP->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVDOP->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVDOP and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVDOP() +{ + packetUBXNAVDOP = new UBX_NAV_DOP_t; //Allocate RAM for the main struct + if (packetUBXNAVDOP == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVDOP: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVDOP->automaticFlags.flags.all = 0; + packetUBXNAVDOP->callbackPointer = NULL; + packetUBXNAVDOP->callbackData = NULL; + packetUBXNAVDOP->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the DOP data as read/stale. This is handy to get data alignment after CRC failure +void SFE_UBLOX_GPS::flushDOP() +{ + if (packetUBXNAVDOP == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVDOP->moduleQueried.moduleQueried.all = 0; //Mark all DOPs as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVDOP(boolean enabled) +{ + if (packetUBXNAVDOP == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVDOP->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** VEH ATT automatic support + +boolean SFE_UBLOX_GPS::getVehAtt(uint16_t maxWait) +{ + return (getNAVATT(maxWait)); +} + +boolean SFE_UBLOX_GPS::getNAVATT(uint16_t maxWait) +{ + if (packetUBXNAVATT == NULL) initPacketUBXNAVATT(); //Check that RAM has been allocated for the ESF RAW data + if (packetUBXNAVATT == NULL) //Only attempt this if RAM allocation was successful + return false; + + if (packetUBXNAVATT->automaticFlags.flags.bits.automatic && packetUBXNAVATT->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_ATT); + return packetUBXNAVATT->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVATT->automaticFlags.flags.bits.automatic && !packetUBXNAVATT->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + return (false); + } + else + { + //The GPS is not automatically reporting HNR PVT so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_ATT; + 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); + } + + return (false); // Trap. We should never get here... +} + +//Enable or disable automatic NAV ATT message generation by the GNSS. This changes the way getVehAtt +//works. +boolean SFE_UBLOX_GPS::setAutoNAVATT(boolean enable, uint16_t maxWait) +{ + return setAutoNAVATT(enable, true, maxWait); +} + +//Enable or disable automatic NAV ATT attitude message generation by the GNSS. This changes the way getVehAtt +//works. +boolean SFE_UBLOX_GPS::setAutoNAVATT(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVATT == NULL) initPacketUBXNAVATT(); //Check that RAM has been allocated for the data + if (packetUBXNAVATT == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_ATT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVATT->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVATT->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVATT->moduleQueried.moduleQueried.bits.all = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoNAVATTcallback(void (*callbackPointer)(UBX_NAV_ATT_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoNAVATT(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVATT->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVATT->callbackData = new UBX_NAV_ATT_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVATT->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoNAVATTcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVATT->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and NAV ATT attitude is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoNAVATT(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVATT == NULL) initPacketUBXNAVATT(); //Check that RAM has been allocated for the ESF RAW data + if (packetUBXNAVATT == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXNAVATT->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVATT->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVATT->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVATT->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVATT and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVATT() +{ + packetUBXNAVATT = new UBX_NAV_ATT_t; //Allocate RAM for the main struct + if (packetUBXNAVATT == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVATT: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVATT->automaticFlags.flags.all = 0; + packetUBXNAVATT->callbackPointer = NULL; + packetUBXNAVATT->callbackData = NULL; + packetUBXNAVATT->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the DOP data as read/stale. This is handy to get data alignment after CRC failure +void SFE_UBLOX_GPS::flushNAVATT() +{ + if (packetUBXNAVATT == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVATT->moduleQueried.moduleQueried.all = 0; //Mark all DOPs as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVATT(boolean enabled) +{ + if (packetUBXNAVATT == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVATT->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** PVT automatic support + +//Get the latest Position/Velocity/Time solution and fill all global variables +boolean SFE_UBLOX_GPS::getPVT(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVPVT->automaticFlags.flags.bits.automatic && packetUBXNAVPVT->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getPVT: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_PVT); + return packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all; + } + else if (packetUBXNAVPVT->automaticFlags.flags.bits.automatic && !packetUBXNAVPVT->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getPVT: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(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; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + //packetCfg.startingSpot = 20; //Begin listening at spot 20 so we can record up to 20+packetCfgPayloadSize = 84 bytes Note:now hard-coded in processUBX + + //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) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getPVT: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getPVT retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getPVT +//works. +boolean SFE_UBLOX_GPS::setAutoPVT(boolean enable, uint16_t maxWait) +{ + return setAutoPVT(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getPVT +//works. +boolean SFE_UBLOX_GPS::setAutoPVT(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_PVT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVPVT->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVPVT->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. This changes the way getPVT works. +boolean SFE_UBLOX_GPS::setAutoPVTcallback(void (*callbackPointer)(UBX_NAV_PVT_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoPVT(true, false, maxWait); + if (!result) + return (result); // Bail if setAutoPVT failed + + if (packetUBXNAVPVT->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVPVT->callbackData = new UBX_NAV_PVT_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVPVT->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoPVTcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVPVT->callbackPointer = callbackPointer; // RAM has been allocated so now update the pointer + + return (true); +} + +//In case no config access to the GNSS is possible and PVT is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoPVT(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXNAVPVT->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVPVT->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVPVT->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVPVT->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVPVT and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVPVT() +{ + packetUBXNAVPVT = new UBX_NAV_PVT_t; //Allocate RAM for the main struct + if (packetUBXNAVPVT == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVPVT: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVPVT->automaticFlags.flags.all = 0; + packetUBXNAVPVT->callbackPointer = NULL; + packetUBXNAVPVT->callbackData = NULL; + packetUBXNAVPVT->moduleQueried.moduleQueried1.all = 0; + packetUBXNAVPVT->moduleQueried.moduleQueried2.all = 0; + return (true); +} + +//Mark all the PVT data as read/stale. This is handy to get data alignment after CRC failure +void SFE_UBLOX_GPS::flushPVT() +{ + if (packetUBXNAVPVT == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVPVT->moduleQueried.moduleQueried1.all = 0; //Mark all datums as stale (read before) + packetUBXNAVPVT->moduleQueried.moduleQueried2.all = 0; +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVPVT(boolean enabled) +{ + if (packetUBXNAVPVT == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVPVT->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** NAV ODO automatic support + +boolean SFE_UBLOX_GPS::getNAVODO(uint16_t maxWait) +{ + if (packetUBXNAVODO == NULL) initPacketUBXNAVODO(); //Check that RAM has been allocated for the ODO data + if (packetUBXNAVODO == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVODO->automaticFlags.flags.bits.automatic && packetUBXNAVODO->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_ODO); + return packetUBXNAVODO->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVODO->automaticFlags.flags.bits.automatic && !packetUBXNAVODO->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_NAV; + packetCfg.id = UBX_NAV_ODO; + 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 navigation message generation by the GNSS. This changes the way getODO +//works. +boolean SFE_UBLOX_GPS::setAutoNAVODO(boolean enable, uint16_t maxWait) +{ + return setAutoNAVODO(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getODO +//works. +boolean SFE_UBLOX_GPS::setAutoNAVODO(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVODO == NULL) initPacketUBXNAVODO(); //Check that RAM has been allocated for the data + if (packetUBXNAVODO == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_ODO; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVODO->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVODO->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVODO->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoNAVODOcallback(void (*callbackPointer)(UBX_NAV_ODO_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoNAVODO(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVODO->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVODO->callbackData = new UBX_NAV_ODO_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVODO->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoNAVODOcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVODO->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and ODO is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoNAVODO(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVODO == NULL) initPacketUBXNAVODO(); //Check that RAM has been allocated for the data + if (packetUBXNAVODO == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXNAVODO->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVODO->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVODO->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVODO->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVODO and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVODO() +{ + packetUBXNAVODO = new UBX_NAV_ODO_t; //Allocate RAM for the main struct + if (packetUBXNAVODO == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVODO: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVODO->automaticFlags.flags.all = 0; + packetUBXNAVODO->callbackPointer = NULL; + packetUBXNAVODO->callbackData = NULL; + packetUBXNAVODO->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushNAVODO() +{ + if (packetUBXNAVODO == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVODO->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVODO(boolean enabled) +{ + if (packetUBXNAVODO == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVODO->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** NAV VELECEF automatic support + +boolean SFE_UBLOX_GPS::getNAVVELECEF(uint16_t maxWait) +{ + if (packetUBXNAVVELECEF == NULL) initPacketUBXNAVVELECEF(); //Check that RAM has been allocated for the VELECEF data + if (packetUBXNAVVELECEF == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVVELECEF->automaticFlags.flags.bits.automatic && packetUBXNAVVELECEF->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_VELECEF); + return packetUBXNAVVELECEF->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVVELECEF->automaticFlags.flags.bits.automatic && !packetUBXNAVVELECEF->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_NAV; + packetCfg.id = UBX_NAV_VELECEF; + 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 navigation message generation by the GNSS. This changes the way getVELECEF +//works. +boolean SFE_UBLOX_GPS::setAutoNAVVELECEF(boolean enable, uint16_t maxWait) +{ + return setAutoNAVVELECEF(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getVELECEF +//works. +boolean SFE_UBLOX_GPS::setAutoNAVVELECEF(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVVELECEF == NULL) initPacketUBXNAVVELECEF(); //Check that RAM has been allocated for the data + if (packetUBXNAVVELECEF == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_VELECEF; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVVELECEF->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVVELECEF->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVVELECEF->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoNAVVELECEFcallback(void (*callbackPointer)(UBX_NAV_VELECEF_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoNAVVELECEF(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVVELECEF->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVVELECEF->callbackData = new UBX_NAV_VELECEF_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVVELECEF->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoNAVVELECEFcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVVELECEF->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and VELECEF is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoNAVVELECEF(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVVELECEF == NULL) initPacketUBXNAVVELECEF(); //Check that RAM has been allocated for the data + if (packetUBXNAVVELECEF == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXNAVVELECEF->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVVELECEF->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVVELECEF->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVVELECEF->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVVELECEF and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVVELECEF() +{ + packetUBXNAVVELECEF = new UBX_NAV_VELECEF_t; //Allocate RAM for the main struct + if (packetUBXNAVVELECEF == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVVELECEF: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVVELECEF->automaticFlags.flags.all = 0; + packetUBXNAVVELECEF->callbackPointer = NULL; + packetUBXNAVVELECEF->callbackData = NULL; + packetUBXNAVVELECEF->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushNAVVELECEF() +{ + if (packetUBXNAVVELECEF == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVVELECEF->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVVELECEF(boolean enabled) +{ + if (packetUBXNAVVELECEF == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVVELECEF->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** NAV VELNED automatic support + +boolean SFE_UBLOX_GPS::getNAVVELNED(uint16_t maxWait) +{ + if (packetUBXNAVVELNED == NULL) initPacketUBXNAVVELNED(); //Check that RAM has been allocated for the VELNED data + if (packetUBXNAVVELNED == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVVELNED->automaticFlags.flags.bits.automatic && packetUBXNAVVELNED->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_VELNED); + return packetUBXNAVVELNED->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVVELNED->automaticFlags.flags.bits.automatic && !packetUBXNAVVELNED->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_NAV; + packetCfg.id = UBX_NAV_VELNED; + 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 navigation message generation by the GNSS. This changes the way getVELNED +//works. +boolean SFE_UBLOX_GPS::setAutoNAVVELNED(boolean enable, uint16_t maxWait) +{ + return setAutoNAVVELNED(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getVELNED +//works. +boolean SFE_UBLOX_GPS::setAutoNAVVELNED(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVVELNED == NULL) initPacketUBXNAVVELNED(); //Check that RAM has been allocated for the data + if (packetUBXNAVVELNED == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_VELNED; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVVELNED->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVVELNED->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVVELNED->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoNAVVELNEDcallback(void (*callbackPointer)(UBX_NAV_VELNED_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoNAVVELNED(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVVELNED->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVVELNED->callbackData = new UBX_NAV_VELNED_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVVELNED->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoNAVVELNEDcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVVELNED->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and VELNED is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoNAVVELNED(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVVELNED == NULL) initPacketUBXNAVVELNED(); //Check that RAM has been allocated for the data + if (packetUBXNAVVELNED == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXNAVVELNED->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVVELNED->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVVELNED->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVVELNED->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVVELNED and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVVELNED() +{ + packetUBXNAVVELNED = new UBX_NAV_VELNED_t; //Allocate RAM for the main struct + if (packetUBXNAVVELNED == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVVELNED: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVVELNED->automaticFlags.flags.all = 0; + packetUBXNAVVELNED->callbackPointer = NULL; + packetUBXNAVVELNED->callbackData = NULL; + packetUBXNAVVELNED->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushNAVVELNED() +{ + if (packetUBXNAVVELNED == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere! + packetUBXNAVVELNED->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVVELNED(boolean enabled) +{ + if (packetUBXNAVVELNED == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVVELNED->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** NAV HPPOSECEF automatic support + +boolean SFE_UBLOX_GPS::getNAVHPPOSECEF(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSECEF == NULL) initPacketUBXNAVHPPOSECEF(); //Check that RAM has been allocated for the HPPOSECEF data + if (packetUBXNAVHPPOSECEF == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.automatic && packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_HPPOSECEF); + return packetUBXNAVHPPOSECEF->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.automatic && !packetUBXNAVHPPOSECEF->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_NAV; + packetCfg.id = UBX_NAV_HPPOSECEF; + 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 navigation message generation by the GNSS. This changes the way getHPPOSECEF +//works. +boolean SFE_UBLOX_GPS::setAutoNAVHPPOSECEF(boolean enable, uint16_t maxWait) +{ + return setAutoNAVHPPOSECEF(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getHPPOSECEF +//works. +boolean SFE_UBLOX_GPS::setAutoNAVHPPOSECEF(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVHPPOSECEF == NULL) initPacketUBXNAVHPPOSECEF(); //Check that RAM has been allocated for the data + if (packetUBXNAVHPPOSECEF == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_HPPOSECEF; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVHPPOSECEF->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoNAVHPPOSECEFcallback(void (*callbackPointer)(UBX_NAV_HPPOSECEF_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoNAVHPPOSECEF(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVHPPOSECEF->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVHPPOSECEF->callbackData = new UBX_NAV_HPPOSECEF_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVHPPOSECEF->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoNAVHPPOSECEFcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVHPPOSECEF->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and HPPOSECEF is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoNAVHPPOSECEF(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVHPPOSECEF == NULL) initPacketUBXNAVHPPOSECEF(); //Check that RAM has been allocated for the data + if (packetUBXNAVHPPOSECEF == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVHPPOSECEF and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVHPPOSECEF() +{ + packetUBXNAVHPPOSECEF = new UBX_NAV_HPPOSECEF_t; //Allocate RAM for the main struct + if (packetUBXNAVHPPOSECEF == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVHPPOSECEF: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVHPPOSECEF->automaticFlags.flags.all = 0; + packetUBXNAVHPPOSECEF->callbackPointer = NULL; + packetUBXNAVHPPOSECEF->callbackData = NULL; + packetUBXNAVHPPOSECEF->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushNAVHPPOSECEF() +{ + if (packetUBXNAVHPPOSECEF == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVHPPOSECEF->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVHPPOSECEF(boolean enabled) +{ + if (packetUBXNAVHPPOSECEF == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVHPPOSECEF->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** NAV HPPOSLLH automatic support + +boolean SFE_UBLOX_GPS::getHPPOSLLH(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.automatic && packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHPPOSLLH: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_HPPOSLLH); + return packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.automatic && !packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHPPOSLLH: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHPPOSLLH: Polling")); + // } + + //The GPS is not automatically reporting navigation position so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_HPPOSLLH; + 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) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHPPOSLLH: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getHPPOSLLH retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getHPPOSLLH +//works. +boolean SFE_UBLOX_GPS::setAutoHPPOSLLH(boolean enable, uint16_t maxWait) +{ + return setAutoHPPOSLLH(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getHPPOSLLH +//works. +boolean SFE_UBLOX_GPS::setAutoHPPOSLLH(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the data + if (packetUBXNAVHPPOSLLH == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_HPPOSLLH; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoHPPOSLLHcallback(void (*callbackPointer)(UBX_NAV_HPPOSLLH_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoHPPOSLLH(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVHPPOSLLH->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVHPPOSLLH->callbackData = new UBX_NAV_HPPOSLLH_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVHPPOSLLH->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoHPPOSLLHcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVHPPOSLLH->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and HPPOSLLH is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoHPPOSLLH(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the data + if (packetUBXNAVHPPOSLLH == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVHPPOSLLH and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVHPPOSLLH() +{ + packetUBXNAVHPPOSLLH = new UBX_NAV_HPPOSLLH_t; //Allocate RAM for the main struct + if (packetUBXNAVHPPOSLLH == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVHPPOSLLH: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVHPPOSLLH->automaticFlags.flags.all = 0; + packetUBXNAVHPPOSLLH->callbackPointer = NULL; + packetUBXNAVHPPOSLLH->callbackData = NULL; + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the HPPOSLLH data as read/stale. This is handy to get data alignment after CRC failure +void SFE_UBLOX_GPS::flushHPPOSLLH() +{ + if (packetUBXNAVHPPOSLLH == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere! + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVHPPOSLLH(boolean enabled) +{ + if (packetUBXNAVHPPOSLLH == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVHPPOSLLH->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** NAV CLOCK automatic support + +boolean SFE_UBLOX_GPS::getNAVCLOCK(uint16_t maxWait) +{ + if (packetUBXNAVCLOCK == NULL) initPacketUBXNAVCLOCK(); //Check that RAM has been allocated for the CLOCK data + if (packetUBXNAVCLOCK == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVCLOCK->automaticFlags.flags.bits.automatic && packetUBXNAVCLOCK->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_CLOCK); + return packetUBXNAVCLOCK->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVCLOCK->automaticFlags.flags.bits.automatic && !packetUBXNAVCLOCK->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + return (false); + } + else + { + //The GPS is not automatically reporting CLOCK so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_CLOCK; + 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 CLOCK message generation by the GNSS. This changes the way getNAVCLOCK +//works. +boolean SFE_UBLOX_GPS::setAutoNAVCLOCK(boolean enable, uint16_t maxWait) +{ + return setAutoNAVCLOCK(enable, true, maxWait); +} + +//Enable or disable automatic CLOCK attitude message generation by the GNSS. This changes the way getNAVCLOCK +//works. +boolean SFE_UBLOX_GPS::setAutoNAVCLOCK(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVCLOCK == NULL) initPacketUBXNAVCLOCK(); //Check that RAM has been allocated for the data + if (packetUBXNAVCLOCK == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_CLOCK; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVCLOCK->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVCLOCK->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVCLOCK->moduleQueried.moduleQueried.bits.all = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoNAVCLOCKcallback(void (*callbackPointer)(UBX_NAV_CLOCK_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoNAVCLOCK(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVCLOCK->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVCLOCK->callbackData = new UBX_NAV_CLOCK_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVCLOCK->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoNAVCLOCKcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVCLOCK->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and HNR attitude is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoNAVCLOCK(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVCLOCK == NULL) initPacketUBXNAVCLOCK(); //Check that RAM has been allocated for the CLOCK data + if (packetUBXNAVCLOCK == NULL) //Bail if the RAM allocation failed + return (false); + + boolean changes = packetUBXNAVCLOCK->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVCLOCK->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVCLOCK->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVCLOCK->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVCLOCK and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVCLOCK() +{ + packetUBXNAVCLOCK = new UBX_NAV_CLOCK_t ; //Allocate RAM for the main struct + if (packetUBXNAVCLOCK == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVCLOCK: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVCLOCK->automaticFlags.flags.all = 0; + packetUBXNAVCLOCK->callbackPointer = NULL; + packetUBXNAVCLOCK->callbackData = NULL; + packetUBXNAVCLOCK->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushNAVCLOCK() +{ + if (packetUBXNAVCLOCK == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVCLOCK->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVCLOCK(boolean enabled) +{ + if (packetUBXNAVCLOCK == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVCLOCK->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** NAV SVIN automatic support + +//Reads survey in status and sets the global variables +//for status, position valid, observation time, and mean 3D StdDev +//Returns true if commands was successful +boolean SFE_UBLOX_GPS::getSurveyStatus(uint16_t maxWait) +{ + if (packetUBXNAVSVIN == NULL) initPacketUBXNAVSVIN(); //Check that RAM has been allocated for the SVIN data + if (packetUBXNAVSVIN == NULL) // Abort if the RAM allocation failed + return (false); + + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_SVIN; + 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); +} + +// PRIVATE: Allocate RAM for packetUBXNAVSVIN and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVSVIN() +{ + packetUBXNAVSVIN = new UBX_NAV_SVIN_t; //Allocate RAM for the main struct + if (packetUBXNAVSVIN == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVSVIN: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVSVIN->automaticFlags.flags.all = 0; + packetUBXNAVSVIN->callbackPointer = NULL; + packetUBXNAVSVIN->callbackData = NULL; + packetUBXNAVSVIN->moduleQueried.moduleQueried.all = 0; + return (true); +} + +// ***** NAV RELPOSNED automatic support + +//Relative Positioning Information in NED frame +//Returns true if commands was successful +//Note: +// RELPOSNED on the M8 is only 40 bytes long +// RELPOSNED on the F9 is 64 bytes long and contains much more information +boolean SFE_UBLOX_GPS::getRELPOSNED(uint16_t maxWait) +{ + if (packetUBXNAVRELPOSNED == NULL) initPacketUBXNAVRELPOSNED(); //Check that RAM has been allocated for the RELPOSNED data + if (packetUBXNAVRELPOSNED == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVRELPOSNED->automaticFlags.flags.bits.automatic && packetUBXNAVRELPOSNED->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_NAV, UBX_NAV_RELPOSNED); + return packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXNAVRELPOSNED->automaticFlags.flags.bits.automatic && !packetUBXNAVRELPOSNED->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + return (false); + } + else + { + //The GPS is not automatically reporting RELPOSNED so we have to poll explicitly + packetCfg.cls = UBX_CLASS_NAV; + packetCfg.id = UBX_NAV_RELPOSNED; + 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 RELPOSNED message generation by the GNSS. This changes the way getRELPOSNED +//works. +boolean SFE_UBLOX_GPS::setAutoRELPOSNED(boolean enable, uint16_t maxWait) +{ + return setAutoRELPOSNED(enable, true, maxWait); +} + +//Enable or disable automatic HNR attitude message generation by the GNSS. This changes the way getRELPOSNED +//works. +boolean SFE_UBLOX_GPS::setAutoRELPOSNED(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXNAVRELPOSNED == NULL) initPacketUBXNAVRELPOSNED(); //Check that RAM has been allocated for the data + if (packetUBXNAVRELPOSNED == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_NAV; + payloadCfg[1] = UBX_NAV_RELPOSNED; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXNAVRELPOSNED->automaticFlags.flags.bits.automatic = enable; + packetUBXNAVRELPOSNED->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.all = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoRELPOSNEDcallback(void (*callbackPointer)(UBX_NAV_RELPOSNED_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoRELPOSNED(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXNAVRELPOSNED->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXNAVRELPOSNED->callbackData = new UBX_NAV_RELPOSNED_data_t; //Allocate RAM for the main struct + } + + if (packetUBXNAVRELPOSNED->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoRELPOSNEDcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXNAVRELPOSNED->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and HNR attitude is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoRELPOSNED(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXNAVRELPOSNED == NULL) initPacketUBXNAVRELPOSNED(); //Check that RAM has been allocated for the RELPOSNED data + if (packetUBXNAVRELPOSNED == NULL) //Bail if the RAM allocation failed + return (false); + + boolean changes = packetUBXNAVRELPOSNED->automaticFlags.flags.bits.automatic != enabled || packetUBXNAVRELPOSNED->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXNAVRELPOSNED->automaticFlags.flags.bits.automatic = enabled; + packetUBXNAVRELPOSNED->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXNAVRELPOSNED and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXNAVRELPOSNED() +{ + packetUBXNAVRELPOSNED = new UBX_NAV_RELPOSNED_t ; //Allocate RAM for the main struct + if (packetUBXNAVRELPOSNED == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXNAVRELPOSNED: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXNAVRELPOSNED->automaticFlags.flags.all = 0; + packetUBXNAVRELPOSNED->callbackPointer = NULL; + packetUBXNAVRELPOSNED->callbackData = NULL; + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushNAVRELPOSNED() +{ + if (packetUBXNAVRELPOSNED == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logNAVRELPOSNED(boolean enabled) +{ + if (packetUBXNAVRELPOSNED == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXNAVRELPOSNED->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** RXM SFRBX automatic support + +boolean SFE_UBLOX_GPS::getRXMSFRBX(uint16_t maxWait) +{ + if (packetUBXRXMSFRBX == NULL) initPacketUBXRXMSFRBX(); //Check that RAM has been allocated for the TM2 data + if (packetUBXRXMSFRBX == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXRXMSFRBX->automaticFlags.flags.bits.automatic && packetUBXRXMSFRBX->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_TIM, UBX_TIM_TM2); + return packetUBXRXMSFRBX->moduleQueried; + } + else if (packetUBXRXMSFRBX->automaticFlags.flags.bits.automatic && !packetUBXRXMSFRBX->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_RXM; + packetCfg.id = UBX_RXM_SFRBX; + 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 navigation message generation by the GNSS. This changes the way getRXMSFRBX +//works. +boolean SFE_UBLOX_GPS::setAutoRXMSFRBX(boolean enable, uint16_t maxWait) +{ + return setAutoRXMSFRBX(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getRXMSFRBX +//works. +boolean SFE_UBLOX_GPS::setAutoRXMSFRBX(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXRXMSFRBX == NULL) initPacketUBXRXMSFRBX(); //Check that RAM has been allocated for the data + if (packetUBXRXMSFRBX == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_RXM; + payloadCfg[1] = UBX_RXM_SFRBX; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXRXMSFRBX->automaticFlags.flags.bits.automatic = enable; + packetUBXRXMSFRBX->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXRXMSFRBX->moduleQueried = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoRXMSFRBXcallback(void (*callbackPointer)(UBX_RXM_SFRBX_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoRXMSFRBX(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXRXMSFRBX->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXRXMSFRBX->callbackData = new UBX_RXM_SFRBX_data_t; //Allocate RAM for the main struct + } + + if (packetUBXRXMSFRBX->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoRXMSFRBXcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXRXMSFRBX->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and SFRBX is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoRXMSFRBX(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXRXMSFRBX == NULL) initPacketUBXRXMSFRBX(); //Check that RAM has been allocated for the data + if (packetUBXRXMSFRBX == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXRXMSFRBX->automaticFlags.flags.bits.automatic != enabled || packetUBXRXMSFRBX->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXRXMSFRBX->automaticFlags.flags.bits.automatic = enabled; + packetUBXRXMSFRBX->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXRXMSFRBX and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXRXMSFRBX() +{ + packetUBXRXMSFRBX = new UBX_RXM_SFRBX_t; //Allocate RAM for the main struct + if (packetUBXRXMSFRBX == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXRXMSFRBX: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXRXMSFRBX->automaticFlags.flags.all = 0; + packetUBXRXMSFRBX->callbackPointer = NULL; + packetUBXRXMSFRBX->callbackData = NULL; + packetUBXRXMSFRBX->moduleQueried = false; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushRXMSFRBX() +{ + if (packetUBXRXMSFRBX == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXRXMSFRBX->moduleQueried = false; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logRXMSFRBX(boolean enabled) +{ + if (packetUBXRXMSFRBX == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXRXMSFRBX->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** RXM RAWX automatic support + +boolean SFE_UBLOX_GPS::getRXMRAWX(uint16_t maxWait) +{ + if (packetUBXRXMRAWX == NULL) initPacketUBXRXMRAWX(); //Check that RAM has been allocated for the TM2 data + if (packetUBXRXMRAWX == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXRXMRAWX->automaticFlags.flags.bits.automatic && packetUBXRXMRAWX->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_TIM, UBX_TIM_TM2); + return packetUBXRXMRAWX->moduleQueried; + } + else if (packetUBXRXMRAWX->automaticFlags.flags.bits.automatic && !packetUBXRXMRAWX->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_RXM; + packetCfg.id = UBX_RXM_RAWX; + 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 navigation message generation by the GNSS. This changes the way getRXMRAWX +//works. +boolean SFE_UBLOX_GPS::setAutoRXMRAWX(boolean enable, uint16_t maxWait) +{ + return setAutoRXMRAWX(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getRXMRAWX +//works. +boolean SFE_UBLOX_GPS::setAutoRXMRAWX(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXRXMRAWX == NULL) initPacketUBXRXMRAWX(); //Check that RAM has been allocated for the data + if (packetUBXRXMRAWX == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_RXM; + payloadCfg[1] = UBX_RXM_RAWX; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXRXMRAWX->automaticFlags.flags.bits.automatic = enable; + packetUBXRXMRAWX->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXRXMRAWX->moduleQueried = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoRXMRAWXcallback(void (*callbackPointer)(UBX_RXM_RAWX_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoRXMRAWX(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXRXMRAWX->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXRXMRAWX->callbackData = new UBX_RXM_RAWX_data_t; //Allocate RAM for the main struct + } + + if (packetUBXRXMRAWX->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoRXMRAWXcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXRXMRAWX->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and VELNED is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoRXMRAWX(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXRXMRAWX == NULL) initPacketUBXRXMRAWX(); //Check that RAM has been allocated for the data + if (packetUBXRXMRAWX == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXRXMRAWX->automaticFlags.flags.bits.automatic != enabled || packetUBXRXMRAWX->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXRXMRAWX->automaticFlags.flags.bits.automatic = enabled; + packetUBXRXMRAWX->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXRXMRAWX and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXRXMRAWX() +{ + packetUBXRXMRAWX = new UBX_RXM_RAWX_t; //Allocate RAM for the main struct + if (packetUBXRXMRAWX == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXRXMRAWX: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXRXMRAWX->automaticFlags.flags.all = 0; + packetUBXRXMRAWX->callbackPointer = NULL; + packetUBXRXMRAWX->callbackData = NULL; + packetUBXRXMRAWX->moduleQueried = false; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushRXMRAWX() +{ + if (packetUBXRXMRAWX == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXRXMRAWX->moduleQueried = false; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logRXMRAWX(boolean enabled) +{ + if (packetUBXRXMRAWX == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXRXMRAWX->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** CFG automatic support + +//Get the latest CFG RATE - as used by isConnected +boolean SFE_UBLOX_GPS::getNavigationFrequencyInternal(uint16_t maxWait) +{ + if (packetUBXCFGRATE == NULL) initPacketUBXCFGRATE(); //Check that RAM has been allocated for the data + if (packetUBXCFGRATE == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXCFGRATE->automaticFlags.flags.bits.automatic && packetUBXCFGRATE->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_CFG, UBX_CFG_RATE); + return packetUBXCFGRATE->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXCFGRATE->automaticFlags.flags.bits.automatic && !packetUBXCFGRATE->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + return (false); + } + else + { + //The GPS is not automatically reporting navigation rate so we have to poll explicitly + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + 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); + } +} + +// PRIVATE: Allocate RAM for packetUBXCFGRATE and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXCFGRATE() +{ + packetUBXCFGRATE = new UBX_CFG_RATE_t; //Allocate RAM for the main struct + if (packetUBXCFGRATE == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXCFGRATE: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXCFGRATE->automaticFlags.flags.all = 0; + packetUBXCFGRATE->callbackPointer = NULL; + packetUBXCFGRATE->callbackData = NULL; + packetUBXCFGRATE->moduleQueried.moduleQueried.all = 0; + return (true); +} + +// ***** TIM TM2 automatic support + +boolean SFE_UBLOX_GPS::getTIMTM2(uint16_t maxWait) +{ + if (packetUBXTIMTM2 == NULL) initPacketUBXTIMTM2(); //Check that RAM has been allocated for the TM2 data + if (packetUBXTIMTM2 == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXTIMTM2->automaticFlags.flags.bits.automatic && packetUBXTIMTM2->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + checkUbloxInternal(&packetCfg, UBX_CLASS_TIM, UBX_TIM_TM2); + return packetUBXTIMTM2->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXTIMTM2->automaticFlags.flags.bits.automatic && !packetUBXTIMTM2->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_TM2; + 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 navigation message generation by the GNSS. This changes the way getTIMTM2 +//works. +boolean SFE_UBLOX_GPS::setAutoTIMTM2(boolean enable, uint16_t maxWait) +{ + return setAutoTIMTM2(enable, true, maxWait); +} + +//Enable or disable automatic navigation message generation by the GNSS. This changes the way getTIMTM2 +//works. +boolean SFE_UBLOX_GPS::setAutoTIMTM2(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXTIMTM2 == NULL) initPacketUBXTIMTM2(); //Check that RAM has been allocated for the data + if (packetUBXTIMTM2 == NULL) //Only attempt this if RAM allocation was successful + return false; + + 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_TM2; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXTIMTM2->automaticFlags.flags.bits.automatic = enable; + packetUBXTIMTM2->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXTIMTM2->moduleQueried.moduleQueried.bits.all = false; + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoTIMTM2callback(void (*callbackPointer)(UBX_TIM_TM2_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoTIMTM2(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXTIMTM2->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXTIMTM2->callbackData = new UBX_TIM_TM2_data_t; //Allocate RAM for the main struct + } + + if (packetUBXTIMTM2->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoTIMTM2callback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXTIMTM2->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and VELNED is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoTIMTM2(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXTIMTM2 == NULL) initPacketUBXTIMTM2(); //Check that RAM has been allocated for the data + if (packetUBXTIMTM2 == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXTIMTM2->automaticFlags.flags.bits.automatic != enabled || packetUBXTIMTM2->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXTIMTM2->automaticFlags.flags.bits.automatic = enabled; + packetUBXTIMTM2->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXTIMTM2 and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXTIMTM2() +{ + packetUBXTIMTM2 = new UBX_TIM_TM2_t; //Allocate RAM for the main struct + if (packetUBXTIMTM2 == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXTIMTM2: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXTIMTM2->automaticFlags.flags.all = 0; + packetUBXTIMTM2->callbackPointer = NULL; + packetUBXTIMTM2->callbackData = NULL; + packetUBXTIMTM2->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushTIMTM2() +{ + if (packetUBXTIMTM2 == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXTIMTM2->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logTIMTM2(boolean enabled) +{ + if (packetUBXTIMTM2 == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXTIMTM2->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** ESF ALG automatic support + +boolean SFE_UBLOX_GPS::getEsfAlignment(uint16_t maxWait) +{ + return (getESFALG(maxWait)); +} + +boolean SFE_UBLOX_GPS::getESFALG(uint16_t maxWait) +{ + if (packetUBXESFALG == NULL) initPacketUBXESFALG(); //Check that RAM has been allocated for the ESF alignment data + if (packetUBXESFALG == NULL) //Only attempt this if RAM allocation was successful + return false; + + if (packetUBXESFALG->automaticFlags.flags.bits.automatic && packetUBXESFALG->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfAlignment: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_ESF, UBX_ESF_ALG); + return packetUBXESFALG->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXESFALG->automaticFlags.flags.bits.automatic && !packetUBXESFALG->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfAlignment: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfAlignment: Polling")); + // } + + //The GPS is not automatically reporting HNR PVT so we have to poll explicitly + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_ALG; + 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) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfAlignment: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getEsfAlignment retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//Enable or disable automatic ESF ALG message generation by the GNSS. This changes the way getEsfAlignment +//works. +boolean SFE_UBLOX_GPS::setAutoESFALG(boolean enable, uint16_t maxWait) +{ + return setAutoESFALG(enable, true, maxWait); +} + +//Enable or disable automatic ESF ALG message generation by the GNSS. This changes the way getEsfAlignment +//works. +boolean SFE_UBLOX_GPS::setAutoESFALG(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXESFALG == NULL) initPacketUBXESFALG(); //Check that RAM has been allocated for the data + if (packetUBXESFALG == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_ESF; + payloadCfg[1] = UBX_ESF_ALG; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXESFALG->automaticFlags.flags.bits.automatic = enable; + packetUBXESFALG->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXESFALG->moduleQueried.moduleQueried.bits.all = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoESFALGcallback(void (*callbackPointer)(UBX_ESF_ALG_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoESFALG(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXESFALG->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXESFALG->callbackData = new UBX_ESF_ALG_data_t; //Allocate RAM for the main struct + } + + if (packetUBXESFALG->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoESFALGcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXESFALG->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and ESF ALG is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoESFALG(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXESFALG == NULL) initPacketUBXESFALG(); //Check that RAM has been allocated for the ESF alignment data + if (packetUBXESFALG == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXESFALG->automaticFlags.flags.bits.automatic != enabled || packetUBXESFALG->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXESFALG->automaticFlags.flags.bits.automatic = enabled; + packetUBXESFALG->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXESFALG and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXESFALG() +{ + packetUBXESFALG = new UBX_ESF_ALG_t; //Allocate RAM for the main struct + if (packetUBXESFALG == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXESFALG: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXESFALG->automaticFlags.flags.all = 0; + packetUBXESFALG->callbackPointer = NULL; + packetUBXESFALG->callbackData = NULL; + packetUBXESFALG->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushESFALG() +{ + if (packetUBXESFALG == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXESFALG->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logESFALG(boolean enabled) +{ + if (packetUBXESFALG == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXESFALG->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** ESF STATUS automatic support + +boolean SFE_UBLOX_GPS::getEsfInfo(uint16_t maxWait) +{ + return (getESFSTATUS(maxWait)); +} + +boolean SFE_UBLOX_GPS::getESFSTATUS(uint16_t maxWait) +{ + if (packetUBXESFSTATUS == NULL) initPacketUBXESFSTATUS(); //Check that RAM has been allocated for the ESF status data + if (packetUBXESFSTATUS == NULL) //Only attempt this if RAM allocation was successful + return false; + + if (packetUBXESFSTATUS->automaticFlags.flags.bits.automatic && packetUBXESFSTATUS->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfInfo: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_ESF, UBX_ESF_STATUS); + return packetUBXESFSTATUS->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXESFSTATUS->automaticFlags.flags.bits.automatic && !packetUBXESFSTATUS->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfInfo: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfInfo: Polling")); + // } + + //The GPS is not automatically reporting HNR PVT so we have to poll explicitly + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_STATUS; + 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) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfInfo: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getEsfInfo retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//Enable or disable automatic ESF STATUS message generation by the GNSS. This changes the way getESFInfo +//works. +boolean SFE_UBLOX_GPS::setAutoESFSTATUS(boolean enable, uint16_t maxWait) +{ + return setAutoESFSTATUS(enable, true, maxWait); +} + +//Enable or disable automatic ESF STATUS message generation by the GNSS. This changes the way getESFInfo +//works. +boolean SFE_UBLOX_GPS::setAutoESFSTATUS(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXESFSTATUS == NULL) initPacketUBXESFSTATUS(); //Check that RAM has been allocated for the data + if (packetUBXESFSTATUS == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_ESF; + payloadCfg[1] = UBX_ESF_STATUS; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXESFSTATUS->automaticFlags.flags.bits.automatic = enable; + packetUBXESFSTATUS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXESFSTATUS->moduleQueried.moduleQueried.bits.all = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoESFSTATUScallback(void (*callbackPointer)(UBX_ESF_STATUS_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoESFSTATUS(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXESFSTATUS->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXESFSTATUS->callbackData = new UBX_ESF_STATUS_data_t; //Allocate RAM for the main struct + } + + if (packetUBXESFSTATUS->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoESFSTATUScallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXESFSTATUS->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and ESF STATUS is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoESFSTATUS(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXESFSTATUS == NULL) initPacketUBXESFSTATUS(); //Check that RAM has been allocated for the ESF status data + if (packetUBXESFSTATUS == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXESFSTATUS->automaticFlags.flags.bits.automatic != enabled || packetUBXESFSTATUS->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXESFSTATUS->automaticFlags.flags.bits.automatic = enabled; + packetUBXESFSTATUS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXESFSTATUS and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXESFSTATUS() +{ + packetUBXESFSTATUS = new UBX_ESF_STATUS_t; //Allocate RAM for the main struct + + if (packetUBXESFSTATUS == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXESFSTATUS: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXESFSTATUS->automaticFlags.flags.all = 0; + packetUBXESFSTATUS->callbackPointer = NULL; + packetUBXESFSTATUS->callbackData = NULL; + packetUBXESFSTATUS->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushESFSTATUS() +{ + if (packetUBXESFSTATUS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXESFSTATUS->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logESFSTATUS(boolean enabled) +{ + if (packetUBXESFSTATUS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXESFSTATUS->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** ESF INS automatic support + +boolean SFE_UBLOX_GPS::getEsfIns(uint16_t maxWait) +{ + return (getESFINS(maxWait)); +} + +boolean SFE_UBLOX_GPS::getESFINS(uint16_t maxWait) +{ + if (packetUBXESFINS == NULL) initPacketUBXESFINS(); //Check that RAM has been allocated for the ESF INS data + if (packetUBXESFINS == NULL) //Only attempt this if RAM allocation was successful + return false; + + if (packetUBXESFINS->automaticFlags.flags.bits.automatic && packetUBXESFINS->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfIns: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_ESF, UBX_ESF_INS); + return packetUBXESFINS->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXESFINS->automaticFlags.flags.bits.automatic && !packetUBXESFINS->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfIns: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfIns: Polling")); + // } + + //The GPS is not automatically reporting HNR PVT so we have to poll explicitly + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_INS; + 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) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfIns: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getEsfIns retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//Enable or disable automatic ESF INS message generation by the GNSS. This changes the way getESFIns +//works. +boolean SFE_UBLOX_GPS::setAutoESFINS(boolean enable, uint16_t maxWait) +{ + return setAutoESFINS(enable, true, maxWait); +} + +//Enable or disable automatic ESF INS message generation by the GNSS. This changes the way getESFIns +//works. +boolean SFE_UBLOX_GPS::setAutoESFINS(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXESFINS == NULL) initPacketUBXESFINS(); //Check that RAM has been allocated for the data + if (packetUBXESFINS == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_ESF; + payloadCfg[1] = UBX_ESF_INS; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXESFINS->automaticFlags.flags.bits.automatic = enable; + packetUBXESFINS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXESFINS->moduleQueried.moduleQueried.bits.all = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoESFINScallback(void (*callbackPointer)(UBX_ESF_INS_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoESFINS(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXESFINS->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXESFINS->callbackData = new UBX_ESF_INS_data_t; //Allocate RAM for the main struct + } + + if (packetUBXESFINS->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoESFINScallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXESFINS->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and ESF INS is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoESFINS(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXESFINS == NULL) initPacketUBXESFINS(); //Check that RAM has been allocated for the ESF INS data + if (packetUBXESFINS == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXESFINS->automaticFlags.flags.bits.automatic != enabled || packetUBXESFINS->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXESFINS->automaticFlags.flags.bits.automatic = enabled; + packetUBXESFINS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXESFINS and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXESFINS() +{ + packetUBXESFINS = new UBX_ESF_INS_t; //Allocate RAM for the main struct + if (packetUBXESFINS == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXESFINS: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXESFINS->automaticFlags.flags.all = 0; + packetUBXESFINS->callbackPointer = NULL; + packetUBXESFINS->callbackData = NULL; + packetUBXESFINS->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushESFINS() +{ + if (packetUBXESFINS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXESFINS->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logESFINS(boolean enabled) +{ + if (packetUBXESFINS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXESFINS->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** ESF MEAS automatic support + +boolean SFE_UBLOX_GPS::getEsfDataInfo(uint16_t maxWait) +{ + return (getESFMEAS(maxWait)); +} + +boolean SFE_UBLOX_GPS::getESFMEAS(uint16_t maxWait) +{ + if (packetUBXESFMEAS == NULL) initPacketUBXESFMEAS(); //Check that RAM has been allocated for the ESF MEAS data + if (packetUBXESFMEAS == NULL) //Only attempt this if RAM allocation was successful + return false; + + if (packetUBXESFMEAS->automaticFlags.flags.bits.automatic && packetUBXESFMEAS->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfDataInfo: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_ESF, UBX_ESF_MEAS); + return packetUBXESFMEAS->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXESFMEAS->automaticFlags.flags.bits.automatic && !packetUBXESFMEAS->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfDataInfo: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfDataInfo: Polling")); + // } + + //The GPS is not automatically reporting HNR PVT so we have to poll explicitly + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_MEAS; + 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) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfDataInfo: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getEsfDataInfo retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//Enable or disable automatic ESF MEAS message generation by the GNSS. This changes the way getESFDataInfo +//works. +boolean SFE_UBLOX_GPS::setAutoESFMEAS(boolean enable, uint16_t maxWait) +{ + return setAutoESFMEAS(enable, true, maxWait); +} + +//Enable or disable automatic ESF MEAS message generation by the GNSS. This changes the way getESFDataInfo +//works. +boolean SFE_UBLOX_GPS::setAutoESFMEAS(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXESFMEAS == NULL) initPacketUBXESFMEAS(); //Check that RAM has been allocated for the data + if (packetUBXESFMEAS == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_ESF; + payloadCfg[1] = UBX_ESF_MEAS; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXESFMEAS->automaticFlags.flags.bits.automatic = enable; + packetUBXESFMEAS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXESFMEAS->moduleQueried.moduleQueried.bits.all = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoESFMEAScallback(void (*callbackPointer)(UBX_ESF_MEAS_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoESFMEAS(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXESFMEAS->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXESFMEAS->callbackData = new UBX_ESF_MEAS_data_t; //Allocate RAM for the main struct + } + + if (packetUBXESFMEAS->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoESFMEAScallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXESFMEAS->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and ESF MEAS is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoESFMEAS(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXESFMEAS == NULL) initPacketUBXESFMEAS(); //Check that RAM has been allocated for the ESF MEAS data + if (packetUBXESFMEAS == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXESFMEAS->automaticFlags.flags.bits.automatic != enabled || packetUBXESFMEAS->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXESFMEAS->automaticFlags.flags.bits.automatic = enabled; + packetUBXESFMEAS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXESFMEAS and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXESFMEAS() +{ + packetUBXESFMEAS = new UBX_ESF_MEAS_t; //Allocate RAM for the main struct + if (packetUBXESFMEAS == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXESFMEAS: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXESFMEAS->automaticFlags.flags.all = 0; + packetUBXESFMEAS->callbackPointer = NULL; + packetUBXESFMEAS->callbackData = NULL; + packetUBXESFMEAS->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushESFMEAS() +{ + if (packetUBXESFMEAS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXESFMEAS->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logESFMEAS(boolean enabled) +{ + if (packetUBXESFMEAS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXESFMEAS->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** ESF RAW automatic support + +boolean SFE_UBLOX_GPS::getEsfRawDataInfo(uint16_t maxWait) +{ + return (getESFRAW(maxWait)); +} + +boolean SFE_UBLOX_GPS::getESFRAW(uint16_t maxWait) +{ + if (packetUBXESFRAW == NULL) initPacketUBXESFRAW(); //Check that RAM has been allocated for the ESF RAW data + if (packetUBXESFRAW == NULL) //Only attempt this if RAM allocation was successful + return false; + + if (packetUBXESFRAW->automaticFlags.flags.bits.automatic && packetUBXESFRAW->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfRawDataInfo: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_ESF, UBX_ESF_RAW); + return packetUBXESFRAW->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXESFRAW->automaticFlags.flags.bits.automatic && !packetUBXESFRAW->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfRawDataInfo: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfRawDataInfo: Polling")); + // } + + //The GPS is not automatically reporting HNR PVT so we have to poll explicitly + packetCfg.cls = UBX_CLASS_ESF; + packetCfg.id = UBX_ESF_RAW; + 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) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getEsfRawDataInfo: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getEsfRawDataInfo retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//Enable or disable automatic ESF RAW message generation by the GNSS. This changes the way getESFRawDataInfo +//works. +boolean SFE_UBLOX_GPS::setAutoESFRAW(boolean enable, uint16_t maxWait) +{ + return setAutoESFRAW(enable, true, maxWait); +} + +//Enable or disable automatic ESF RAW message generation by the GNSS. This changes the way getESFRawDataInfo +//works. +boolean SFE_UBLOX_GPS::setAutoESFRAW(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXESFRAW == NULL) initPacketUBXESFRAW(); //Check that RAM has been allocated for the data + if (packetUBXESFRAW == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_ESF; + payloadCfg[1] = UBX_ESF_RAW; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXESFRAW->automaticFlags.flags.bits.automatic = enable; + packetUBXESFRAW->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXESFRAW->moduleQueried.moduleQueried.bits.all = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoESFRAWcallback(void (*callbackPointer)(UBX_ESF_RAW_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoESFRAW(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXESFRAW->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXESFRAW->callbackData = new UBX_ESF_RAW_data_t; //Allocate RAM for the main struct + } + + if (packetUBXESFRAW->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoESFRAWcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXESFRAW->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and ESF RAW is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoESFRAW(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXESFRAW == NULL) initPacketUBXESFRAW(); //Check that RAM has been allocated for the ESF RAW data + if (packetUBXESFRAW == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXESFRAW->automaticFlags.flags.bits.automatic != enabled || packetUBXESFRAW->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXESFRAW->automaticFlags.flags.bits.automatic = enabled; + packetUBXESFRAW->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXESFRAW and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXESFRAW() +{ + packetUBXESFRAW = new UBX_ESF_RAW_t; //Allocate RAM for the main struct + if (packetUBXESFRAW == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXESFRAW: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXESFRAW->automaticFlags.flags.all = 0; + packetUBXESFRAW->callbackPointer = NULL; + packetUBXESFRAW->callbackData = NULL; + packetUBXESFRAW->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushESFRAW() +{ + if (packetUBXESFRAW == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXESFRAW->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logESFRAW(boolean enabled) +{ + if (packetUBXESFRAW == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXESFRAW->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** HNR ATT automatic support + +boolean SFE_UBLOX_GPS::getHNRAtt(uint16_t maxWait) +{ + return (getHNRATT(maxWait)); +} + +//Get the HNR Attitude data +// Returns true if the get HNR attitude is successful. Data is returned in hnrAtt +// Note: if hnrAttQueried is true, it gets set to false by this function since we assume +// that the user will read hnrAtt immediately after this. I.e. this function will +// only return true _once_ after each auto HNR Att is processed +boolean SFE_UBLOX_GPS::getHNRATT(uint16_t maxWait) +{ + if (packetUBXHNRATT == NULL) initPacketUBXHNRATT(); //Check that RAM has been allocated for the data + if (packetUBXHNRATT == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXHNRATT->automaticFlags.flags.bits.automatic && packetUBXHNRATT->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRAtt: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_HNR, UBX_HNR_ATT); + return packetUBXHNRATT->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXHNRATT->automaticFlags.flags.bits.automatic && !packetUBXHNRATT->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRAtt: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRAtt: Polling")); + // } + + //The GPS is not automatically reporting HNR attitude so we have to poll explicitly + packetCfg.cls = UBX_CLASS_HNR; + packetCfg.id = UBX_HNR_ATT; + packetCfg.len = 0; + 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) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRAtt: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getHNRAtt retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//Enable or disable automatic HNR attitude message generation by the GNSS. This changes the way getHNRAtt +//works. +boolean SFE_UBLOX_GPS::setAutoHNRATT(boolean enable, uint16_t maxWait) +{ + return setAutoHNRATT(enable, true, maxWait); +} + +//Enable or disable automatic HNR attitude message generation by the GNSS. This changes the way getHNRAtt +//works. +boolean SFE_UBLOX_GPS::setAutoHNRATT(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXHNRATT == NULL) initPacketUBXHNRATT(); //Check that RAM has been allocated for the data + if (packetUBXHNRATT == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_HNR; + payloadCfg[1] = UBX_HNR_ATT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXHNRATT->automaticFlags.flags.bits.automatic = enable; + packetUBXHNRATT->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXHNRATT->moduleQueried.moduleQueried.bits.all = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoHNRATTcallback(void (*callbackPointer)(UBX_HNR_ATT_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoHNRATT(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXHNRATT->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXHNRATT->callbackData = new UBX_HNR_ATT_data_t; //Allocate RAM for the main struct + } + + if (packetUBXHNRATT->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoHNRAttcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXHNRATT->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and HNR attitude is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoHNRATT(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXHNRATT == NULL) initPacketUBXHNRATT(); //Check that RAM has been allocated for the data + if (packetUBXHNRATT == NULL) //Bail if the RAM allocation failed + return (false); + + boolean changes = packetUBXHNRATT->automaticFlags.flags.bits.automatic != enabled || packetUBXHNRATT->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXHNRATT->automaticFlags.flags.bits.automatic = enabled; + packetUBXHNRATT->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXHNRATT and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXHNRATT() +{ + packetUBXHNRATT = new UBX_HNR_ATT_t; //Allocate RAM for the main struct + if (packetUBXHNRATT == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXHNRATT: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXHNRATT->automaticFlags.flags.all = 0; + packetUBXHNRATT->callbackPointer = NULL; + packetUBXHNRATT->callbackData = NULL; + packetUBXHNRATT->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushHNRATT() +{ + if (packetUBXHNRATT == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXHNRATT->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + + +//Log this data in file buffer +void SFE_UBLOX_GPS::logHNRATT(boolean enabled) +{ + if (packetUBXHNRATT == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXHNRATT->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** HNR DYN automatic support + +boolean SFE_UBLOX_GPS::getHNRDyn(uint16_t maxWait) +{ + return (getHNRINS(maxWait)); +} + +//Get the HNR vehicle dynamics data +// Returns true if the get HNR vehicle dynamics is successful. Data is returned in hnrVehDyn +// Note: if hnrDynQueried is true, it gets set to false by this function since we assume +// that the user will read hnrVehDyn immediately after this. I.e. this function will +// only return true _once_ after each auto HNR Dyn is processed +boolean SFE_UBLOX_GPS::getHNRINS(uint16_t maxWait) +{ + if (packetUBXHNRINS == NULL) initPacketUBXHNRINS(); //Check that RAM has been allocated for the data + if (packetUBXHNRINS == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXHNRINS->automaticFlags.flags.bits.automatic && packetUBXHNRINS->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRDyn: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_HNR, UBX_HNR_INS); + return packetUBXHNRINS->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXHNRINS->automaticFlags.flags.bits.automatic && !packetUBXHNRINS->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRDyn: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRDyn: Polling")); + // } + + //The GPS is not automatically reporting HNR vehicle dynamics so we have to poll explicitly + packetCfg.cls = UBX_CLASS_HNR; + packetCfg.id = UBX_HNR_INS; + packetCfg.len = 0; + 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) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRDyn: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getHNRDyn retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//Enable or disable automatic HNR vehicle dynamics message generation by the GNSS. This changes the way getHNRDyn +//works. +boolean SFE_UBLOX_GPS::setAutoHNRINS(boolean enable, uint16_t maxWait) +{ + return setAutoHNRINS(enable, true, maxWait); +} + +//Enable or disable automatic HNR vehicle dynamics message generation by the GNSS. This changes the way getHNRDyn +//works. +boolean SFE_UBLOX_GPS::setAutoHNRINS(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXHNRINS == NULL) initPacketUBXHNRINS(); //Check that RAM has been allocated for the data + if (packetUBXHNRINS == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_HNR; + payloadCfg[1] = UBX_HNR_INS; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXHNRINS->automaticFlags.flags.bits.automatic = enable; + packetUBXHNRINS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXHNRINS->moduleQueried.moduleQueried.bits.all = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoHNRINScallback(void (*callbackPointer)(UBX_HNR_INS_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoHNRINS(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXHNRINS->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXHNRINS->callbackData = new UBX_HNR_INS_data_t; //Allocate RAM for the main struct + } + + if (packetUBXHNRINS->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoHNRDyncallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXHNRINS->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and HNR vehicle dynamics is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoHNRINS(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXHNRINS == NULL) initPacketUBXHNRINS(); //Check that RAM has been allocated for the data + if (packetUBXHNRINS == NULL) //Bail if the RAM allocation failed + return (false); + + boolean changes = packetUBXHNRINS->automaticFlags.flags.bits.automatic != enabled || packetUBXHNRINS->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXHNRINS->automaticFlags.flags.bits.automatic = enabled; + packetUBXHNRINS->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXHNRINS and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXHNRINS() +{ + packetUBXHNRINS = new UBX_HNR_INS_t; //Allocate RAM for the main struct + if (packetUBXHNRINS == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXHNRINS: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXHNRINS->automaticFlags.flags.all = 0; + packetUBXHNRINS->callbackPointer = NULL; + packetUBXHNRINS->callbackData = NULL; + packetUBXHNRINS->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushHNRINS() +{ + if (packetUBXHNRINS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXHNRINS->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logHNRINS(boolean enabled) +{ + if (packetUBXHNRINS == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXHNRINS->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** HNR PVT automatic support + +//Get the HNR PVT data +// Returns true if the get HNR PVT is successful. Data is returned in hnrPVT +// Note: if hnrPVTQueried is true, it gets set to false by this function since we assume +// that the user will read hnrPVT immediately after this. I.e. this function will +// only return true _once_ after each auto HNR PVT is processed +boolean SFE_UBLOX_GPS::getHNRPVT(uint16_t maxWait) +{ + if (packetUBXHNRPVT == NULL) initPacketUBXHNRPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXHNRPVT == NULL) //Only attempt this if RAM allocation was successful + return false; + + if (packetUBXHNRPVT->automaticFlags.flags.bits.automatic && packetUBXHNRPVT->automaticFlags.flags.bits.implicitUpdate) + { + //The GPS is automatically reporting, we just check whether we got unread data + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRPVT: Autoreporting")); + // } + checkUbloxInternal(&packetCfg, UBX_CLASS_HNR, UBX_HNR_PVT); + return packetUBXHNRPVT->moduleQueried.moduleQueried.bits.all; + } + else if (packetUBXHNRPVT->automaticFlags.flags.bits.automatic && !packetUBXHNRPVT->automaticFlags.flags.bits.implicitUpdate) + { + //Someone else has to call checkUblox for us... + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRPVT: Exit immediately")); + // } + return (false); + } + else + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRPVT: Polling")); + // } + + //The GPS is not automatically reporting HNR PVT so we have to poll explicitly + packetCfg.cls = UBX_CLASS_HNR; + packetCfg.id = UBX_HNR_PVT; + packetCfg.len = 0; + 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) + { + // if (_printDebug == true) + // { + // _debugSerial->println(F("getHNRPVT: data in packetCfg was OVERWRITTEN by another message (but that's OK)")); + // } + return (true); + } + + // if (_printDebug == true) + // { + // _debugSerial->print(F("getHNRPVT retVal: ")); + // _debugSerial->println(statusString(retVal)); + // } + return (false); + } + + return (false); // Trap. We should never get here... +} + +//Enable or disable automatic HNR PVT message generation by the GNSS. This changes the way getHNRPVT +//works. +boolean SFE_UBLOX_GPS::setAutoHNRPVT(boolean enable, uint16_t maxWait) +{ + return setAutoHNRPVT(enable, true, maxWait); +} + +//Enable or disable automatic HNR PVT message generation by the GNSS. This changes the way getHNRPVT +//works. +boolean SFE_UBLOX_GPS::setAutoHNRPVT(boolean enable, boolean implicitUpdate, uint16_t maxWait) +{ + if (packetUBXHNRPVT == NULL) initPacketUBXHNRPVT(); //Check that RAM has been allocated for the data + if (packetUBXHNRPVT == NULL) //Only attempt this if RAM allocation was successful + return false; + + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_MSG; + packetCfg.len = 3; + packetCfg.startingSpot = 0; + payloadCfg[0] = UBX_CLASS_HNR; + payloadCfg[1] = UBX_HNR_PVT; + payloadCfg[2] = enable ? 1 : 0; // rate relative to navigation freq. + + boolean ok = ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK + if (ok) + { + packetUBXHNRPVT->automaticFlags.flags.bits.automatic = enable; + packetUBXHNRPVT->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + packetUBXHNRPVT->moduleQueried.moduleQueried.bits.all = false; // Mark data as stale + return ok; +} + +//Enable automatic navigation message generation by the GNSS. +boolean SFE_UBLOX_GPS::setAutoHNRPVTcallback(void (*callbackPointer)(UBX_HNR_PVT_data_t), uint16_t maxWait) +{ + // Enable auto messages. Set implicitUpdate to false as we expect the user to call checkUblox manually. + boolean result = setAutoHNRPVT(true, false, maxWait); + if (!result) + return (result); // Bail if setAuto failed + + if (packetUBXHNRPVT->callbackData == NULL) //Check if RAM has been allocated for the callback copy + { + packetUBXHNRPVT->callbackData = new UBX_HNR_PVT_data_t; //Allocate RAM for the main struct + } + + if (packetUBXHNRPVT->callbackData == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("setAutoHNRPVTcallback: PANIC! RAM allocation failed!")); + return (false); + } + + packetUBXHNRPVT->callbackPointer = callbackPointer; + return (true); +} + +//In case no config access to the GNSS is possible and HNR PVT is send cyclically already +//set config to suitable parameters +boolean SFE_UBLOX_GPS::assumeAutoHNRPVT(boolean enabled, boolean implicitUpdate) +{ + if (packetUBXHNRPVT == NULL) initPacketUBXHNRPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXHNRPVT == NULL) //Only attempt this if RAM allocation was successful + return false; + + boolean changes = packetUBXHNRPVT->automaticFlags.flags.bits.automatic != enabled || packetUBXHNRPVT->automaticFlags.flags.bits.implicitUpdate != implicitUpdate; + if (changes) + { + packetUBXHNRPVT->automaticFlags.flags.bits.automatic = enabled; + packetUBXHNRPVT->automaticFlags.flags.bits.implicitUpdate = implicitUpdate; + } + return changes; +} + +// PRIVATE: Allocate RAM for packetUBXHNRPVT and initialize it +boolean SFE_UBLOX_GPS::initPacketUBXHNRPVT() +{ + packetUBXHNRPVT = new UBX_HNR_PVT_t; //Allocate RAM for the main struct + if (packetUBXHNRPVT == NULL) + { + if ((_printDebug == true) || (_printLimitedDebug == true)) // This is important. Print this if doing limited debugging + _debugSerial->println(F("initPacketUBXHNRPVT: PANIC! RAM allocation failed! This will end _very_ badly...")); + return (false); + } + packetUBXHNRPVT->automaticFlags.flags.all = 0; + packetUBXHNRPVT->callbackPointer = NULL; + packetUBXHNRPVT->callbackData = NULL; + packetUBXHNRPVT->moduleQueried.moduleQueried.all = 0; + return (true); +} + +//Mark all the data as read/stale +void SFE_UBLOX_GPS::flushHNRPVT() +{ + if (packetUBXHNRPVT == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXHNRPVT->moduleQueried.moduleQueried.all = 0; //Mark all datums as stale (read before) +} + +//Log this data in file buffer +void SFE_UBLOX_GPS::logHNRPVT(boolean enabled) +{ + if (packetUBXHNRPVT == NULL) return; // Bail if RAM has not been allocated (otherwise we could be writing anywhere!) + packetUBXHNRPVT->automaticFlags.flags.bits.addToFileBuffer = (uint8_t)enabled; +} + +// ***** CFG RATE Helper Functions + +//Set the rate at which the module will give us an updated navigation solution +//Expects a number that is the updates per second. For example 1 = 1Hz, 2 = 2Hz, etc. +//Max is 40Hz(?!) +boolean SFE_UBLOX_GPS::setNavigationFrequency(uint8_t navFreq, uint16_t maxWait) +{ + //if(updateRate > 40) updateRate = 40; //Not needed: module will correct out of bounds values + + //Adjust the I2C polling timeout based on update rate + i2cPollingWait = 1000 / (((int)navFreq) * 4); //This is the number of ms to wait between checks for new I2C data + + //Query the module for the latest lat/long + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_RATE; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //This will load the payloadCfg array with current settings of the given register + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) // We are expecting data and an ACK + return (false); //If command send fails then bail + + uint16_t measurementRate = 1000 / navFreq; + + //payloadCfg is now loaded with current bytes. Change only the ones we need to + payloadCfg[0] = measurementRate & 0xFF; //measRate LSB + payloadCfg[1] = measurementRate >> 8; //measRate MSB + + return ((sendCommand(&packetCfg, maxWait)) == SFE_UBLOX_STATUS_DATA_SENT); // We are only expecting an ACK +} + +//Get the rate at which the module is outputting nav solutions +uint8_t SFE_UBLOX_GPS::getNavigationFrequency(uint16_t maxWait) +{ + if (packetUBXCFGRATE == NULL) initPacketUBXCFGRATE(); //Check that RAM has been allocated for the RATE data + if (packetUBXCFGRATE == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXCFGRATE->moduleQueried.moduleQueried.bits.measRate == false) + getNavigationFrequencyInternal(maxWait); + packetUBXCFGRATE->moduleQueried.moduleQueried.bits.measRate = false; //Since we are about to give this to user, mark this data as stale + packetUBXCFGRATE->moduleQueried.moduleQueried.bits.all = false; + + uint16_t measurementRate = packetUBXCFGRATE->data.measRate; + + measurementRate = 1000 / measurementRate; //This may return an int when it's a float, but I'd rather not return 4 bytes + return (measurementRate); +} + +// ***** DOP Helper Functions + +uint16_t SFE_UBLOX_GPS::getGeometricDOP(uint16_t maxWait) +{ + if (packetUBXNAVDOP == NULL) initPacketUBXNAVDOP(); //Check that RAM has been allocated for the DOP data + if (packetUBXNAVDOP == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVDOP->moduleQueried.moduleQueried.bits.gDOP == false) + getDOP(maxWait); + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.gDOP = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVDOP->data.gDOP); +} + +uint16_t SFE_UBLOX_GPS::getPositionDOP(uint16_t maxWait) +{ + if (packetUBXNAVDOP == NULL) initPacketUBXNAVDOP(); //Check that RAM has been allocated for the DOP data + if (packetUBXNAVDOP == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVDOP->moduleQueried.moduleQueried.bits.pDOP == false) + getDOP(maxWait); + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.pDOP = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVDOP->data.pDOP); +} + +uint16_t SFE_UBLOX_GPS::getTimeDOP(uint16_t maxWait) +{ + if (packetUBXNAVDOP == NULL) initPacketUBXNAVDOP(); //Check that RAM has been allocated for the DOP data + if (packetUBXNAVDOP == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVDOP->moduleQueried.moduleQueried.bits.tDOP == false) + getDOP(maxWait); + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.tDOP = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVDOP->data.tDOP); +} + +uint16_t SFE_UBLOX_GPS::getVerticalDOP(uint16_t maxWait) +{ + if (packetUBXNAVDOP == NULL) initPacketUBXNAVDOP(); //Check that RAM has been allocated for the DOP data + if (packetUBXNAVDOP == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVDOP->moduleQueried.moduleQueried.bits.vDOP == false) + getDOP(maxWait); + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.vDOP = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVDOP->data.vDOP); +} + +uint16_t SFE_UBLOX_GPS::getHorizontalDOP(uint16_t maxWait) +{ + if (packetUBXNAVDOP == NULL) initPacketUBXNAVDOP(); //Check that RAM has been allocated for the DOP data + if (packetUBXNAVDOP == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVDOP->moduleQueried.moduleQueried.bits.hDOP == false) + getDOP(maxWait); + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.hDOP = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVDOP->data.hDOP); +} + +uint16_t SFE_UBLOX_GPS::getNorthingDOP(uint16_t maxWait) +{ + if (packetUBXNAVDOP == NULL) initPacketUBXNAVDOP(); //Check that RAM has been allocated for the DOP data + if (packetUBXNAVDOP == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVDOP->moduleQueried.moduleQueried.bits.nDOP == false) + getDOP(maxWait); + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.nDOP = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVDOP->data.nDOP); +} + +uint16_t SFE_UBLOX_GPS::getEastingDOP(uint16_t maxWait) +{ + if (packetUBXNAVDOP == NULL) initPacketUBXNAVDOP(); //Check that RAM has been allocated for the DOP data + if (packetUBXNAVDOP == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVDOP->moduleQueried.moduleQueried.bits.eDOP == false) + getDOP(maxWait); + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.eDOP = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVDOP->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVDOP->data.eDOP); +} + +// ***** ATT Helper Functions + +float SFE_UBLOX_GPS::getATTroll(uint16_t maxWait) // Returned as degrees +{ + if (packetUBXNAVATT == NULL) initPacketUBXNAVATT(); //Check that RAM has been allocated for the NAV ATT data + if (packetUBXNAVATT == NULL) //Bail if the RAM allocation failed + return (0); + + if (packetUBXNAVATT->moduleQueried.moduleQueried.bits.roll == false) + getNAVATT(maxWait); + packetUBXNAVATT->moduleQueried.moduleQueried.bits.roll = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVATT->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXNAVATT->data.roll) / 100000.0); // Convert to degrees +} + +float SFE_UBLOX_GPS::getATTpitch(uint16_t maxWait) // Returned as degrees +{ + if (packetUBXNAVATT == NULL) initPacketUBXNAVATT(); //Check that RAM has been allocated for the NAV ATT data + if (packetUBXNAVATT == NULL) //Bail if the RAM allocation failed + return (0); + + if (packetUBXNAVATT->moduleQueried.moduleQueried.bits.pitch == false) + getNAVATT(maxWait); + packetUBXNAVATT->moduleQueried.moduleQueried.bits.pitch = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVATT->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXNAVATT->data.pitch) / 100000.0); // Convert to degrees +} + +float SFE_UBLOX_GPS::getATTheading(uint16_t maxWait) // Returned as degrees +{ + if (packetUBXNAVATT == NULL) initPacketUBXNAVATT(); //Check that RAM has been allocated for the NAV ATT data + if (packetUBXNAVATT == NULL) //Bail if the RAM allocation failed + return (0); + + if (packetUBXNAVATT->moduleQueried.moduleQueried.bits.heading == false) + getNAVATT(maxWait); + packetUBXNAVATT->moduleQueried.moduleQueried.bits.heading = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVATT->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXNAVATT->data.heading) / 100000.0); // Convert to degrees +} + +// ***** PVT Helper Functions + +uint32_t SFE_UBLOX_GPS::getTimeOfWeek(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.iTOW == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.iTOW = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.iTOW); +} + +//Get the current year +uint16_t SFE_UBLOX_GPS::getYear(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.year == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.year = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.year); +} + +//Get the current month +uint8_t SFE_UBLOX_GPS::getMonth(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.month == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.month = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.month); +} + +//Get the current day +uint8_t SFE_UBLOX_GPS::getDay(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.day == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.day = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.day); +} + +//Get the current hour +uint8_t SFE_UBLOX_GPS::getHour(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.hour == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.hour = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.hour); +} + +//Get the current minute +uint8_t SFE_UBLOX_GPS::getMinute(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.min == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.min = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.min); +} + +//Get the current second +uint8_t SFE_UBLOX_GPS::getSecond(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.sec == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.sec = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.sec); +} + +//Get the current millisecond +uint16_t SFE_UBLOX_GPS::getMillisecond(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.iTOW == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.iTOW = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.iTOW % 1000); +} + +//Get the current nanoseconds - includes milliseconds +int32_t SFE_UBLOX_GPS::getNanosecond(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.nano == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.nano = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.nano); +} + +//Get the current date validity +bool SFE_UBLOX_GPS::getDateValid(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.validDate == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.validDate = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return ((bool)packetUBXNAVPVT->data.valid.bits.validDate); +} + +//Get the current time validity +bool SFE_UBLOX_GPS::getTimeValid(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.validTime == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.validTime = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return ((bool)packetUBXNAVPVT->data.valid.bits.validTime); +} + +//Get the current fix type +//0=no fix, 1=dead reckoning, 2=2D, 3=3D, 4=GNSS, 5=Time fix +uint8_t SFE_UBLOX_GPS::getFixType(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.fixType == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.fixType = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.fixType); +} + +//Get whether we have a valid fix (i.e within DOP & accuracy masks) +bool SFE_UBLOX_GPS::getGnssFixOk(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.gnssFixOK == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.gnssFixOK = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.flags.bits.gnssFixOK); +} + +//Get whether differential corrections were applied +bool SFE_UBLOX_GPS::getDiffSoln(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.diffSoln == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.diffSoln = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.flags.bits.diffSoln); +} + +//Get whether head vehicle valid or not +bool SFE_UBLOX_GPS::getHeadVehValid(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.headVehValid == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.headVehValid = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.flags.bits.headVehValid); +} + +//Get the carrier phase range solution status +//Useful when querying module to see if it has high-precision RTK fix +//0=No solution, 1=Float solution, 2=Fixed solution +uint8_t SFE_UBLOX_GPS::getCarrierSolutionType(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.carrSoln == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.carrSoln = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.flags.bits.carrSoln); +} + +//Get the number of satellites used in fix +uint8_t SFE_UBLOX_GPS::getSIV(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.numSV == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.numSV = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.numSV); +} + +//Get the current longitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLongitude(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.lon == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.lon = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.lon); +} + +//Get the current latitude in degrees +//Returns a long representing the number of degrees *10^-7 +int32_t SFE_UBLOX_GPS::getLatitude(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.lat == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.lat = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.lat); +} + +//Get the current altitude in mm according to ellipsoid model +int32_t SFE_UBLOX_GPS::getAltitude(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.height == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.height = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.height); +} + +//Get the current altitude in mm according to mean sea level +//Ellipsoid model: https://www.esri.com/news/arcuser/0703/geoid1of3.html +//Difference between Ellipsoid Model and Mean Sea Level: https://eos-gnss.com/elevation-for-beginners/ +int32_t SFE_UBLOX_GPS::getAltitudeMSL(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.hMSL == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.hMSL = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.hMSL); +} + +int32_t SFE_UBLOX_GPS::getHorizontalAccEst(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.hAcc == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.hAcc = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.hAcc); +} + +int32_t SFE_UBLOX_GPS::getVerticalAccEst(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.vAcc == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.vAcc = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.vAcc); +} + +int32_t SFE_UBLOX_GPS::getNedNorthVel(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.velN == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.velN = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.velN); +} + +int32_t SFE_UBLOX_GPS::getNedEastVel(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.velE == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.velE = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.velE); +} + +int32_t SFE_UBLOX_GPS::getNedDownVel(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.velD == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.velD = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.velD); +} + +//Get the ground speed in mm/s +int32_t SFE_UBLOX_GPS::getGroundSpeed(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.gSpeed == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.gSpeed = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.gSpeed); +} + +//Get the heading of motion (as opposed to heading of car) in degrees * 10^-5 +int32_t SFE_UBLOX_GPS::getHeading(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.headMot == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.headMot = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.headMot); +} + +uint32_t SFE_UBLOX_GPS::getSpeedAccEst(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.sAcc == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.sAcc = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.sAcc); +} + +uint32_t SFE_UBLOX_GPS::getHeadingAccEst(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.headAcc == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.headAcc = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.headAcc); +} + +//Get the positional dillution of precision * 10^-2 (dimensionless) +uint16_t SFE_UBLOX_GPS::getPDOP(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.pDOP == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.pDOP = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.pDOP); +} + +bool SFE_UBLOX_GPS::getInvalidLlh(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.invalidLlh == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.invalidLlh = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return ((bool)packetUBXNAVPVT->data.flags3.bits.invalidLlh); +} + +int32_t SFE_UBLOX_GPS::getHeadVeh(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.headVeh == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.headVeh = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.headVeh); +} + +int16_t SFE_UBLOX_GPS::getMagDec(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.magDec == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.magDec = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.magDec); +} + +uint16_t SFE_UBLOX_GPS::getMagAcc(uint16_t maxWait) +{ + if (packetUBXNAVPVT == NULL) initPacketUBXNAVPVT(); //Check that RAM has been allocated for the PVT data + if (packetUBXNAVPVT == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.magAcc == false) + getPVT(maxWait); + packetUBXNAVPVT->moduleQueried.moduleQueried2.bits.magAcc = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVPVT->moduleQueried.moduleQueried1.bits.all = false; + return (packetUBXNAVPVT->data.magAcc); +} + +// getGeoidSeparation is currently redundant. The geoid separation seems to only be provided in NMEA GGA and GNS messages. +int32_t SFE_UBLOX_GPS::getGeoidSeparation(uint16_t maxWait) +{ + return (0); +} + +// ***** HPPOSECEF Helper Functions + +//Get the current 3D high precision positional accuracy - a fun thing to watch +//Returns a long representing the 3D accuracy in millimeters +uint32_t SFE_UBLOX_GPS::getPositionAccuracy(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSECEF == NULL) initPacketUBXNAVHPPOSECEF(); //Check that RAM has been allocated for the HPPOSECEF data + if (packetUBXNAVHPPOSECEF == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSECEF->moduleQueried.moduleQueried.bits.pAcc == false) + getNAVHPPOSECEF(maxWait); + packetUBXNAVHPPOSECEF->moduleQueried.moduleQueried.bits.pAcc = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSECEF->moduleQueried.moduleQueried.bits.all = false; + + uint32_t tempAccuracy = packetUBXNAVHPPOSECEF->data.pAcc; + + if ((tempAccuracy % 10) >= 5) + tempAccuracy += 5; //Round fraction of mm up to next mm if .5 or above + tempAccuracy /= 10; //Convert 0.1mm units to mm + + return (tempAccuracy); +} + +// ***** HPPOSLLH Helper Functions + +uint32_t SFE_UBLOX_GPS::getTimeOfWeekFromHPPOSLLH(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.iTOW == false) + getHPPOSLLH(maxWait); + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.iTOW = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVHPPOSLLH->data.iTOW); +} + +int32_t SFE_UBLOX_GPS::getHighResLongitude(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.lon == false) + getHPPOSLLH(maxWait); + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.lon = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVHPPOSLLH->data.lon); +} + +int32_t SFE_UBLOX_GPS::getHighResLatitude(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.lat == false) + getHPPOSLLH(maxWait); + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.lat = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVHPPOSLLH->data.lat); +} + +int32_t SFE_UBLOX_GPS::getElipsoid(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.height == false) + getHPPOSLLH(maxWait); + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.height = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVHPPOSLLH->data.height); +} + +int32_t SFE_UBLOX_GPS::getMeanSeaLevel(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.hMSL == false) + getHPPOSLLH(maxWait); + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.hMSL = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVHPPOSLLH->data.hMSL); +} + +int8_t SFE_UBLOX_GPS::getHighResLongitudeHp(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.lonHp == false) + getHPPOSLLH(maxWait); + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.lonHp = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVHPPOSLLH->data.lonHp); +} + +int8_t SFE_UBLOX_GPS::getHighResLatitudeHp(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.latHp == false) + getHPPOSLLH(maxWait); + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.latHp = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVHPPOSLLH->data.latHp); +} + +int8_t SFE_UBLOX_GPS::getElipsoidHp(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.heightHp == false) + getHPPOSLLH(maxWait); + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.heightHp = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVHPPOSLLH->data.heightHp); +} + +int8_t SFE_UBLOX_GPS::getMeanSeaLevelHp(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.hMSLHp == false) + getHPPOSLLH(maxWait); + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.hMSLHp = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVHPPOSLLH->data.hMSLHp); +} + +uint32_t SFE_UBLOX_GPS::getHorizontalAccuracy(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.hAcc == false) + getHPPOSLLH(maxWait); + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.hAcc = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVHPPOSLLH->data.hAcc); +} + +uint32_t SFE_UBLOX_GPS::getVerticalAccuracy(uint16_t maxWait) +{ + if (packetUBXNAVHPPOSLLH == NULL) initPacketUBXNAVHPPOSLLH(); //Check that RAM has been allocated for the HPPOSLLH data + if (packetUBXNAVHPPOSLLH == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.vAcc == false) + getHPPOSLLH(maxWait); + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.vAcc = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVHPPOSLLH->moduleQueried.moduleQueried.bits.all = false; + return (packetUBXNAVHPPOSLLH->data.vAcc); +} + +// ***** SVIN Helper Functions + +boolean SFE_UBLOX_GPS::getSurveyInActive(uint16_t maxWait) +{ + if (packetUBXNAVSVIN == NULL) initPacketUBXNAVSVIN(); //Check that RAM has been allocated for the SVIN data + if (packetUBXNAVSVIN == NULL) //Bail if the RAM allocation failed + return false; + + if (packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.active == false) + getSurveyStatus(maxWait); + packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.active = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.all = false; + return ((boolean)packetUBXNAVSVIN->data.active); +} + +boolean SFE_UBLOX_GPS::getSurveyInValid(uint16_t maxWait) +{ + if (packetUBXNAVSVIN == NULL) initPacketUBXNAVSVIN(); //Check that RAM has been allocated for the SVIN data + if (packetUBXNAVSVIN == NULL) //Bail if the RAM allocation failed + return false; + + if (packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.valid == false) + getSurveyStatus(maxWait); + packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.valid = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.all = false; + return ((boolean)packetUBXNAVSVIN->data.valid); +} + +uint16_t SFE_UBLOX_GPS::getSurveyInObservationTime(uint16_t maxWait) // Truncated to 65535 seconds +{ + if (packetUBXNAVSVIN == NULL) initPacketUBXNAVSVIN(); //Check that RAM has been allocated for the SVIN data + if (packetUBXNAVSVIN == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.dur == false) + getSurveyStatus(maxWait); + packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.dur = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.all = false; + + //dur (Passed survey-in observation time) is U4 (uint32_t) seconds. We truncate to 16 bits + //(waiting more than 65535 seconds (18.2 hours) seems excessive!) + uint32_t tmpObsTime = packetUBXNAVSVIN->data.dur; + if (tmpObsTime <= 0xFFFF) + { + return((uint16_t)tmpObsTime); + } + else + { + return(0xFFFF); + } +} + +float SFE_UBLOX_GPS::getSurveyInMeanAccuracy(uint16_t maxWait) // Returned as m +{ + if (packetUBXNAVSVIN == NULL) initPacketUBXNAVSVIN(); //Check that RAM has been allocated for the SVIN data + if (packetUBXNAVSVIN == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.meanAcc == false) + getSurveyStatus(maxWait); + packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.meanAcc = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVSVIN->moduleQueried.moduleQueried.bits.all = false; + + // meanAcc is U4 (uint32_t) in 0.1mm. We convert this to float. + uint32_t tempFloat = packetUBXNAVSVIN->data.meanAcc; + return (((float)tempFloat) / 10000.0); //Convert 0.1mm to m +} + +// ***** RELPOSNED Helper Functions and automatic support + +float SFE_UBLOX_GPS::getRelPosN(uint16_t maxWait) // Returned as m +{ + if (packetUBXNAVRELPOSNED == NULL) initPacketUBXNAVRELPOSNED(); //Check that RAM has been allocated for the RELPOSNED data + if (packetUBXNAVRELPOSNED == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.relPosN == false) + getRELPOSNED(maxWait); + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.relPosN = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXNAVRELPOSNED->data.relPosN) / 100.0); // Convert to m +} + +float SFE_UBLOX_GPS::getRelPosE(uint16_t maxWait) // Returned as m +{ + if (packetUBXNAVRELPOSNED == NULL) initPacketUBXNAVRELPOSNED(); //Check that RAM has been allocated for the RELPOSNED data + if (packetUBXNAVRELPOSNED == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.relPosE == false) + getRELPOSNED(maxWait); + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.relPosE = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXNAVRELPOSNED->data.relPosE) / 100.0); // Convert to m +} + +float SFE_UBLOX_GPS::getRelPosD(uint16_t maxWait) // Returned as m +{ + if (packetUBXNAVRELPOSNED == NULL) initPacketUBXNAVRELPOSNED(); //Check that RAM has been allocated for the RELPOSNED data + if (packetUBXNAVRELPOSNED == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.relPosD == false) + getRELPOSNED(maxWait); + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.relPosD = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXNAVRELPOSNED->data.relPosD) / 100.0); // Convert to m +} + +float SFE_UBLOX_GPS::getRelPosAccN(uint16_t maxWait) // Returned as m +{ + if (packetUBXNAVRELPOSNED == NULL) initPacketUBXNAVRELPOSNED(); //Check that RAM has been allocated for the RELPOSNED data + if (packetUBXNAVRELPOSNED == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.accN == false) + getRELPOSNED(maxWait); + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.accN = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXNAVRELPOSNED->data.accN) / 10000.0); // Convert to m +} + +float SFE_UBLOX_GPS::getRelPosAccE(uint16_t maxWait) // Returned as m +{ + if (packetUBXNAVRELPOSNED == NULL) initPacketUBXNAVRELPOSNED(); //Check that RAM has been allocated for the RELPOSNED data + if (packetUBXNAVRELPOSNED == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.accE == false) + getRELPOSNED(maxWait); + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.accE = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXNAVRELPOSNED->data.accE) / 10000.0); // Convert to m +} + +float SFE_UBLOX_GPS::getRelPosAccD(uint16_t maxWait) // Returned as m +{ + if (packetUBXNAVRELPOSNED == NULL) initPacketUBXNAVRELPOSNED(); //Check that RAM has been allocated for the RELPOSNED data + if (packetUBXNAVRELPOSNED == NULL) //Bail if the RAM allocation failed + return 0; + + if (packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.accD == false) + getRELPOSNED(maxWait); + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.accD = false; //Since we are about to give this to user, mark this data as stale + packetUBXNAVRELPOSNED->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXNAVRELPOSNED->data.accD) / 10000.0); // Convert to m +} + +// ***** ESF Helper Functions + +float SFE_UBLOX_GPS::getESFroll(uint16_t maxWait) // Returned as degrees +{ + if (packetUBXESFALG == NULL) initPacketUBXESFALG(); //Check that RAM has been allocated for the ESF ALG data + if (packetUBXESFALG == NULL) //Bail if the RAM allocation failed + return (0); + + if (packetUBXESFALG->moduleQueried.moduleQueried.bits.roll == false) + getESFALG(maxWait); + packetUBXESFALG->moduleQueried.moduleQueried.bits.roll = false; //Since we are about to give this to user, mark this data as stale + packetUBXESFALG->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXESFALG->data.roll) / 100.0); // Convert to degrees +} + +float SFE_UBLOX_GPS::getESFpitch(uint16_t maxWait) // Returned as degrees +{ + if (packetUBXESFALG == NULL) initPacketUBXESFALG(); //Check that RAM has been allocated for the ESF ALG data + if (packetUBXESFALG == NULL) //Bail if the RAM allocation failed + return (0); + + if (packetUBXESFALG->moduleQueried.moduleQueried.bits.pitch == false) + getESFALG(maxWait); + packetUBXESFALG->moduleQueried.moduleQueried.bits.pitch = false; //Since we are about to give this to user, mark this data as stale + packetUBXESFALG->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXESFALG->data.pitch) / 100.0); // Convert to degrees +} + +float SFE_UBLOX_GPS::getESFyaw(uint16_t maxWait) // Returned as degrees +{ + if (packetUBXESFALG == NULL) initPacketUBXESFALG(); //Check that RAM has been allocated for the ESF ALG data + if (packetUBXESFALG == NULL) //Bail if the RAM allocation failed + return (0); + + if (packetUBXESFALG->moduleQueried.moduleQueried.bits.yaw == false) + getESFALG(maxWait); + packetUBXESFALG->moduleQueried.moduleQueried.bits.yaw = false; //Since we are about to give this to user, mark this data as stale + packetUBXESFALG->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXESFALG->data.yaw) / 100.0); // Convert to degrees +} + +boolean SFE_UBLOX_GPS::getSensorFusionMeasurement(UBX_ESF_MEAS_sensorData_t *sensorData, uint8_t sensor, uint16_t maxWait) +{ + if (packetUBXESFMEAS == NULL) initPacketUBXESFMEAS(); //Check that RAM has been allocated for the ESF MEAS data + if (packetUBXESFMEAS == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXESFMEAS->moduleQueried.moduleQueried.bits.data & (1 << sensor) == 0) + getESFMEAS(maxWait); + packetUBXESFMEAS->moduleQueried.moduleQueried.bits.data &= ~(1 << sensor); //Since we are about to give this to user, mark this data as stale + packetUBXESFMEAS->moduleQueried.moduleQueried.bits.all = false; + sensorData->data.all = packetUBXESFMEAS->data.data[sensor].data.all; + return (true); +} + +boolean SFE_UBLOX_GPS::getSensorFusionMeasurement(UBX_ESF_MEAS_sensorData_t *sensorData, UBX_ESF_MEAS_data_t ubxDataStruct, uint8_t sensor) +{ + sensorData->data.all = ubxDataStruct.data[sensor].data.all; + return (true); +} + +boolean SFE_UBLOX_GPS::getRawSensorMeasurement(UBX_ESF_RAW_sensorData_t *sensorData, uint8_t sensor, uint16_t maxWait) +{ + if (packetUBXESFRAW == NULL) initPacketUBXESFRAW(); //Check that RAM has been allocated for the ESF RAW data + if (packetUBXESFRAW == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXESFRAW->moduleQueried.moduleQueried.bits.data & (1 << sensor) == 0) + getESFRAW(maxWait); + packetUBXESFRAW->moduleQueried.moduleQueried.bits.data &= ~(1 << sensor); //Since we are about to give this to user, mark this data as stale + packetUBXESFRAW->moduleQueried.moduleQueried.bits.all = false; + sensorData->data.all = packetUBXESFRAW->data.data[sensor].data.all; + sensorData->sTag = packetUBXESFRAW->data.data[sensor].sTag; + return (true); +} + +boolean SFE_UBLOX_GPS::getRawSensorMeasurement(UBX_ESF_RAW_sensorData_t *sensorData, UBX_ESF_RAW_data_t ubxDataStruct, uint8_t sensor) +{ + sensorData->data.all = ubxDataStruct.data[sensor].data.all; + sensorData->sTag = ubxDataStruct.data[sensor].sTag; + return (true); +} + +boolean SFE_UBLOX_GPS::getSensorFusionStatus(UBX_ESF_STATUS_sensorStatus_t *sensorStatus, uint8_t sensor, uint16_t maxWait) +{ + if (packetUBXESFSTATUS == NULL) initPacketUBXESFSTATUS(); //Check that RAM has been allocated for the ESF STATUS data + if (packetUBXESFSTATUS == NULL) //Bail if the RAM allocation failed + return (false); + + if (packetUBXESFSTATUS->moduleQueried.moduleQueried.bits.status & (1 << sensor) == 0) + getESFSTATUS(maxWait); + packetUBXESFSTATUS->moduleQueried.moduleQueried.bits.status &= ~(1 << sensor); //Since we are about to give this to user, mark this data as stale + packetUBXESFSTATUS->moduleQueried.moduleQueried.bits.all = false; + sensorStatus->sensStatus1.all = packetUBXESFSTATUS->data.status[sensor].sensStatus1.all; + sensorStatus->sensStatus2.all = packetUBXESFSTATUS->data.status[sensor].sensStatus2.all; + sensorStatus->freq = packetUBXESFSTATUS->data.status[sensor].freq; + sensorStatus->faults.all = packetUBXESFSTATUS->data.status[sensor].faults.all; + return (true); +} + +boolean SFE_UBLOX_GPS::getSensorFusionStatus(UBX_ESF_STATUS_sensorStatus_t *sensorStatus, UBX_ESF_STATUS_data_t ubxDataStruct, uint8_t sensor) +{ + sensorStatus->sensStatus1.all = ubxDataStruct.status[sensor].sensStatus1.all; + sensorStatus->sensStatus2.all = ubxDataStruct.status[sensor].sensStatus2.all; + sensorStatus->freq = ubxDataStruct.status[sensor].freq; + sensorStatus->faults.all = ubxDataStruct.status[sensor].faults.all; + return (true); +} + +// ***** HNR Helper Functions + +// Set the High Navigation Rate +// Returns true if the setHNRNavigationRate is successful +boolean SFE_UBLOX_GPS::setHNRNavigationRate(uint8_t rate, uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_HNR; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current HNR settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (false); + + //Load the new navigation rate into payloadCfg + payloadCfg[0] = rate; + + //Update the navigation rate + sfe_ublox_status_e result = sendCommand(&packetCfg, maxWait); // We are only expecting an ACK + + //Adjust the I2C polling timeout based on update rate + if (result == SFE_UBLOX_STATUS_DATA_SENT) + i2cPollingWait = 1000 / (((int)rate) * 4); //This is the number of ms to wait between checks for new I2C data + + return (result == SFE_UBLOX_STATUS_DATA_SENT); +} + +// Get the High Navigation Rate +// Returns 0 if the getHNRNavigationRate fails +uint8_t SFE_UBLOX_GPS::getHNRNavigationRate(uint16_t maxWait) +{ + packetCfg.cls = UBX_CLASS_CFG; + packetCfg.id = UBX_CFG_HNR; + packetCfg.len = 0; + packetCfg.startingSpot = 0; + + //Ask module for the current HNR settings. Loads into payloadCfg. + if (sendCommand(&packetCfg, maxWait) != SFE_UBLOX_STATUS_DATA_RECEIVED) + return (0); + + //Return the navigation rate + return (payloadCfg[0]); +} + +float SFE_UBLOX_GPS::getHNRroll(uint16_t maxWait) // Returned as degrees +{ + if (packetUBXHNRATT == NULL) initPacketUBXHNRATT(); //Check that RAM has been allocated for the HNR ATT data + if (packetUBXHNRATT == NULL) //Bail if the RAM allocation failed + return (0); + + if (packetUBXHNRATT->moduleQueried.moduleQueried.bits.roll == false) + getHNRATT(maxWait); + packetUBXHNRATT->moduleQueried.moduleQueried.bits.roll = false; //Since we are about to give this to user, mark this data as stale + packetUBXHNRATT->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXHNRATT->data.roll) / 100000.0); // Convert to degrees +} + +float SFE_UBLOX_GPS::getHNRpitch(uint16_t maxWait) // Returned as degrees +{ + if (packetUBXHNRATT == NULL) initPacketUBXHNRATT(); //Check that RAM has been allocated for the HNR ATT data + if (packetUBXHNRATT == NULL) //Bail if the RAM allocation failed + return (0); + + if (packetUBXHNRATT->moduleQueried.moduleQueried.bits.pitch == false) + getHNRATT(maxWait); + packetUBXHNRATT->moduleQueried.moduleQueried.bits.pitch = false; //Since we are about to give this to user, mark this data as stale + packetUBXHNRATT->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXHNRATT->data.pitch) / 100000.0); // Convert to degrees +} + +float SFE_UBLOX_GPS::getHNRheading(uint16_t maxWait) // Returned as degrees +{ + if (packetUBXHNRATT == NULL) initPacketUBXHNRATT(); //Check that RAM has been allocated for the HNR ATT data + if (packetUBXHNRATT == NULL) //Bail if the RAM allocation failed + return (0); + + if (packetUBXHNRATT->moduleQueried.moduleQueried.bits.heading == false) + getHNRATT(maxWait); + packetUBXHNRATT->moduleQueried.moduleQueried.bits.heading = false; //Since we are about to give this to user, mark this data as stale + packetUBXHNRATT->moduleQueried.moduleQueried.bits.all = false; + return (((float)packetUBXHNRATT->data.heading) / 100000.0); // Convert to degrees +} + +// 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 + +//Given a spot in the payload array, extract four bytes and build a long +uint32_t SFE_UBLOX_GPS::extractLong(ubxPacket *msg, uint8_t spotToStart) +{ + uint32_t val = 0; + val |= (uint32_t)msg->payload[spotToStart + 0] << 8 * 0; + val |= (uint32_t)msg->payload[spotToStart + 1] << 8 * 1; + val |= (uint32_t)msg->payload[spotToStart + 2] << 8 * 2; + val |= (uint32_t)msg->payload[spotToStart + 3] << 8 * 3; + return (val); +} + +//Just so there is no ambiguity about whether a uint32_t will cast to a int32_t correctly... +int32_t SFE_UBLOX_GPS::extractSignedLong(ubxPacket *msg, uint8_t spotToStart) +{ + union // Use a union to convert from uint32_t to int32_t + { + uint32_t unsignedLong; + int32_t signedLong; + } unsignedSigned; + + unsignedSigned.unsignedLong = extractLong(msg, spotToStart); + return (unsignedSigned.signedLong); +} + +//Given a spot in the payload array, extract two bytes and build an int +uint16_t SFE_UBLOX_GPS::extractInt(ubxPacket *msg, uint8_t spotToStart) +{ + uint16_t val = 0; + val |= (uint16_t)msg->payload[spotToStart + 0] << 8 * 0; + val |= (uint16_t)msg->payload[spotToStart + 1] << 8 * 1; + return (val); +} + +//Just so there is no ambiguity about whether a uint16_t will cast to a int16_t correctly... +int16_t SFE_UBLOX_GPS::extractSignedInt(ubxPacket *msg, int8_t spotToStart) +{ + union // Use a union to convert from uint16_t to int16_t + { + uint16_t unsignedInt; + int16_t signedInt; + } stSignedInt; + + stSignedInt.unsignedInt = extractInt(msg, spotToStart); + return (stSignedInt.signedInt); +} + +//Given a spot, extract a byte from the payload +uint8_t SFE_UBLOX_GPS::extractByte(ubxPacket *msg, uint8_t spotToStart) +{ + return (msg->payload[spotToStart]); +} + +//Given a spot, extract a signed 8-bit value from the payload +int8_t SFE_UBLOX_GPS::extractSignedChar(ubxPacket *msg, uint8_t spotToStart) +{ + union // Use a union to convert from uint8_t to int8_t + { + uint8_t unsignedByte; + int8_t signedByte; + } stSignedByte; + + stSignedByte.unsignedByte = extractByte(msg, spotToStart); + return (stSignedByte.signedByte); +} diff --git a/src/SparkFun_Ublox_Arduino_Library.h b/src/SparkFun_Ublox_Arduino_Library.h new file mode 100644 index 0000000..862c2d5 --- /dev/null +++ b/src/SparkFun_Ublox_Arduino_Library.h @@ -0,0 +1,1192 @@ +/* + This is a library written for the u-blox ZED-F9P and NEO-M8P-2 + SparkFun sells these at its website: www.sparkfun.com + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/16481 + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Original version by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + v2.0 rework by Paul Clark @ SparkFun Electronics, December 31st, 2020 + + This library handles configuring and handling the responses + from a u-blox GPS module. Works with most modules from u-blox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + Arduino IDE 1.8.13 + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SPARKFUN_UBLOX_ARDUINO_LIBRARY_H +#define SPARKFUN_UBLOX_ARDUINO_LIBRARY_H + +#if (ARDUINO >= 100) +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#include + +#include "u-blox_config_keys.h" +#include "u-blox_structs.h" + +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +//Define a digital pin to aid debugging +//Leave set to -1 if not needed +const int debugPin = -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_NACK, // Indicates that the command was unrecognised, invalid or that the module is too busy to respond + 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, // This indicates that a 'set' was successful + SFE_UBLOX_STATUS_DATA_RECEIVED, // This indicates that a 'get' (poll) was successful + SFE_UBLOX_STATUS_I2C_COMM_FAILURE, + SFE_UBLOX_STATUS_DATA_OVERWRITTEN // This is an error - the data was valid but has been or _is being_ overwritten by another packet +} sfe_ublox_status_e; + +// ubxPacket validity +typedef enum +{ + SFE_UBLOX_PACKET_VALIDITY_NOT_VALID, + SFE_UBLOX_PACKET_VALIDITY_VALID, + SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, + SFE_UBLOX_PACKET_NOTACKNOWLEDGED // This indicates that we received a NACK +} sfe_ublox_packet_validity_e; + +// Identify which packet buffer is in use: +// packetCfg (or a custom packet), packetAck or packetBuf +// packetAuto is used to store expected "automatic" messages +typedef enum +{ + SFE_UBLOX_PACKET_PACKETCFG, + SFE_UBLOX_PACKET_PACKETACK, + SFE_UBLOX_PACKET_PACKETBUF, + SFE_UBLOX_PACKET_PACKETAUTO +} sfe_ublox_packet_buffer_e; + +//Registers +const uint8_t UBX_SYNCH_1 = 0xB5; +const uint8_t UBX_SYNCH_2 = 0x62; + +//The following are UBX Class IDs. Descriptions taken from ZED-F9P Interface Description Document page 32, NEO-M8P Interface Description page 145 +const uint8_t UBX_CLASS_NAV = 0x01; //Navigation Results Messages: Position, Speed, Time, Acceleration, Heading, DOP, SVs used +const uint8_t UBX_CLASS_RXM = 0x02; //Receiver Manager Messages: Satellite Status, RTC Status +const uint8_t UBX_CLASS_INF = 0x04; //Information Messages: Printf-Style Messages, with IDs such as Error, Warning, Notice +const uint8_t UBX_CLASS_ACK = 0x05; //Ack/Nak Messages: Acknowledge or Reject messages to UBX-CFG input messages +const uint8_t UBX_CLASS_CFG = 0x06; //Configuration Input Messages: Configure the receiver. +const uint8_t UBX_CLASS_UPD = 0x09; //Firmware Update Messages: Memory/Flash erase/write, Reboot, Flash identification, etc. +const uint8_t UBX_CLASS_MON = 0x0A; //Monitoring Messages: Communication Status, CPU Load, Stack Usage, Task Status +const uint8_t UBX_CLASS_AID = 0x0B; //(NEO-M8P ONLY!!!) AssistNow Aiding Messages: Ephemeris, Almanac, other A-GPS data input +const uint8_t UBX_CLASS_TIM = 0x0D; //Timing Messages: Time Pulse Output, Time Mark Results +const uint8_t UBX_CLASS_ESF = 0x10; //(NEO-M8P ONLY!!!) External Sensor Fusion Messages: External Sensor Measurements and Status Information +const uint8_t UBX_CLASS_MGA = 0x13; //Multiple GNSS Assistance Messages: Assistance data for various GNSS +const uint8_t UBX_CLASS_LOG = 0x21; //Logging Messages: Log creation, deletion, info and retrieval +const uint8_t UBX_CLASS_SEC = 0x27; //Security Feature Messages +const uint8_t UBX_CLASS_HNR = 0x28; //(NEO-M8P ONLY!!!) High Rate Navigation Results Messages: High rate time, position speed, heading +const uint8_t UBX_CLASS_NMEA = 0xF0; //NMEA Strings: standard NMEA strings + +//Class: CFG +//The following are used for configuration. Descriptions are from the ZED-F9P Interface Description pg 33-34 and NEO-M9N Interface Description pg 47-48 +const uint8_t UBX_CFG_ANT = 0x13; //Antenna Control Settings. Used to configure the antenna control settings +const uint8_t UBX_CFG_BATCH = 0x93; //Get/set data batching configuration. +const uint8_t UBX_CFG_CFG = 0x09; //Clear, Save, and Load Configurations. Used to save current configuration +const uint8_t UBX_CFG_DAT = 0x06; //Set User-defined Datum or The currently defined Datum +const uint8_t UBX_CFG_DGNSS = 0x70; //DGNSS configuration +const uint8_t UBX_CFG_ESFALG = 0x56; //ESF alignment +const uint8_t UBX_CFG_ESFA = 0x4C; //ESF accelerometer +const uint8_t UBX_CFG_ESFG = 0x4D; //ESF gyro +const uint8_t UBX_CFG_GEOFENCE = 0x69; //Geofencing configuration. Used to configure a geofence +const uint8_t UBX_CFG_GNSS = 0x3E; //GNSS system configuration +const uint8_t UBX_CFG_HNR = 0x5C; //High Navigation Rate +const uint8_t UBX_CFG_INF = 0x02; //Depending on packet length, either: poll configuration for one protocol, or information message configuration +const uint8_t UBX_CFG_ITFM = 0x39; //Jamming/Interference Monitor configuration +const uint8_t UBX_CFG_LOGFILTER = 0x47; //Data Logger Configuration +const uint8_t UBX_CFG_MSG = 0x01; //Poll a message configuration, or Set Message Rate(s), or Set Message Rate +const uint8_t UBX_CFG_NAV5 = 0x24; //Navigation Engine Settings. Used to configure the navigation engine including the dynamic model. +const uint8_t UBX_CFG_NAVX5 = 0x23; //Navigation Engine Expert Settings +const uint8_t UBX_CFG_NMEA = 0x17; //Extended NMEA protocol configuration V1 +const uint8_t UBX_CFG_ODO = 0x1E; //Odometer, Low-speed COG Engine Settings +const uint8_t UBX_CFG_PM2 = 0x3B; //Extended power management configuration +const uint8_t UBX_CFG_PMS = 0x86; //Power mode setup +const uint8_t UBX_CFG_PRT = 0x00; //Used to configure port specifics. Polls the configuration for one I/O Port, or Port configuration for UART ports, or Port configuration for USB port, or Port configuration for SPI port, or Port configuration for DDC port +const uint8_t UBX_CFG_PWR = 0x57; //Put receiver in a defined power state +const uint8_t UBX_CFG_RATE = 0x08; //Navigation/Measurement Rate Settings. Used to set port baud rates. +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_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 +const uint8_t UBX_CFG_VALDEL = 0x8C; //Used for config of higher version u-blox modules (ie protocol v27 and above). Deletes values corresponding to provided keys/ provided keys with a transaction +const uint8_t UBX_CFG_VALGET = 0x8B; //Used for config of higher version u-blox modules (ie protocol v27 and above). Configuration Items +const uint8_t UBX_CFG_VALSET = 0x8A; //Used for config of higher version u-blox modules (ie protocol v27 and above). Sets values corresponding to provided key-value pairs/ provided key-value pairs within a transaction. + +//Class: NMEA +//The following are used to enable NMEA messages. Descriptions come from the NMEA messages overview in the ZED-F9P Interface Description +const uint8_t UBX_NMEA_MSB = 0xF0; //All NMEA enable commands have 0xF0 as MSB +const uint8_t UBX_NMEA_DTM = 0x0A; //GxDTM (datum reference) +const uint8_t UBX_NMEA_GAQ = 0x45; //GxGAQ (poll a standard message (if the current talker ID is GA)) +const uint8_t UBX_NMEA_GBQ = 0x44; //GxGBQ (poll a standard message (if the current Talker ID is GB)) +const uint8_t UBX_NMEA_GBS = 0x09; //GxGBS (GNSS satellite fault detection) +const uint8_t UBX_NMEA_GGA = 0x00; //GxGGA (Global positioning system fix data) +const uint8_t UBX_NMEA_GLL = 0x01; //GxGLL (latitude and long, whith time of position fix and status) +const uint8_t UBX_NMEA_GLQ = 0x43; //GxGLQ (poll a standard message (if the current Talker ID is GL)) +const uint8_t UBX_NMEA_GNQ = 0x42; //GxGNQ (poll a standard message (if the current Talker ID is GN)) +const uint8_t UBX_NMEA_GNS = 0x0D; //GxGNS (GNSS fix data) +const uint8_t UBX_NMEA_GPQ = 0x040; //GxGPQ (poll a standard message (if the current Talker ID is GP)) +const uint8_t UBX_NMEA_GRS = 0x06; //GxGRS (GNSS range residuals) +const uint8_t UBX_NMEA_GSA = 0x02; //GxGSA (GNSS DOP and Active satellites) +const uint8_t UBX_NMEA_GST = 0x07; //GxGST (GNSS Pseudo Range Error Statistics) +const uint8_t UBX_NMEA_GSV = 0x03; //GxGSV (GNSS satellites in view) +const uint8_t UBX_NMEA_RMC = 0x04; //GxRMC (Recommended minimum data) +const uint8_t UBX_NMEA_TXT = 0x41; //GxTXT (text transmission) +const uint8_t UBX_NMEA_VLW = 0x0F; //GxVLW (dual ground/water distance) +const uint8_t UBX_NMEA_VTG = 0x05; //GxVTG (course over ground and Ground speed) +const uint8_t UBX_NMEA_ZDA = 0x08; //GxZDA (Time and Date) + +//The following are used to configure the NMEA protocol main talker ID and GSV talker ID +const uint8_t UBX_NMEA_MAINTALKERID_NOTOVERRIDDEN = 0x00; //main talker ID is system dependent +const uint8_t UBX_NMEA_MAINTALKERID_GP = 0x01; //main talker ID is GPS +const uint8_t UBX_NMEA_MAINTALKERID_GL = 0x02; //main talker ID is GLONASS +const uint8_t UBX_NMEA_MAINTALKERID_GN = 0x03; //main talker ID is combined receiver +const uint8_t UBX_NMEA_MAINTALKERID_GA = 0x04; //main talker ID is Galileo +const uint8_t UBX_NMEA_MAINTALKERID_GB = 0x05; //main talker ID is BeiDou +const uint8_t UBX_NMEA_GSVTALKERID_GNSS = 0x00; //GNSS specific Talker ID (as defined by NMEA) +const uint8_t UBX_NMEA_GSVTALKERID_MAIN = 0x01; //use the main Talker ID + +//Class: HNR +//The following are used to configure the HNR message rates +const uint8_t UBX_HNR_ATT = 0x01; //HNR Attitude +const uint8_t UBX_HNR_INS = 0x02; //HNR Vehicle Dynamics +const uint8_t UBX_HNR_PVT = 0x00; //HNR PVT + +//Class: INF +//The following are used to configure INF UBX messages (information messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_INF_CLASS = 0x04; //All INF messages have 0x04 as the class +const uint8_t UBX_INF_DEBUG = 0x04; //ASCII output with debug contents +const uint8_t UBX_INF_ERROR = 0x00; //ASCII output with error contents +const uint8_t UBX_INF_NOTICE = 0x02; //ASCII output with informational contents +const uint8_t UBX_INF_TEST = 0x03; //ASCII output with test contents +const uint8_t UBX_INF_WARNING = 0x01; //ASCII output with warning contents + +//Class: LOG +//The following are used to configure LOG UBX messages (loggings messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_LOG_CREATE = 0x07; //Create Log File +const uint8_t UBX_LOG_ERASE = 0x03; //Erase Logged Data +const uint8_t UBX_LOG_FINDTIME = 0x0E; //Find index of a log entry based on a given time, or response to FINDTIME requested +const uint8_t UBX_LOG_INFO = 0x08; //Poll for log information, or Log information +const uint8_t UBX_LOG_RETRIEVEPOSEXTRA = 0x0F; //Odometer log entry +const uint8_t UBX_LOG_RETRIEVEPOS = 0x0B; //Position fix log entry +const uint8_t UBX_LOG_RETRIEVESTRING = 0x0D; //Byte string log entry +const uint8_t UBX_LOG_RETRIEVE = 0x09; //Request log data +const uint8_t UBX_LOG_STRING = 0x04; //Store arbitrary string on on-board flash + +//Class: MGA +//The following are used to configure MGA UBX messages (Multiple GNSS Assistance Messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 34) +const uint8_t UBX_MGA_ACK_DATA0 = 0x60; //Multiple GNSS Acknowledge message +const uint8_t UBX_MGA_BDS_EPH = 0x03; //BDS Ephemeris Assistance +const uint8_t UBX_MGA_BDS_ALM = 0x03; //BDS Almanac Assistance +const uint8_t UBX_MGA_BDS_HEALTH = 0x03; //BDS Health Assistance +const uint8_t UBX_MGA_BDS_UTC = 0x03; //BDS UTC Assistance +const uint8_t UBX_MGA_BDS_IONO = 0x03; //BDS Ionospheric Assistance +const uint8_t UBX_MGA_DBD = 0x80; //Either: Poll the Navigation Database, or Navigation Database Dump Entry +const uint8_t UBX_MGA_GAL_EPH = 0x02; //Galileo Ephemeris Assistance +const uint8_t UBX_MGA_GAL_ALM = 0x02; //Galileo Almanac Assitance +const uint8_t UBX_MGA_GAL_TIMOFFSET = 0x02; //Galileo GPS time offset assistance +const uint8_t UBX_MGA_GAL_UTC = 0x02; //Galileo UTC Assistance +const uint8_t UBX_MGA_GLO_EPH = 0x06; //GLONASS Ephemeris Assistance +const uint8_t UBX_MGA_GLO_ALM = 0x06; //GLONASS Almanac Assistance +const uint8_t UBX_MGA_GLO_TIMEOFFSET = 0x06; //GLONASS Auxiliary Time Offset Assistance +const uint8_t UBX_MGA_GPS_EPH = 0x00; //GPS Ephemeris Assistance +const uint8_t UBX_MGA_GPS_ALM = 0x00; //GPS Almanac Assistance +const uint8_t UBX_MGA_GPS_HEALTH = 0x00; //GPS Health Assistance +const uint8_t UBX_MGA_GPS_UTC = 0x00; //GPS UTC Assistance +const uint8_t UBX_MGA_GPS_IONO = 0x00; //GPS Ionosphere Assistance +const uint8_t UBX_MGA_INI_POS_XYZ = 0x40; //Initial Position Assistance +const uint8_t UBX_MGA_INI_POS_LLH = 0x40; //Initial Position Assitance +const uint8_t UBX_MGA_INI_TIME_UTC = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_TIME_GNSS = 0x40; //Initial Time Assistance +const uint8_t UBX_MGA_INI_CLKD = 0x40; //Initial Clock Drift Assitance +const uint8_t UBX_MGA_INI_FREQ = 0x40; //Initial Frequency Assistance +const uint8_t UBX_MGA_INI_EOP = 0x40; //Earth Orientation Parameters Assistance +const uint8_t UBX_MGA_QZSS_EPH = 0x05; //QZSS Ephemeris Assistance +const uint8_t UBX_MGA_QZSS_ALM = 0x05; //QZSS Almanac Assistance +const uint8_t UBX_MGA_QZAA_HEALTH = 0x05; //QZSS Health Assistance + +//Class: MON +//The following are used to configure the MON UBX messages (monitoring messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35) +const uint8_t UBX_MON_COMMS = 0x36; //Comm port information +const uint8_t UBX_MON_GNSS = 0x28; //Information message major GNSS selection +const uint8_t UBX_MON_HW2 = 0x0B; //Extended Hardware Status +const uint8_t UBX_MON_HW3 = 0x37; //HW I/O pin information +const uint8_t UBX_MON_HW = 0x09; //Hardware Status +const uint8_t UBX_MON_IO = 0x02; //I/O Subsystem Status +const uint8_t UBX_MON_MSGPP = 0x06; //Message Parse and Process Status +const uint8_t UBX_MON_PATCH = 0x27; //Output information about installed patches +const uint8_t UBX_MON_RF = 0x38; //RF information +const uint8_t UBX_MON_RXBUF = 0x07; //Receiver Buffer Status +const uint8_t UBX_MON_RXR = 0x21; //Receiver Status Information +const uint8_t UBX_MON_TXBUF = 0x08; //Transmitter Buffer Status. Used for query tx buffer size/state. +const uint8_t UBX_MON_VER = 0x04; //Receiver/Software Version. Used for obtaining Protocol Version. + +//Class: NAV +//The following are used to configure the NAV UBX messages (navigation results messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 35-36) +const uint8_t UBX_NAV_ATT = 0x05; //Vehicle "Attitude" Solution +const uint8_t UBX_NAV_CLOCK = 0x22; //Clock Solution +const uint8_t UBX_NAV_DOP = 0x04; //Dilution of precision +const uint8_t UBX_NAV_EOE = 0x61; //End of Epoch +const uint8_t UBX_NAV_GEOFENCE = 0x39; //Geofencing status. Used to poll the geofence status +const uint8_t UBX_NAV_HPPOSECEF = 0x13; //High Precision Position Solution in ECEF. Used to find our positional accuracy (high precision). +const uint8_t UBX_NAV_HPPOSLLH = 0x14; //High Precision Geodetic Position Solution. Used for obtaining lat/long/alt in high precision +const uint8_t UBX_NAV_ODO = 0x09; //Odometer Solution +const uint8_t UBX_NAV_ORB = 0x34; //GNSS Orbit Database Info +const uint8_t UBX_NAV_POSECEF = 0x01; //Position Solution in ECEF +const uint8_t UBX_NAV_POSLLH = 0x02; //Geodetic Position Solution +const uint8_t UBX_NAV_PVT = 0x07; //All the things! Position, velocity, time, PDOP, height, h/v accuracies, number of satellites. Navigation Position Velocity Time Solution. +const uint8_t UBX_NAV_RELPOSNED = 0x3C; //Relative Positioning Information in NED frame +const uint8_t UBX_NAV_RESETODO = 0x10; //Reset odometer +const uint8_t UBX_NAV_SAT = 0x35; //Satellite Information +const uint8_t UBX_NAV_SIG = 0x43; //Signal Information +const uint8_t UBX_NAV_STATUS = 0x03; //Receiver Navigation Status +const uint8_t UBX_NAV_SVIN = 0x3B; //Survey-in data. Used for checking Survey In status +const uint8_t UBX_NAV_TIMEBDS = 0x24; //BDS Time Solution +const uint8_t UBX_NAV_TIMEGAL = 0x25; //Galileo Time Solution +const uint8_t UBX_NAV_TIMEGLO = 0x23; //GLO Time Solution +const uint8_t UBX_NAV_TIMEGPS = 0x20; //GPS Time Solution +const uint8_t UBX_NAV_TIMELS = 0x26; //Leap second event information +const uint8_t UBX_NAV_TIMEUTC = 0x21; //UTC Time Solution +const uint8_t UBX_NAV_VELECEF = 0x11; //Velocity Solution in ECEF +const uint8_t UBX_NAV_VELNED = 0x12; //Velocity Solution in NED + +//Class: RXM +//The following are used to configure the RXM UBX messages (receiver manager messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +const uint8_t UBX_RXM_MEASX = 0x14; //Satellite Measurements for RRLP +const uint8_t UBX_RXM_PMREQ = 0x41; //Requests a Power Management task (two differenent packet sizes) +const uint8_t UBX_RXM_RAWX = 0x15; //Multi-GNSS Raw Measurement Data +const uint8_t UBX_RXM_RLM = 0x59; //Galileo SAR Short-RLM report (two different packet sizes) +const uint8_t UBX_RXM_RTCM = 0x32; //RTCM input status +const uint8_t UBX_RXM_SFRBX = 0x13; //Boradcast Navigation Data Subframe + +//Class: SEC +//The following are used to configure the SEC UBX messages (security feature messages). Descriptions from UBX messages overview (ZED_F9P Interface Description Document page 36) +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 + +//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) +const uint8_t UBX_UPD_SOS = 0x14; //Poll Backup Fil Restore Status, Create Backup File in Flash, Clear Backup File in Flash, Backup File Creation Acknowledge, System Restored from Backup + +//The following are used to enable RTCM messages +const uint8_t UBX_RTCM_MSB = 0xF5; //All RTCM enable commands have 0xF5 as MSB +const uint8_t UBX_RTCM_1005 = 0x05; //Stationary RTK reference ARP +const uint8_t UBX_RTCM_1074 = 0x4A; //GPS MSM4 +const uint8_t UBX_RTCM_1077 = 0x4D; //GPS MSM7 +const uint8_t UBX_RTCM_1084 = 0x54; //GLONASS MSM4 +const uint8_t UBX_RTCM_1087 = 0x57; //GLONASS MSM7 +const uint8_t UBX_RTCM_1094 = 0x5E; //Galileo MSM4 +const uint8_t UBX_RTCM_1097 = 0x61; //Galileo MSM7 +const uint8_t UBX_RTCM_1124 = 0x7C; //BeiDou MSM4 +const uint8_t UBX_RTCM_1127 = 0x7F; //BeiDou MSM7 +const uint8_t UBX_RTCM_1230 = 0xE6; //GLONASS code-phase biases, set to once every 10 seconds +const uint8_t UBX_RTCM_4072_0 = 0xFE; //Reference station PVT (ublox proprietary RTCM message) +const uint8_t UBX_RTCM_4072_1 = 0xFD; //Additional reference station information (ublox proprietary RTCM message) + +// Class: ACK +const uint8_t UBX_ACK_NACK = 0x00; +const uint8_t UBX_ACK_ACK = 0x01; +const uint8_t UBX_ACK_NONE = 0x02; //Not a real value + +//Class: ESF +// The following constants are used to get External Sensor Measurements and Status +// Information. +const uint8_t UBX_ESF_MEAS = 0x02; +const uint8_t UBX_ESF_RAW = 0x03; +const uint8_t UBX_ESF_STATUS = 0x10; +const uint8_t UBX_ESF_RESETALG = 0x13; +const uint8_t UBX_ESF_ALG = 0x14; +const uint8_t UBX_ESF_INS = 0x15; //36 bytes + +const uint8_t SVIN_MODE_DISABLE = 0x00; +const uint8_t SVIN_MODE_ENABLE = 0x01; + +//The following consts are used to configure the various ports and streams for those ports. See -CFG-PRT. +const uint8_t COM_PORT_I2C = 0; +const uint8_t COM_PORT_UART1 = 1; +const uint8_t COM_PORT_UART2 = 2; +const uint8_t COM_PORT_USB = 3; +const uint8_t COM_PORT_SPI = 4; + +const uint8_t COM_TYPE_UBX = (1 << 0); +const uint8_t COM_TYPE_NMEA = (1 << 1); +const uint8_t COM_TYPE_RTCM3 = (1 << 5); + +// Configuration Sub-Section mask definitions for saveConfigSelective (UBX-CFG-CFG) +const uint32_t VAL_CFG_SUBSEC_IOPORT = 0x00000001; // ioPort - communications port settings (causes IO system reset!) +const uint32_t VAL_CFG_SUBSEC_MSGCONF = 0x00000002; // msgConf - message configuration +const uint32_t VAL_CFG_SUBSEC_INFMSG = 0x00000004; // infMsg - INF message configuration +const uint32_t VAL_CFG_SUBSEC_NAVCONF = 0x00000008; // navConf - navigation configuration +const uint32_t VAL_CFG_SUBSEC_RXMCONF = 0x00000010; // rxmConf - receiver manager configuration +const uint32_t VAL_CFG_SUBSEC_SENCONF = 0x00000100; // senConf - sensor interface configuration (requires protocol 19+) +const uint32_t VAL_CFG_SUBSEC_RINVCONF = 0x00000200; // rinvConf - remove inventory configuration +const uint32_t VAL_CFG_SUBSEC_ANTCONF = 0x00000400; // antConf - antenna configuration +const uint32_t VAL_CFG_SUBSEC_LOGCONF = 0x00000800; // logConf - logging configuration +const uint32_t VAL_CFG_SUBSEC_FTSCONF = 0x00001000; // ftsConf - FTS configuration (FTS products only) + +// Bitfield wakeupSources for UBX_RXM_PMREQ +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_UARTRX = 0x00000008; // uartrx +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 = 0x00000020; // extint0 +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT1 = 0x00000040; // extint1 +const uint32_t VAL_RXM_PMREQ_WAKEUPSOURCE_SPICS = 0x00000080; // spics + +enum dynModel // Possible values for the dynamic platform model, which provide more accuract position output for the situation. Description extracted from ZED-F9P Integration Manual +{ + DYN_MODEL_PORTABLE = 0, //Applications with low acceleration, e.g. portable devices. Suitable for most situations. + // 1 is not defined + DYN_MODEL_STATIONARY = 2, //Used in timing applications (antenna must be stationary) or other stationary applications. Velocity restricted to 0 m/s. Zero dynamics assumed. + DYN_MODEL_PEDESTRIAN, //Applications with low acceleration and speed, e.g. how a pedestrian would move. Low acceleration assumed. + DYN_MODEL_AUTOMOTIVE, //Used for applications with equivalent dynamics to those of a passenger car. Low vertical acceleration assumed + DYN_MODEL_SEA, //Recommended for applications at sea, with zero vertical velocity. Zero vertical velocity assumed. Sea level assumed. + DYN_MODEL_AIRBORNE1g, //Airborne <1g acceleration. Used for applications with a higher dynamic range and greater vertical acceleration than a passenger car. No 2D position fixes supported. + DYN_MODEL_AIRBORNE2g, //Airborne <2g acceleration. Recommended for typical airborne environments. No 2D position fixes supported. + DYN_MODEL_AIRBORNE4g, //Airborne <4g acceleration. Only recommended for extremely dynamic environments. No 2D position fixes supported. + DYN_MODEL_WRIST, // Not supported in protocol versions less than 18. Only recommended for wrist worn applications. Receiver will filter out arm motion. + DYN_MODEL_BIKE, // Supported in protocol versions 19.2 + DYN_MODEL_UNKNOWN = 255 // getDynamicModel will return 255 if sendCommand fails +}; + +// The GNSS identifiers - used by UBX-CFG-GNSS (0x06 0x3E) GNSS system configuration +enum sfe_ublox_gnss_ids_e +{ + SFE_UBLOX_GNSS_ID_GPS, + SFE_UBLOX_GNSS_ID_SBAS, + SFE_UBLOX_GNSS_ID_GALILEO, + SFE_UBLOX_GNSS_ID_BEIDOU, + SFE_UBLOX_GNSS_ID_IMES, + SFE_UBLOX_GNSS_ID_QZSS, + SFE_UBLOX_GNSS_ID_GLONASS +}; + +#ifndef MAX_PAYLOAD_SIZE +// v2.0: keep this for backwards-compatibility, but this is largely superseded by setPacketCfgPayloadSize +#define MAX_PAYLOAD_SIZE 256 //We need ~220 bytes for getProtocolVersion on most ublox modules +//#define MAX_PAYLOAD_SIZE 768 //Worst case: UBX_CFG_VALSET packet with 64 keyIDs each with 64 bit values +#endif + +//-=-=-=-=- UBX binary specific variables +struct ubxPacket +{ + uint8_t cls; + uint8_t 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; // We will allocate RAM for the payload if/when needed. + uint8_t checksumA; //Given to us from 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 +}; + +// Struct to hold the results returned by getGeofenceState (returned by UBX-NAV-GEOFENCE) +typedef struct +{ + uint8_t status; // Geofencing status: 0 - Geofencing not available or not reliable; 1 - Geofencing active + uint8_t numFences; // Number of geofences + uint8_t combState; // Combined (logical OR) state of all geofences: 0 - Unknown; 1 - Inside; 2 - Outside + uint8_t states[4]; // Geofence states: 0 - Unknown; 1 - Inside; 2 - Outside +} geofenceState; + +// Struct to hold the current geofence parameters +typedef struct +{ + uint8_t numFences; // Number of active geofences + int32_t lats[4]; // Latitudes of geofences (in degrees * 10^-7) + int32_t longs[4]; // Longitudes of geofences (in degrees * 10^-7) + uint32_t rads[4]; // Radii of geofences (in m * 10^-2) +} geofenceParams_t; + +// Struct to hold the module software version +typedef struct +{ + uint8_t versionLow; //Loaded from getProtocolVersion(). + uint8_t versionHigh; + bool moduleQueried; +} moduleSWVersion_t; + +class SFE_UBLOX_GPS +{ +public: + SFE_UBLOX_GPS(void); + +// A default of 250ms for maxWait seems fine for I2C but is not enough for SerialUSB. +// If you know you are only going to be using I2C / Qwiic communication, you can +// safely reduce defaultMaxWait to 250. +#ifndef defaultMaxWait // Let's allow the user to define their own value if they want to +#define defaultMaxWait 1100 +#endif + + //New in v2.0: allow the payload size for packetCfg to be changed + void setPacketCfgPayloadSize(size_t payloadSize); // Set packetCfgPayloadSize + + //By default use the default I2C address, and use Wire port + boolean begin(TwoWire &wirePort = Wire, uint8_t deviceAddress = 0x42); //Returns true if module is detected + //serialPort needs to be perviously initialized to correct baud rate + boolean begin(Stream &serialPort); //Returns true if module is detected + + void setI2CpollingWait(uint8_t newPollingWait_ms); // Allow the user to change the I2C polling wait if required + + //Control the size of the internal I2C transaction amount + void setI2CTransactionSize(uint8_t bufferSize); + uint8_t getI2CTransactionSize(void); + + //Set the max number of bytes set in a given I2C transaction + uint8_t i2cTransactionSize = 32; //Default to ATmega328 limit + + //Returns true if device answers on _gpsI2Caddress address or via Serial + boolean isConnected(uint16_t maxWait = 1100); + + // Enable debug messages using the chosen Serial port (Stream) + // 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 + void enableDebugging(Stream &debugPort = SerialUSB, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + #else + void enableDebugging(Stream &debugPort = Serial, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + #endif + #else + void enableDebugging(Stream &debugPort = Serial, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + #endif + #else + void enableDebugging(Stream &debugPort = Serial, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + #endif + #else + void enableDebugging(Stream &debugPort = Serial, boolean printLimitedDebug = false); //Given a port to print to, enable debug messages. Default to all, not limited. + #endif + + 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 + + // Check for the arrival of new I2C/Serial data + + void disableUBX7Fcheck(boolean disabled = true); // When logging RAWX data, we need to be able to disable the "7F" check in checkUbloxI2C + + //Changed in V1.8.1: provides backward compatibility for the examples that call checkUblox directly + //Will default to using packetCfg to look for explicit autoPVT packets so they get processed correctly by processUBX + boolean checkUblox(uint8_t requestedClass = 0, uint8_t requestedID = 0); //Checks module with user selected commType + + boolean checkUbloxI2C(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for I2C polling of data, passing any new bytes to process() + boolean checkUbloxSerial(ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Method for serial polling of data, passing any new bytes to process() + + // Process the incoming data + + void process(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Processes NMEA and UBX binary sentences one byte at a time + 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 processRTCMframe(uint8_t incoming); //Monitor the incoming bytes for start and length bytes + void processRTCM(uint8_t incoming) __attribute__((weak)); //Given rtcm byte, do something with it. User can overwrite if desired to pipe bytes to radio, internet, etc. + void processUBX(uint8_t incoming, ubxPacket *incomingUBX, uint8_t requestedClass, uint8_t requestedID); //Given a character, file it away into the uxb packet structure + void processUBXpacket(ubxPacket *msg); //Once a packet has been received and validated, identify this packet's class/id and update internal flags + + // Send I2C/Serial commands to the module + + void calcChecksum(ubxPacket *msg); //Sets the checksumA and checksumB of a given messages + sfe_ublox_status_e sendCommand(ubxPacket *outgoingUBX, uint16_t maxWait = defaultMaxWait, boolean expectACKonly = false); //Given a packet and payload, send everything including CRC bytes, return true if we got a response + sfe_ublox_status_e sendI2cCommand(ubxPacket *outgoingUBX, uint16_t maxWait = defaultMaxWait); + void sendSerialCommand(ubxPacket *outgoingUBX); + + void printPacket(ubxPacket *packet, boolean alwaysPrintPayload = false); //Useful for debugging + + // After sending a message to the module, wait for the expected response (data+ACK or just data) + + sfe_ublox_status_e waitForACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //Poll the module until a config packet and an ACK is received, or just an ACK + sfe_ublox_status_e waitForNoACKResponse(ubxPacket *outgoingUBX, uint8_t requestedClass, uint8_t requestedID, uint16_t maxTime = defaultMaxWait); //Poll the module until a config packet is received + + // Check if any callbacks need to be called + void checkCallbacks(void); + + // Push (e.g.) RTCM data directly to the module + // Warning: this function does not check that the data is valid. It is the user's responsibility to ensure the data is valid before pushing. + boolean pushRawData(uint8_t *dataBytes, size_t numDataBytes); + + // Support for data logging + void setFileBufferSize(uint16_t bufferSize); // Set the size of the file buffer. This must be called _before_ .begin. + uint16_t extractFileBufferData(uint8_t *destination, uint16_t numBytes); // Extract numBytes of data from the file buffer. Copy it to destination. It is the user's responsibility to ensure destination is large enough. + uint16_t fileBufferAvailable(void); // Returns the number of bytes available in file buffer which are waiting to be read + uint16_t getMaxFileBufferAvail(void); // Returns the maximum number of bytes which the file buffer has contained. Handy for checking the buffer is large enough to handle all the incoming data. + + // Specific commands + + //Port configurations + boolean getPortSettings(uint8_t portID, uint16_t maxWait = defaultMaxWait); //Returns the current protocol bits in the UBX-CFG-PRT command for a given port + boolean setPortOutput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setPortInput(uint8_t portID, uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure a given port to input UBX, NMEA, RTCM3 or a combination thereof + + boolean setI2CAddress(uint8_t deviceAddress, uint16_t maxTime = defaultMaxWait); //Changes the I2C address of the u-blox module + void setSerialRate(uint32_t baudrate, uint8_t uartPort = COM_PORT_UART1, uint16_t maxTime = defaultMaxWait); //Changes the serial baud rate of the u-blox module, uartPort should be COM_PORT_UART1/2 + + boolean setI2COutput(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure I2C port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUART1Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART1 port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUART2Output(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure UART2 port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setUSBOutput(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure USB port to output UBX, NMEA, RTCM3 or a combination thereof + boolean setSPIOutput(uint8_t comSettings, uint16_t maxWait = defaultMaxWait); //Configure SPI port to output UBX, NMEA, RTCM3 or a combination thereof + void setNMEAOutputPort(Stream &nmeaOutputPort); //Sets the internal variable for the port to direct NMEA characters to + + //Reset to defaults + + void factoryReset(); //Send factory reset sequence (i.e. load "default" configuration and perform hardReset) + void hardReset(); //Perform a reset leading to a cold start (zero info start-up) + boolean factoryDefault(uint16_t maxWait = defaultMaxWait); //Reset module to factory defaults + + //Save configuration to BBR / Flash + + boolean saveConfiguration(uint16_t maxWait = defaultMaxWait); //Save current configuration to flash and BBR (battery backed RAM) + boolean saveConfigSelective(uint32_t configMask, uint16_t maxWait = defaultMaxWait); //Save the selected configuration sub-sections to flash and BBR (battery backed RAM) + + //Functions to turn on/off message types for a given port ID (see COM_PORT_I2C, etc above) + boolean configureMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); + boolean enableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + boolean disableMessage(uint8_t msgClass, uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + boolean enableNMEAMessage(uint8_t msgID, uint8_t portID, uint8_t sendRate = 1, uint16_t maxWait = defaultMaxWait); + boolean disableNMEAMessage(uint8_t msgID, uint8_t portID, uint16_t maxWait = defaultMaxWait); + boolean enableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint8_t sendRate, uint16_t maxWait = defaultMaxWait); //Given a message number turns on a message ID for output over given PortID + boolean disableRTCMmessage(uint8_t messageNumber, uint8_t portID, uint16_t maxWait = defaultMaxWait); //Turn off given RTCM message from a given port + + //Functions used for RTK and base station setup + //It is probably safe to assume that users of the RTK will be using I2C / Qwiic. So let's leave maxWait set to 250ms. + boolean getSurveyMode(uint16_t maxWait = 250); //Get the current TimeMode3 settings + boolean setSurveyMode(uint8_t mode, uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); //Control survey in mode + boolean enableSurveyMode(uint16_t observationTime, float requiredAccuracy, uint16_t maxWait = 250); //Begin Survey-In for NEO-M8P + boolean disableSurveyMode(uint16_t maxWait = 250); //Stop Survey-In mode + // Given coordinates, put receiver into static position. Set latlong to true to pass in lat/long values instead of ecef. + // For ECEF the units are: cm, 0.1mm, cm, 0.1mm, cm, 0.1mm + // For Lat/Lon/Alt the units are: degrees^-7, degrees^-9, degrees^-7, degrees^-9, cm, 0.1mm + bool setStaticPosition(int32_t ecefXOrLat, int8_t ecefXOrLatHP, int32_t ecefYOrLon, int8_t ecefYOrLonHP, int32_t ecefZOrAlt, int8_t ecefZOrAltHP, bool latLong = false, uint16_t maxWait = 250); + bool setStaticPosition(int32_t ecefXOrLat, int32_t ecefYOrLon, int32_t ecefZOrAlt, bool latLong = false, uint16_t maxWait = 250); + + //Read the module's protocol version + uint8_t getProtocolVersionHigh(uint16_t maxWait = defaultMaxWait); //Returns the PROTVER XX.00 from UBX-MON-VER register + uint8_t getProtocolVersionLow(uint16_t maxWait = defaultMaxWait); //Returns the PROTVER 00.XX from UBX-MON-VER register + boolean getProtocolVersion(uint16_t maxWait = defaultMaxWait); //Queries module, loads low/high bytes + moduleSWVersion_t *moduleSWVersion = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + + //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 = defaultMaxWait); // Add a new geofence + boolean clearGeofences(uint16_t maxWait = defaultMaxWait); //Clears all geofences + boolean clearAntPIO(uint16_t maxWait = defaultMaxWait); //Clears the antenna control pin settings to release the PIOs + boolean getGeofenceState(geofenceState ¤tGeofenceState, uint16_t maxWait = defaultMaxWait); //Returns the combined geofence state + // Storage for the geofence parameters. RAM is allocated for this if/when required. + geofenceParams_t *currentGeofenceParams = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + + //Power save / off + boolean powerSaveMode(bool power_save = true, uint16_t maxWait = defaultMaxWait); + uint8_t getPowerSaveMode(uint16_t maxWait = defaultMaxWait); // Returns 255 if the sendCommand fails + boolean powerOff(uint32_t durationInMs, uint16_t maxWait = defaultMaxWait); + boolean powerOffWithInterrupt(uint32_t durationInMs, uint32_t wakeupSources = VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0, boolean forceWhileUsb = true, uint16_t maxWait = 1100); + + //Change the dynamic platform model using UBX-CFG-NAV5 + boolean setDynamicModel(dynModel newDynamicModel = DYN_MODEL_PORTABLE, uint16_t maxWait = defaultMaxWait); + uint8_t getDynamicModel(uint16_t maxWait = defaultMaxWait); // Get the dynamic model - returns 255 if the sendCommand fails + + //Reset the odometer + boolean resetOdometer(uint16_t maxWait = defaultMaxWait); // Reset 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 + //TO DO: Add support for sigCfgMask and maxTrkCh. (Need to resolve ambiguity with maxWait) + boolean enableGNSS(boolean enable, sfe_ublox_gnss_ids_e id, uint16_t maxWait = defaultMaxWait); + boolean isGNSSenabled(sfe_ublox_gnss_ids_e id, uint16_t maxWait = defaultMaxWait); + + //Reset ESF automatic IMU-mount alignment + boolean resetIMUalignment(uint16_t maxWait = defaultMaxWait); + + //General configuration (used only on protocol v27 and higher - ie, ZED-F9P) + + //It is probably safe to assume that users of the ZED-F9P will be using I2C / Qwiic. + //If they are using Serial then the higher baud rate will also help. So let's leave maxWait set to 250ms. + uint32_t createKey(uint16_t group, uint16_t id, uint8_t size); //Form 32-bit key from group/id/size + sfe_ublox_status_e getVal(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Load payload with response + uint8_t getVal8(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given key location + uint16_t getVal16(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given key location + uint32_t getVal32(uint32_t keyID, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given key location + uint8_t getVal8(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint16_t getVal16(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint32_t getVal32(uint16_t group, uint16_t id, uint8_t size, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = 250); //Returns the value at a given group/id/size location + uint8_t setVal(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); //Sets the 8-bit value at a given group/id/size location + uint8_t setVal16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); //Sets the 16-bit value at a given group/id/size location + uint8_t setVal32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_ALL, uint16_t maxWait = 250); //Sets the 32-bit value at a given group/id/size location + uint8_t newCfgValset8(uint32_t keyID, uint8_t value, uint8_t layer = VAL_LAYER_ALL); //Define a new UBX-CFG-VALSET with the given KeyID and 8-bit value + uint8_t newCfgValset16(uint32_t keyID, uint16_t value, uint8_t layer = VAL_LAYER_ALL); //Define a new UBX-CFG-VALSET with the given KeyID and 16-bit value + uint8_t newCfgValset32(uint32_t keyID, uint32_t value, uint8_t layer = VAL_LAYER_ALL); //Define a new UBX-CFG-VALSET with the given KeyID and 32-bit value + uint8_t addCfgValset8(uint32_t keyID, uint8_t value); //Add a new KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset16(uint32_t keyID, uint16_t value); //Add a new KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t addCfgValset32(uint32_t keyID, uint32_t value); //Add a new KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket + uint8_t sendCfgValset8(uint32_t keyID, uint8_t value, uint16_t maxWait = 250); //Add the final KeyID and 8-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset16(uint32_t keyID, uint16_t value, uint16_t maxWait = 250); //Add the final KeyID and 16-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + uint8_t sendCfgValset32(uint32_t keyID, uint32_t value, uint16_t maxWait = 250); //Add the final KeyID and 32-bit value to an existing UBX-CFG-VALSET ubxPacket and send it + +// getPVT will only return data once in each navigation cycle. By default, that is once per second. +// Therefore we should set defaultMaxWait 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. 300msec would be about right: getPVT(300) + + // get and set functions for all of the "automatic" message processing + + // Navigation (NAV) + + boolean getNAVPOSECEF(uint16_t maxWait = defaultMaxWait); // NAV POSECEF + boolean setAutoNAVPOSECEF(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic POSECEF reports at the navigation frequency + boolean setAutoNAVPOSECEF(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic POSECEF 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 setAutoNAVPOSECEFcallback(void (*callbackPointer)(UBX_NAV_POSECEF_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic POSECEF reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoNAVPOSECEF(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and POSECEF is send cyclically already + void flushNAVPOSECEF(); //Mark all the data as read/stale + void logNAVPOSECEF(boolean enabled = true); // Log data to file buffer + + boolean getNAVSTATUS(uint16_t maxWait = defaultMaxWait); // NAV STATUS + boolean setAutoNAVSTATUS(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic STATUS reports at the navigation frequency + boolean setAutoNAVSTATUS(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic STATUS 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 setAutoNAVSTATUScallback(void (*callbackPointer)(UBX_NAV_STATUS_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic STATUS reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoNAVSTATUS(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and STATUS is send cyclically already + void flushNAVSTATUS(); //Mark all the data as read/stale + void logNAVSTATUS(boolean enabled = true); // Log data to file buffer + + boolean getDOP(uint16_t maxWait = defaultMaxWait); //Query module for latest dilution of precision values and load global vars:. If autoDOP is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new DOP is available. + boolean setAutoDOP(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic DOP reports at the navigation frequency + boolean setAutoDOP(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic DOP 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 setAutoDOPcallback(void (*callbackPointer)(UBX_NAV_DOP_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic DOP reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoDOP(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and DOP is send cyclically already + void flushDOP(); //Mark all the DOP data as read/stale + void logNAVDOP(boolean enabled = true); // Log data to file buffer + + boolean getVehAtt(uint16_t maxWait = defaultMaxWait); // NAV ATT Helper + boolean getNAVATT(uint16_t maxWait = defaultMaxWait); // NAV ATT + boolean setAutoNAVATT(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic vehicle attitude reports at the navigation frequency + boolean setAutoNAVATT(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic vehicle attitude 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 setAutoNAVATTcallback(void (*callbackPointer)(UBX_NAV_ATT_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic ATT reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoNAVATT(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and vehicle attitude is send cyclically already + void flushNAVATT(); //Mark all the data as read/stale + void logNAVATT(boolean enabled = true); // Log data to file buffer + + boolean getPVT(uint16_t maxWait = defaultMaxWait); //Query module for latest group of datums and load global vars: lat, long, alt, speed, SIV, accuracies, etc. If autoPVT is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new PVT is available. + boolean setAutoPVT(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency + boolean setAutoPVT(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic PVT reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean setAutoPVTcallback(void (*callbackPointer)(UBX_NAV_PVT_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic PVT reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoPVT(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and PVT is send cyclically already + void flushPVT(); //Mark all the PVT data as read/stale + void logNAVPVT(boolean enabled = true); // Log data to file buffer + + boolean getNAVODO(uint16_t maxWait = defaultMaxWait); // NAV ODO + boolean setAutoNAVODO(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ODO reports at the navigation frequency + boolean setAutoNAVODO(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ODO 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 setAutoNAVODOcallback(void (*callbackPointer)(UBX_NAV_ODO_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic ODO reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoNAVODO(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and ODO is send cyclically already + void flushNAVODO(); //Mark all the data as read/stale + void logNAVODO(boolean enabled = true); // Log data to file buffer + + boolean getNAVVELECEF(uint16_t maxWait = defaultMaxWait); // NAV VELECEF + boolean setAutoNAVVELECEF(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic VELECEF reports at the navigation frequency + boolean setAutoNAVVELECEF(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic VELECEF 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 setAutoNAVVELECEFcallback(void (*callbackPointer)(UBX_NAV_VELECEF_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic VELECEF reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoNAVVELECEF(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and VELECEF is send cyclically already + void flushNAVVELECEF(); //Mark all the data as read/stale + void logNAVVELECEF(boolean enabled = true); // Log data to file buffer + + boolean getNAVVELNED(uint16_t maxWait = defaultMaxWait); // NAV VELNED + boolean setAutoNAVVELNED(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic VELNED reports at the navigation frequency + boolean setAutoNAVVELNED(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic VELNED 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 setAutoNAVVELNEDcallback(void (*callbackPointer)(UBX_NAV_VELNED_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic VELNED reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoNAVVELNED(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and VELNED is send cyclically already + void flushNAVVELNED(); //Mark all the data as read/stale + void logNAVVELNED(boolean enabled = true); // Log data to file buffer + + boolean getNAVHPPOSECEF(uint16_t maxWait = defaultMaxWait); // NAV HPPOSECEF + boolean setAutoNAVHPPOSECEF(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HPPOSECEF reports at the navigation frequency + boolean setAutoNAVHPPOSECEF(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HPPOSECEF 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 setAutoNAVHPPOSECEFcallback(void (*callbackPointer)(UBX_NAV_HPPOSECEF_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic HPPOSECEF reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoNAVHPPOSECEF(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HPPOSECEF is send cyclically already + void flushNAVHPPOSECEF(); //Mark all the data as read/stale + void logNAVHPPOSECEF(boolean enabled = true); // Log data to file buffer + + boolean getHPPOSLLH(uint16_t maxWait = defaultMaxWait); //Query module for latest group of datums and load global vars: lat, long, alt, speed, SIV, accuracies, etc. If autoPVT is disabled, performs an explicit poll and waits, if enabled does not block. Returns true if new HPPOSLLH is available. + boolean setAutoHPPOSLLH(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HPPOSLLH reports at the navigation frequency + boolean setAutoHPPOSLLH(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HPPOSLLH reports at the navigation frequency, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean setAutoHPPOSLLHcallback(void (*callbackPointer)(UBX_NAV_HPPOSLLH_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic HPPOSLLH reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoHPPOSLLH(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HPPOSLLH is send cyclically already + void flushHPPOSLLH(); //Mark all the HPPPOSLLH data as read/stale. This is handy to get data alignment after CRC failure + void logNAVHPPOSLLH(boolean enabled = true); // Log data to file buffer + + boolean getNAVCLOCK(uint16_t maxWait = defaultMaxWait); // NAV CLOCK + boolean setAutoNAVCLOCK(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic clock reports at the navigation frequency + boolean setAutoNAVCLOCK(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic clock 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 setAutoNAVCLOCKcallback(void (*callbackPointer)(UBX_NAV_CLOCK_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic CLOCK reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoNAVCLOCK(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and clock is send cyclically already + void flushNAVCLOCK(); //Mark all the data as read/stale + void logNAVCLOCK(boolean enabled = true); // Log data to file buffer + + // Add "auto" support for NAV SVIN - to avoid needing 'global' storage + boolean getSurveyStatus(uint16_t maxWait); //Reads survey in status + + boolean getRELPOSNED(uint16_t maxWait = defaultMaxWait); //Get Relative Positioning Information of the NED frame + boolean setAutoRELPOSNED(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic RELPOSNED reports + boolean setAutoRELPOSNED(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic RELPOSNED, 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 setAutoRELPOSNEDcallback(void (*callbackPointer)(UBX_NAV_RELPOSNED_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic RELPOSNED reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoRELPOSNED(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and RELPOSNED is send cyclically already + void flushNAVRELPOSNED(); //Mark all the data as read/stale + void logNAVRELPOSNED(boolean enabled = true); // Log data to file buffer + + // Receiver Manager Messages (RXM) + + boolean getRXMSFRBX(uint16_t maxWait = defaultMaxWait); // RXM SFRBX + boolean setAutoRXMSFRBX(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic RXM SFRBX reports at the navigation frequency + boolean setAutoRXMSFRBX(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic RXM SFRBX 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 setAutoRXMSFRBXcallback(void (*callbackPointer)(UBX_RXM_SFRBX_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic SFRBX reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoRXMSFRBX(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and RXM SFRBX is send cyclically already + void flushRXMSFRBX(); //Mark all the data as read/stale + void logRXMSFRBX(boolean enabled = true); // Log data to file buffer + + boolean getRXMRAWX(uint16_t maxWait = defaultMaxWait); // RXM RAWX + boolean setAutoRXMRAWX(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic RXM RAWX reports at the navigation frequency + boolean setAutoRXMRAWX(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic RXM RAWX 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 setAutoRXMRAWXcallback(void (*callbackPointer)(UBX_RXM_RAWX_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic RAWX reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoRXMRAWX(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and RXM RAWX is send cyclically already + void flushRXMRAWX(); //Mark all the data as read/stale + void logRXMRAWX(boolean enabled = true); // Log data to file buffer + + // Configuration (CFG) + + // Add "auto" support for CFG RATE - because we use it for isConnected (to stop it being mugged by other messages) + boolean getNavigationFrequencyInternal(uint16_t maxWait = defaultMaxWait); //Get the number of nav solutions sent per second currently being output by module + + // Timing messages (TIM) + + boolean getTIMTM2(uint16_t maxWait = defaultMaxWait); // TIM TM2 + boolean setAutoTIMTM2(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic TIM TM2 reports at the navigation frequency + boolean setAutoTIMTM2(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic TIM TM2 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 setAutoTIMTM2callback(void (*callbackPointer)(UBX_TIM_TM2_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic TM2 reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoTIMTM2(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and TIM TM2 is send cyclically already + void flushTIMTM2(); //Mark all the data as read/stale + void logTIMTM2(boolean enabled = true); // Log data to file buffer + + // Sensor fusion (dead reckoning) (ESF) + + boolean getEsfAlignment(uint16_t maxWait = defaultMaxWait); // ESF ALG Helper + boolean getESFALG(uint16_t maxWait = defaultMaxWait); // ESF ALG + boolean setAutoESFALG(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ESF ALG reports + boolean setAutoESFALG(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ESF ALG reports, 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 setAutoESFALGcallback(void (*callbackPointer)(UBX_ESF_ALG_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic ALG reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoESFALG(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and ESF ALG is send cyclically already + void flushESFALG(); //Mark all the data as read/stale + void logESFALG(boolean enabled = true); // Log data to file buffer + + boolean getEsfInfo(uint16_t maxWait = defaultMaxWait); // ESF STATUS Helper + boolean getESFSTATUS(uint16_t maxWait = defaultMaxWait); // ESF STATUS + boolean setAutoESFSTATUS(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ESF STATUS reports + boolean setAutoESFSTATUS(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ESF STATUS reports, 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 setAutoESFSTATUScallback(void (*callbackPointer)(UBX_ESF_STATUS_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic STATUS reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoESFSTATUS(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and ESF STATUS is send cyclically already + void flushESFSTATUS(); //Mark all the data as read/stale + void logESFSTATUS(boolean enabled = true); // Log data to file buffer + + boolean getEsfIns(uint16_t maxWait = defaultMaxWait); // ESF INS Helper + boolean getESFINS(uint16_t maxWait = defaultMaxWait); // ESF INS + boolean setAutoESFINS(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ESF INS reports + boolean setAutoESFINS(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ESF INS reports, 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 setAutoESFINScallback(void (*callbackPointer)(UBX_ESF_INS_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic INS reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoESFINS(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and ESF INS is send cyclically already + void flushESFINS(); //Mark all the data as read/stale + void logESFINS(boolean enabled = true); // Log data to file buffer + + boolean getEsfDataInfo(uint16_t maxWait = defaultMaxWait); // ESF MEAS Helper + boolean getESFMEAS(uint16_t maxWait = defaultMaxWait); // ESF MEAS + boolean setAutoESFMEAS(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ESF MEAS reports + boolean setAutoESFMEAS(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ESF MEAS reports, 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 setAutoESFMEAScallback(void (*callbackPointer)(UBX_ESF_MEAS_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic MEAS reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoESFMEAS(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and ESF MEAS is send cyclically already + void flushESFMEAS(); //Mark all the data as read/stale + void logESFMEAS(boolean enabled = true); // Log data to file buffer + + boolean getEsfRawDataInfo(uint16_t maxWait = defaultMaxWait); // ESF RAW Helper + boolean getESFRAW(uint16_t maxWait = defaultMaxWait); // ESF RAW + boolean setAutoESFRAW(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ESF RAW reports + boolean setAutoESFRAW(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic ESF RAW reports, 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 setAutoESFRAWcallback(void (*callbackPointer)(UBX_ESF_RAW_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic RAW reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoESFRAW(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and ESF RAW is send cyclically already + void flushESFRAW(); //Mark all the data as read/stale + void logESFRAW(boolean enabled = true); // Log data to file buffer + + // High navigation rate (HNR) + + boolean getHNRAtt(uint16_t maxWait = defaultMaxWait); // HNR ATT Helper + boolean getHNRATT(uint16_t maxWait = defaultMaxWait); // Returns true if the get HNR attitude is successful + boolean setAutoHNRATT(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR Attitude reports at the HNR rate + boolean setAutoHNRATT(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR Attitude reports at the HNR rate, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean setAutoHNRATTcallback(void (*callbackPointer)(UBX_HNR_ATT_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic ATT reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoHNRATT(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HNR Attitude is send cyclically already + void flushHNRATT(); //Mark all the data as read/stale + void logHNRATT(boolean enabled = true); // Log data to file buffer + + boolean getHNRDyn(uint16_t maxWait = defaultMaxWait); // HNR INS Helper + boolean getHNRINS(uint16_t maxWait = defaultMaxWait); // Returns true if the get HNR dynamics is successful + boolean setAutoHNRINS(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR dynamics reports at the HNR rate + boolean setAutoHNRINS(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR dynamics reports at the HNR rate, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean setAutoHNRINScallback(void (*callbackPointer)(UBX_HNR_INS_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic INS reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoHNRINS(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HNR dynamics is send cyclically already + void flushHNRINS(); //Mark all the data as read/stale + void logHNRINS(boolean enabled = true); // Log data to file buffer + + boolean getHNRPVT(uint16_t maxWait = defaultMaxWait); // Returns true if the get HNR PVT is successful + boolean setAutoHNRPVT(boolean enabled, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR PVT reports at the HNR rate + boolean setAutoHNRPVT(boolean enabled, boolean implicitUpdate, uint16_t maxWait = defaultMaxWait); //Enable/disable automatic HNR PVT reports at the HNR rate, with implicitUpdate == false accessing stale data will not issue parsing of data in the rxbuffer of your interface, instead you have to call checkUblox when you want to perform an update + boolean setAutoHNRPVTcallback(void (*callbackPointer)(UBX_HNR_PVT_data_t), uint16_t maxWait = defaultMaxWait); //Enable automatic PVT reports at the navigation frequency. Data is accessed from the callback. + boolean assumeAutoHNRPVT(boolean enabled, boolean implicitUpdate = true); //In case no config access to the GPS is possible and HNR PVT is send cyclically already + void flushHNRPVT(); //Mark all the data as read/stale + void logHNRPVT(boolean enabled = true); // Log data to file buffer + + // Helper functions for CFG RATE + + boolean setNavigationFrequency(uint8_t navFreq, uint16_t maxWait = defaultMaxWait); //Set the number of nav solutions sent per second + uint8_t getNavigationFrequency(uint16_t maxWait = defaultMaxWait); //Get the number of nav solutions sent per second currently being output by module + + // Helper functions for DOP + + uint16_t getGeometricDOP(uint16_t maxWait = defaultMaxWait); + uint16_t getPositionDOP(uint16_t maxWait = defaultMaxWait); + uint16_t getTimeDOP(uint16_t maxWait = defaultMaxWait); + uint16_t getVerticalDOP(uint16_t maxWait = defaultMaxWait); + uint16_t getHorizontalDOP(uint16_t maxWait = defaultMaxWait); + uint16_t getNorthingDOP(uint16_t maxWait = defaultMaxWait); + uint16_t getEastingDOP(uint16_t maxWait = defaultMaxWait); + + // Helper functions for ATT + + float getATTroll(uint16_t maxWait = defaultMaxWait); // Returned as degrees + float getATTpitch(uint16_t maxWait = defaultMaxWait); // Returned as degrees + float getATTheading(uint16_t maxWait = defaultMaxWait); // Returned as degrees + + // Helper functions for PVT + + uint32_t getTimeOfWeek(uint16_t maxWait = defaultMaxWait); + uint16_t getYear(uint16_t maxWait = defaultMaxWait); + uint8_t getMonth(uint16_t maxWait = defaultMaxWait); + uint8_t getDay(uint16_t maxWait = defaultMaxWait); + uint8_t getHour(uint16_t maxWait = defaultMaxWait); + uint8_t getMinute(uint16_t maxWait = defaultMaxWait); + uint8_t getSecond(uint16_t maxWait = defaultMaxWait); + uint16_t getMillisecond(uint16_t maxWait = defaultMaxWait); + int32_t getNanosecond(uint16_t maxWait = defaultMaxWait); + + bool getDateValid(uint16_t maxWait = defaultMaxWait); + bool getTimeValid(uint16_t maxWait = defaultMaxWait); + + uint8_t getFixType(uint16_t maxWait = defaultMaxWait); //Returns the type of fix: 0=no, 3=3D, 4=GNSS+Deadreckoning + + bool getGnssFixOk(uint16_t maxWait = defaultMaxWait); //Get whether we have a valid fix (i.e within DOP & accuracy masks) + bool getDiffSoln(uint16_t maxWait = defaultMaxWait); //Get whether differential corrections were applied + bool getHeadVehValid(uint16_t maxWait = defaultMaxWait); + uint8_t getCarrierSolutionType(uint16_t maxWait = defaultMaxWait); //Returns RTK solution: 0=no, 1=float solution, 2=fixed solution + + uint8_t getSIV(uint16_t maxWait = defaultMaxWait); //Returns number of sats used in fix + int32_t getLongitude(uint16_t maxWait = defaultMaxWait); //Returns the current longitude in degrees * 10-7. Auto selects between HighPrecision and Regular depending on ability of module. + int32_t getLatitude(uint16_t maxWait = defaultMaxWait); //Returns the current latitude in degrees * 10^-7. Auto selects between HighPrecision and Regular depending on ability of module. + int32_t getAltitude(uint16_t maxWait = defaultMaxWait); //Returns the current altitude in mm above ellipsoid + int32_t getAltitudeMSL(uint16_t maxWait = defaultMaxWait); //Returns the current altitude in mm above mean sea level + int32_t getHorizontalAccEst(uint16_t maxWait = defaultMaxWait); + int32_t getVerticalAccEst(uint16_t maxWait = defaultMaxWait); + int32_t getNedNorthVel(uint16_t maxWait = defaultMaxWait); + int32_t getNedEastVel(uint16_t maxWait = defaultMaxWait); + int32_t getNedDownVel(uint16_t maxWait = defaultMaxWait); + int32_t getGroundSpeed(uint16_t maxWait = defaultMaxWait); //Returns speed in mm/s + int32_t getHeading(uint16_t maxWait = defaultMaxWait); //Returns heading in degrees * 10^-5 + uint32_t getSpeedAccEst(uint16_t maxWait = defaultMaxWait); + uint32_t getHeadingAccEst(uint16_t maxWait = defaultMaxWait); + uint16_t getPDOP(uint16_t maxWait = defaultMaxWait); //Returns positional dillution of precision * 10^-2 (dimensionless) + + bool getInvalidLlh(uint16_t maxWait = defaultMaxWait); + + int32_t getHeadVeh(uint16_t maxWait = defaultMaxWait); + int16_t getMagDec(uint16_t maxWait = defaultMaxWait); + uint16_t getMagAcc(uint16_t maxWait = defaultMaxWait); + + int32_t getGeoidSeparation(uint16_t maxWait = defaultMaxWait); + + // Helper functions for HPPOSECEF + + uint32_t getPositionAccuracy(uint16_t maxWait = 1100); //Returns the 3D accuracy of the current high-precision fix, in mm. Supported on NEO-M8P, ZED-F9P, + + // Helper functions for HPPOSLLH + + uint32_t getTimeOfWeekFromHPPOSLLH(uint16_t maxWait = defaultMaxWait); + int32_t getHighResLongitude(uint16_t maxWait = defaultMaxWait); + int32_t getHighResLatitude(uint16_t maxWait = defaultMaxWait); + int32_t getElipsoid(uint16_t maxWait = defaultMaxWait); + int32_t getMeanSeaLevel(uint16_t maxWait = defaultMaxWait); + int8_t getHighResLongitudeHp(uint16_t maxWait = defaultMaxWait); + int8_t getHighResLatitudeHp(uint16_t maxWait = defaultMaxWait); + int8_t getElipsoidHp(uint16_t maxWait = defaultMaxWait); + int8_t getMeanSeaLevelHp(uint16_t maxWait = defaultMaxWait); + uint32_t getHorizontalAccuracy(uint16_t maxWait = defaultMaxWait); + uint32_t getVerticalAccuracy(uint16_t maxWait = defaultMaxWait); + + // Helper functions for SVIN + + boolean getSurveyInActive(uint16_t maxWait = defaultMaxWait); + boolean getSurveyInValid(uint16_t maxWait = defaultMaxWait); + uint16_t getSurveyInObservationTime(uint16_t maxWait = defaultMaxWait); // Truncated to 65535 seconds + float getSurveyInMeanAccuracy(uint16_t maxWait = defaultMaxWait); // Returned as m + + // Helper functions for RELPOSNED + + float getRelPosN(uint16_t maxWait = defaultMaxWait); // Returned as m + float getRelPosE(uint16_t maxWait = defaultMaxWait); // Returned as m + float getRelPosD(uint16_t maxWait = defaultMaxWait); // Returned as m + float getRelPosAccN(uint16_t maxWait = defaultMaxWait); // Returned as m + float getRelPosAccE(uint16_t maxWait = defaultMaxWait); // Returned as m + float getRelPosAccD(uint16_t maxWait = defaultMaxWait); // Returned as m + + // Helper functions for ESF + + float getESFroll(uint16_t maxWait = defaultMaxWait); // Returned as degrees + float getESFpitch(uint16_t maxWait = defaultMaxWait); // Returned as degrees + float getESFyaw(uint16_t maxWait = defaultMaxWait); // Returned as degrees + boolean getSensorFusionMeasurement(UBX_ESF_MEAS_sensorData_t *sensorData, uint8_t sensor, uint16_t maxWait = defaultMaxWait); + boolean getSensorFusionMeasurement(UBX_ESF_MEAS_sensorData_t *sensorData, UBX_ESF_MEAS_data_t ubxDataStruct, uint8_t sensor); + boolean getRawSensorMeasurement(UBX_ESF_RAW_sensorData_t *sensorData, uint8_t sensor, uint16_t maxWait = defaultMaxWait); + boolean getRawSensorMeasurement(UBX_ESF_RAW_sensorData_t *sensorData, UBX_ESF_RAW_data_t ubxDataStruct, uint8_t sensor); + boolean getSensorFusionStatus(UBX_ESF_STATUS_sensorStatus_t *sensorStatus, uint8_t sensor, uint16_t maxWait = defaultMaxWait); + boolean getSensorFusionStatus(UBX_ESF_STATUS_sensorStatus_t *sensorStatus, UBX_ESF_STATUS_data_t ubxDataStruct, uint8_t sensor); + + // Helper functions for HNR + + boolean setHNRNavigationRate(uint8_t rate, uint16_t maxWait = 1100); // Returns true if the setHNRNavigationRate is successful + uint8_t getHNRNavigationRate(uint16_t maxWait = 1100); // Returns 0 if the getHNRNavigationRate fails + float getHNRroll(uint16_t maxWait = defaultMaxWait); // Returned as degrees + float getHNRpitch(uint16_t maxWait = defaultMaxWait); // Returned as degrees + float getHNRheading(uint16_t maxWait = defaultMaxWait); // Returned as degrees + + // 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 + uint32_t extractLong(ubxPacket *msg, uint8_t spotToStart); //Combine four bytes from payload into long + int32_t extractSignedLong(ubxPacket *msg, uint8_t spotToStart); //Combine four bytes from payload into signed long (avoiding any ambiguity caused by casting) + uint16_t extractInt(ubxPacket *msg, uint8_t spotToStart); //Combine two bytes from payload into int + int16_t extractSignedInt(ubxPacket *msg, int8_t spotToStart); + uint8_t extractByte(ubxPacket *msg, uint8_t spotToStart); //Get byte from payload + int8_t extractSignedChar(ubxPacket *msg, uint8_t spotToStart); //Get signed 8-bit value from payload + + // Pointers to storage for the "automatic" messages + // RAM is allocated for these if/when required. + + UBX_NAV_POSECEF_t *packetUBXNAVPOSECEF = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_STATUS_t *packetUBXNAVSTATUS = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_DOP_t *packetUBXNAVDOP = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_ATT_t *packetUBXNAVATT = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_PVT_t *packetUBXNAVPVT = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_ODO_t *packetUBXNAVODO = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_VELECEF_t *packetUBXNAVVELECEF = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_VELNED_t *packetUBXNAVVELNED = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_HPPOSECEF_t *packetUBXNAVHPPOSECEF = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_HPPOSLLH_t *packetUBXNAVHPPOSLLH = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_CLOCK_t *packetUBXNAVCLOCK = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_SVIN_t *packetUBXNAVSVIN = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_NAV_RELPOSNED_t *packetUBXNAVRELPOSNED = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + + UBX_RXM_SFRBX_t *packetUBXRXMSFRBX = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_RXM_RAWX_t *packetUBXRXMRAWX = 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_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 + UBX_ESF_MEAS_t *packetUBXESFMEAS = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_ESF_RAW_t *packetUBXESFRAW = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_ESF_STATUS_t *packetUBXESFSTATUS = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + + UBX_HNR_PVT_t *packetUBXHNRPVT = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_HNR_ATT_t *packetUBXHNRATT = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + UBX_HNR_INS_t *packetUBXHNRINS = NULL; // Pointer to struct. RAM will be allocated for this if/when necessary + + uint16_t rtcmFrameCounter = 0; //Tracks the type of incoming byte inside RTCM frame + +private: + //Depending on the sentence type the processor will load characters into different arrays + enum SentenceTypes + { + NONE = 0, + NMEA, + UBX, + RTCM + } currentSentence = NONE; + + //Depending on the ubx binary response class, store binary responses into different places + enum classTypes + { + CLASS_NONE = 0, + CLASS_ACK, + CLASS_NOT_AN_ACK + } ubxFrameClass = CLASS_NONE; + + enum commTypes + { + COMM_TYPE_I2C = 0, + COMM_TYPE_SERIAL, + COMM_TYPE_SPI + } commType = COMM_TYPE_I2C; //Controls which port we look to for incoming bytes + + //Functions + boolean checkUbloxInternal(ubxPacket *incomingUBX, uint8_t requestedClass = 255, uint8_t requestedID = 255); //Checks module with user selected commType + void addToChecksum(uint8_t incoming); //Given an incoming byte, adjust rollingChecksumA/B + + //Return true if this "automatic" message has storage allocated for it + boolean checkAutomatic(uint8_t Class, uint8_t ID); + + //Calculate how much RAM is needed to store the payload for a given automatic message + uint16_t getMaxPayloadSize(uint8_t Class, uint8_t ID); + + boolean initGeofenceParams(); // Allocate RAM for currentGeofenceParams and initialize it + boolean initModuleSWVersion(); // Allocate RAM for moduleSWVersion and initialize it + + // The initPacket functions need to be private as they don't check if memory has already been allocated. + // Functions like setAutoNAVPOSECEF will check that memory has not been allocated before calling initPacket. + boolean initPacketUBXNAVPOSECEF(); // Allocate RAM for packetUBXNAVPOSECEF and initialize it + boolean initPacketUBXNAVSTATUS(); // Allocate RAM for packetUBXNAVSTATUS and initialize it + boolean initPacketUBXNAVDOP(); // Allocate RAM for packetUBXNAVDOP and initialize it + boolean initPacketUBXNAVATT(); // Allocate RAM for packetUBXNAVATT and initialize it + boolean initPacketUBXNAVPVT(); // Allocate RAM for packetUBXNAVPVT and initialize it + boolean initPacketUBXNAVODO(); // Allocate RAM for packetUBXNAVODO and initialize it + boolean initPacketUBXNAVVELECEF(); // Allocate RAM for packetUBXNAVVELECEF and initialize it + boolean initPacketUBXNAVVELNED(); // Allocate RAM for packetUBXNAVVELNED and initialize it + boolean initPacketUBXNAVHPPOSECEF(); // Allocate RAM for packetUBXNAVHPPOSECEF and initialize it + boolean initPacketUBXNAVHPPOSLLH(); // Allocate RAM for packetUBXNAVHPPOSLLH and initialize it + boolean initPacketUBXNAVCLOCK(); // Allocate RAM for packetUBXNAVCLOCK and initialize it + boolean initPacketUBXNAVSVIN(); // Allocate RAM for packetUBXNAVSVIN and initialize it + boolean initPacketUBXNAVRELPOSNED(); // Allocate RAM for packetUBXNAVRELPOSNED and initialize it + boolean initPacketUBXRXMSFRBX(); // Allocate RAM for packetUBXRXMSFRBX and initialize it + boolean initPacketUBXRXMRAWX(); // Allocate RAM for packetUBXRXMRAWX and initialize it + boolean initPacketUBXCFGRATE(); // Allocate RAM for packetUBXCFGRATE and initialize it + boolean initPacketUBXTIMTM2(); // Allocate RAM for packetUBXTIMTM2 and initialize it + boolean initPacketUBXESFALG(); // Allocate RAM for packetUBXESFALG and initialize it + boolean initPacketUBXESFSTATUS(); // Allocate RAM for packetUBXESFSTATUS and initialize it + boolean initPacketUBXESFINS(); // Allocate RAM for packetUBXESFINS and initialize it + boolean initPacketUBXESFMEAS(); // Allocate RAM for packetUBXESFMEAS and initialize it + boolean initPacketUBXESFRAW(); // Allocate RAM for packetUBXESFRAW and initialize it + boolean initPacketUBXHNRATT(); // Allocate RAM for packetUBXHNRATT and initialize it + boolean initPacketUBXHNRINS(); // Allocate RAM for packetUBXHNRINS and initialize it + boolean initPacketUBXHNRPVT(); // Allocate RAM for packetUBXHNRPVT and initialize it + + //Variables + TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware + Stream *_serialPort; //The generic connection to user's chosen Serial hardware + Stream *_nmeaOutputPort = NULL; //The user can assign an output port to print NMEA sentences if they wish + Stream *_debugSerial; //The stream to send debug messages to if enabled + + uint8_t _gpsI2Caddress = 0x42; //Default 7-bit unshifted address of the ublox 6/7/8/M8/F9 series + //This can be changed using the ublox configuration software + + boolean _printDebug = false; //Flag to print the serial commands we are sending to the Serial port for debug + boolean _printLimitedDebug = false; //Flag to print limited debug messages. Useful for I2C debugging or high navigation rates + + boolean ubx7FcheckDisabled = false; // Flag to indicate if the "7F" check should be ignored in checkUbloxI2C + + //The packet buffers + //These are pointed at from within the ubxPacket + uint8_t payloadAck[2]; // Holds the requested ACK/NACK + uint8_t payloadBuf[2]; // Temporary buffer used to screen incoming packets or dump unrequested packets + size_t packetCfgPayloadSize = 0; // Size for the packetCfg payload. .begin will set this to MAX_PAYLOAD_SIZE if necessary. User can change with setPacketCfgPayloadSize + uint8_t *payloadCfg = NULL; + uint8_t *payloadAuto = NULL; + + //Init the packet structures and init them with pointers to the payloadAck, payloadCfg, payloadBuf and payloadAuto arrays + ubxPacket packetAck = {0, 0, 0, 0, 0, payloadAck, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetBuf = {0, 0, 0, 0, 0, payloadBuf, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetCfg = {0, 0, 0, 0, 0, payloadCfg, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + ubxPacket packetAuto = {0, 0, 0, 0, 0, payloadAuto, 0, 0, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED, SFE_UBLOX_PACKET_VALIDITY_NOT_DEFINED}; + + //Flag if this packet is unrequested (and so should be ignored and not copied into packetCfg or packetAck) + boolean ignoreThisPayload = false; + + //Identify which buffer is in use + //Data is stored in packetBuf until the requested class and ID can be validated + //If a match is seen, data is diverted into packetAck or packetCfg + //"Automatic" messages which have RAM allocated for them are diverted into packetAuto + sfe_ublox_packet_buffer_e activePacketBuffer = SFE_UBLOX_PACKET_PACKETBUF; + + //Limit checking of new data to every X ms + //If we are expecting an update every X Hz then we should check every half that amount of time + //Otherwise we may block ourselves from seeing new data + uint8_t i2cPollingWait = 100; //Default to 100ms. Adjusted when user calls setNavigationFrequency() or setHNRNavigationRate() + + unsigned long lastCheck = 0; + + uint16_t ubxFrameCounter; //It counts all UBX frame. [Fixed header(2bytes), CLS(1byte), ID(1byte), length(2bytes), payload(x bytes), checksums(2bytes)] + + uint8_t rollingChecksumA; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + uint8_t rollingChecksumB; //Rolls forward as we receive incoming bytes. Checked against the last two A/B checksum bytes + + uint16_t rtcmLen = 0; + + // Flag to prevent reentry into checkCallbacks + // Prevent badness if the user accidentally calls checkCallbacks from inside a callback + volatile boolean checkCallbacksReentrant = false; + + // Support for data logging + uint8_t *ubxFileBuffer = NULL; // Pointer to the file buffer. RAM is allocated for this if required in .begin + uint16_t fileBufferSize = 0; // The size of the file buffer. This can be changed by calling setFileBufferSize _before_ .begin + uint16_t fileBufferHead; // The incoming byte is written into the file buffer at this location + uint16_t fileBufferTail; // The next byte to be read from the buffer will be read from this location + uint16_t fileBufferMaxAvail = 0; // The maximum number of bytes the file buffer has contained. Handy for checking the buffer is large enough to handle all the incoming data. + boolean createFileBuffer(void); // Create the file buffer. Called by .begin + uint16_t fileBufferSpaceAvailable(void); // Check how much space is available in the buffer + uint16_t fileBufferSpaceUsed(void); // Check how much space is used in the buffer + boolean storePacket(ubxPacket *msg); // Add a UBX packet to the file buffer + boolean storeFileBytes(uint8_t *theBytes, uint16_t numBytes); // Add theBytes to the file buffer + void writeToFileBuffer(uint8_t *theBytes, uint16_t numBytes); // Write theBytes to the file buffer +}; + +#endif diff --git a/src/u-blox_config_keys.h b/src/u-blox_config_keys.h new file mode 100644 index 0000000..9217d46 --- /dev/null +++ b/src/u-blox_config_keys.h @@ -0,0 +1,839 @@ +/* + This is a library written for the u-blox ZED-F9P and NEO-M8P-2 + SparkFun sells these at its website: www.sparkfun.com + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/16481 + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Original version by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + v2.0 rework by Paul Clark @ SparkFun Electronics, December 31st, 2020 + + This library handles configuring and handling the responses + from a u-blox GPS module. Works with most modules from u-blox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + Arduino IDE 1.8.13 + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef __u_blox_config_keys_h__ +#define __u_blox_config_keys_h__ + +//The following consts are used to generate KEY values for the advanced protocol functions of VELGET/SET/DEL +const uint8_t VAL_SIZE_1 = 0x01; //One bit +const uint8_t VAL_SIZE_8 = 0x02; //One byte +const uint8_t VAL_SIZE_16 = 0x03; //Two bytes +const uint8_t VAL_SIZE_32 = 0x04; //Four bytes +const uint8_t VAL_SIZE_64 = 0x05; //Eight bytes + +//These are the Bitfield layers definitions for the UBX-CFG-VALSET message (not to be confused with Bitfield deviceMask in UBX-CFG-CFG) +const uint8_t VAL_LAYER_RAM = (1 << 0); +const uint8_t VAL_LAYER_BBR = (1 << 1); +const uint8_t VAL_LAYER_FLASH = (1 << 2); +const uint8_t VAL_LAYER_ALL = VAL_LAYER_RAM | VAL_LAYER_BBR | VAL_LAYER_FLASH; //Not valid with getVal() + +//Below are various Groups, IDs, and sizes for various settings +//These can be used to call getVal/setVal/delVal +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint8_t VAL_ID_PROT_UBX = 0x01; +const uint8_t VAL_ID_PROT_NMEA = 0x02; +const uint8_t VAL_ID_PROT_RTCM3 = 0x04; + +const uint8_t VAL_GROUP_I2C = 0x51; +const uint8_t VAL_GROUP_I2COUTPROT = 0x72; +const uint8_t VAL_GROUP_UART1INPROT = 0x73; +const uint8_t VAL_GROUP_UART1OUTPROT = 0x74; +const uint8_t VAL_GROUP_UART2INPROT = 0x75; +const uint8_t VAL_GROUP_UART2OUTPROT = 0x76; +const uint8_t VAL_GROUP_USBINPROT = 0x77; +const uint8_t VAL_GROUP_USBOUTPROT = 0x78; + +const uint8_t VAL_GROUP_UART_SIZE = VAL_SIZE_1; //All fields in UART group are currently 1 bit +const uint8_t VAL_GROUP_I2C_SIZE = VAL_SIZE_8; //All fields in I2C group are currently 1 byte + +const uint8_t VAL_ID_I2C_ADDRESS = 0x01; + +//Below are the key values for a given configuration setting + +//CFG-BDS: BeiDou system configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_BDS_USE_PRN_1_TO_5 = 0x10340014; // Use BeiDou geostationary satellites (PRN 1-5) + +//CFG-GEOFENCE: Geofencing configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_GEOFENCE_CONFLVL = 0x20240011; // Required confidence level for state evaluation +const uint32_t UBLOX_CFG_GEOFENCE_USE_PIO = 0x10240012; // Use PIO combined fence state output +const uint32_t UBLOX_CFG_GEOFENCE_PINPOL = 0x20240013; // PIO pin polarity +const uint32_t UBLOX_CFG_GEOFENCE_PIN = 0x20240014; // PIO pin number +const uint32_t UBLOX_CFG_GEOFENCE_USE_FENCE1 = 0x10240020; // Use frst geofence +const uint32_t UBLOX_CFG_GEOFENCE_FENCE1_LAT = 0x40240021; // Latitude of the first geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE1_LON = 0x40240022; // Longitude of the first geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE1_RAD = 0x40240023; // Radius of the first geofence circle +const uint32_t UBLOX_CFG_GEOFENCE_USE_FENCE2 = 0x10240030; // Use second geofence +const uint32_t UBLOX_CFG_GEOFENCE_FENCE2_LAT = 0x40240031; // Latitude of the second geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE2_LON = 0x40240032; // Longitude of the second geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE2_RAD = 0x40240033; // Radius of the second geofence circle +const uint32_t UBLOX_CFG_GEOFENCE_USE_FENCE3 = 0x10240040; // Use third geofence +const uint32_t UBLOX_CFG_GEOFENCE_FENCE3_LAT = 0x40240041; // Latitude of the third geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE3_LON = 0x40240042; // Longitude of the third geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE3_RAD = 0x40240043; // Radius of the third geofence circle +const uint32_t UBLOX_CFG_GEOFENCE_USE_FENCE4 = 0x10240050; // Use fourth geofence +const uint32_t UBLOX_CFG_GEOFENCE_FENCE4_LAT = 0x40240051; // Latitude of the fourth geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE4_LON = 0x40240052; // Longitude of the fourth geofence circle center +const uint32_t UBLOX_CFG_GEOFENCE_FENCE4_RAD = 0x40240053; // Radius of the fourth geofence circle + +//CFG-HW: Hardware configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_HW_ANT_CFG_VOLTCTRL = 0x10a3002e; // Active antenna voltage control flag +const uint32_t UBLOX_CFG_HW_ANT_CFG_SHORTDET = 0x10a3002f; // Short antenna detection flag +const uint32_t UBLOX_CFG_HW_ANT_CFG_SHORTDET_POL = 0x10a30030; // Short antenna detection polarity +const uint32_t UBLOX_CFG_HW_ANT_CFG_OPENDET = 0x10a30031; // Open antenna detection flag +const uint32_t UBLOX_CFG_HW_ANT_CFG_OPENDET_POL = 0x10a30032; // Open antenna detection polarity +const uint32_t UBLOX_CFG_HW_ANT_CFG_PWRDOWN = 0x10a30033; // Power down antenna flag +const uint32_t UBLOX_CFG_HW_ANT_CFG_PWRDOWN_POL = 0x10a30034; // Power down antenna logic polarity +const uint32_t UBLOX_CFG_HW_ANT_CFG_RECOVER = 0x10a30035; // Automatic recovery from short state flag +const uint32_t UBLOX_CFG_HW_ANT_SUP_SWITCH_PIN = 0x20a30036; // ANT1 PIO number +const uint32_t UBLOX_CFG_HW_ANT_SUP_SHORT_PIN = 0x20a30037; // ANT0 PIO number +const uint32_t UBLOX_CFG_HW_ANT_SUP_OPEN_PIN = 0x20a30038; // ANT2 PIO number +const uint32_t UBLOX_CFG_HW_ANT_SUP_ENGINE = 0x20a30054; // Antenna supervisor engine selection +const uint32_t UBLOX_CFG_HW_ANT_SUP_SHORT_THR = 0x20a30055; // Antenna supervisor MADC engine short detection threshold +const uint32_t UBLOX_CFG_HW_ANT_SUP_OPEN_THR = 0x20a30056; // Antenna supervisor MADC engine open detection threshold + +//CFG-I2C: Configuration of the I2C interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_I2C_ADDRESS = 0x20510001; // I2C slave address of the receiver (7 bits) +const uint32_t UBLOX_CFG_I2C_EXTENDEDTIMEOUT = 0x10510002; // Flag to disable timeouting the interface after 1.5 s +const uint32_t UBLOX_CFG_I2C_ENABLED = 0x10510003; // Flag to indicate if the I2C interface should be enabled + +//CFG-I2CINPROT: Input protocol configuration of the I2C interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_I2CINPROT_UBX = 0x10710001; // Flag to indicate if UBX should be an input protocol on I2C +const uint32_t UBLOX_CFG_I2CINPROT_NMEA = 0x10710002; // Flag to indicate if NMEA should be an input protocol on I2C +const uint32_t UBLOX_CFG_I2CINPROT_RTCM3X = 0x10710004; // Flag to indicate if RTCM3X should be an input protocol on I2C + +//CFG-I2COUTPROT: Output protocol configuration of the I2C interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_I2COUTPROT_UBX = 0x10720001; // Flag to indicate if UBX should be an output protocol on I2C +const uint32_t UBLOX_CFG_I2COUTPROT_NMEA = 0x10720002; // Flag to indicate if NMEA should be an output protocol on I2C +const uint32_t UBLOX_CFG_I2COUTPROT_RTCM3X = 0x10720004; // Flag to indicate if RTCM3X should be an output protocol on I2C + +//CFG-INFMSG: Information message configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_INFMSG_UBX_I2C = 0x20920001; // Information message enable flags for the UBX protocol on the I2C interface +const uint32_t UBLOX_CFG_INFMSG_UBX_UART1 = 0x20920002; // Information message enable flags for the UBX protocol on the UART1 interface +const uint32_t UBLOX_CFG_INFMSG_UBX_UART2 = 0x20920003; // Information message enable flags for the UBX protocol on the UART2 interface +const uint32_t UBLOX_CFG_INFMSG_UBX_USB = 0x20920004; // Information message enable flags for the UBX protocol on the USB interface +const uint32_t UBLOX_CFG_INFMSG_UBX_SPI = 0x20920005; // Information message enable flags for the UBX protocol on the SPI interface +const uint32_t UBLOX_CFG_INFMSG_NMEA_I2C = 0x20920006; // Information message enable flags for the NMEA protocol on the I2C interface +const uint32_t UBLOX_CFG_INFMSG_NMEA_UART1 = 0x20920007; // Information message enable flags for the NMEA protocol on the UART1 interface +const uint32_t UBLOX_CFG_INFMSG_NMEA_UART2 = 0x20920008; // Information message enable flags for the NMEA protocol on the UART2 interface +const uint32_t UBLOX_CFG_INFMSG_NMEA_USB = 0x20920009; // Information message enable flags for the NMEA protocol on the USB interface +const uint32_t UBLOX_CFG_INFMSG_NMEA_SPI = 0x2092000a; // Information message enable flags for the NMEA protocol on the SPI interface + +//CFG-ITFM: Jamming and interference monitor configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_ITFM_BBTHRESHOLD = 0x20410001; // Broadband jamming detection threshold +const uint32_t UBLOX_CFG_ITFM_CWTHRESHOLD = 0x20410002; // CW jamming detection threshold +const uint32_t UBLOX_CFG_ITFM_ENABLE = 0x1041000d; // Enable interference detection +const uint32_t UBLOX_CFG_ITFM_ANTSETTING = 0x20410010; // Antenna setting +const uint32_t UBLOX_CFG_ITFM_ENABLE_AUX = 0x10410013; // Scan auxiliary bands + +//CFG-LOGFILTER: Data logger configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_LOGFILTER_RECORD_ENA = 0x10de0002; // Recording enabled +const uint32_t UBLOX_CFG_LOGFILTER_ONCE_PER_WAKE_UP_ENA = 0x10de0003; // Once per wake up +const uint32_t UBLOX_CFG_LOGFILTER_APPLY_ALL_FILTERS = 0x10de0004; // Apply all filter settings +const uint32_t UBLOX_CFG_LOGFILTER_MIN_INTERVAL = 0x30de0005; // Minimum time interval between loggedpositions +const uint32_t UBLOX_CFG_LOGFILTER_TIME_THRS = 0x30de0006; // Time threshold +const uint32_t UBLOX_CFG_LOGFILTER_SPEED_THRS = 0x30de0007; // Speed threshold +const uint32_t UBLOX_CFG_LOGFILTER_POSITION_THRS = 0x40de0008; // Position threshold + +//CFG-MOT: Motion detector configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_MOT_GNSSSPEED_THRS = 0x20250038; // GNSS speed threshold below which platform is considered as stationary (a.k.a. static hold threshold) +const uint32_t UBLOX_CFG_MOT_GNSSDIST_THRS = 0x3025003b; // Distance above which GNSS-based stationary motion is exit (a.k.a. static hold distance threshold) + +// CFG-MSGOUT: Message output configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +// For each message and port a separate output rate (per second, per epoch) can be configured. +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_DTM_I2C = 0x209100a6; //Output rate of the NMEA-GX-DTM message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_DTM_SPI = 0x209100aa; //Output rate of the NMEA-GX-DTM message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_DTM_UART1 = 0x209100a7; //Output rate of the NMEA-GX-DTM message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_DTM_UART2 = 0x209100a8; //Output rate of the NMEA-GX-DTM message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_DTM_USB = 0x209100a9; //Output rate of the NMEA-GX-DTM message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GBS_I2C = 0x209100dd; //Output rate of the NMEA-GX-GBS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GBS_SPI = 0x209100e1; //Output rate of the NMEA-GX-GBS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GBS_UART1 = 0x209100de; //Output rate of the NMEA-GX-GBS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GBS_UART2 = 0x209100df; //Output rate of the NMEA-GX-GBS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GBS_USB = 0x209100e0; //Output rate of the NMEA-GX-GBS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C = 0x209100ba; //Output rate of the NMEA-GX-GGA message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GGA_SPI = 0x209100be; //Output rate of the NMEA-GX-GGA message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GGA_UART1 = 0x209100bb; //Output rate of the NMEA-GX-GGA message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GGA_UART2 = 0x209100bc; //Output rate of the NMEA-GX-GGA message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GGA_USB = 0x209100bd; //Output rate of the NMEA-GX-GGA message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GLL_I2C = 0x209100c9; //Output rate of the NMEA-GX-GLL message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GLL_SPI = 0x209100cd; //Output rate of the NMEA-GX-GLL message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GLL_UART1 = 0x209100ca; //Output rate of the NMEA-GX-GLL message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GLL_UART2 = 0x209100cb; //Output rate of the NMEA-GX-GLL message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GLL_USB = 0x209100cc; //Output rate of the NMEA-GX-GLL message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GNS_I2C = 0x209100b5; //Output rate of the NMEA-GX-GNS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GNS_SPI = 0x209100b9; //Output rate of the NMEA-GX-GNS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GNS_UART1 = 0x209100b6; //Output rate of the NMEA-GX-GNS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GNS_UART2 = 0x209100b7; //Output rate of the NMEA-GX-GNS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GNS_USB = 0x209100b8; //Output rate of the NMEA-GX-GNS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GRS_I2C = 0x209100ce; //Output rate of the NMEA-GX-GRS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GRS_SPI = 0x209100d2; //Output rate of the NMEA-GX-GRS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GRS_UART1 = 0x209100cf; //Output rate of the NMEA-GX-GRS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GRS_UART2 = 0x209100d0; //Output rate of the NMEA-GX-GRS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GRS_USB = 0x209100d1; //Output rate of the NMEA-GX-GRS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSA_I2C = 0x209100bf; //Output rate of the NMEA-GX-GSA message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSA_SPI = 0x209100c3; //Output rate of the NMEA-GX-GSA message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSA_UART1 = 0x209100c0; //Output rate of the NMEA-GX-GSA message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSA_UART2 = 0x209100c1; //Output rate of the NMEA-GX-GSA message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSA_USB = 0x209100c2; //Output rate of the NMEA-GX-GSA message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GST_I2C = 0x209100d3; //Output rate of the NMEA-GX-GST message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GST_SPI = 0x209100d7; //Output rate of the NMEA-GX-GST message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GST_UART1 = 0x209100d4; //Output rate of the NMEA-GX-GST message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GST_UART2 = 0x209100d5; //Output rate of the NMEA-GX-GST message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GST_USB = 0x209100d6; //Output rate of the NMEA-GX-GST message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSV_I2C = 0x209100c4; //Output rate of the NMEA-GX-GSV message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSV_SPI = 0x209100c8; //Output rate of the NMEA-GX-GSV message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSV_UART1 = 0x209100c5; //Output rate of the NMEA-GX-GSV message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSV_UART2 = 0x209100c6; //Output rate of the NMEA-GX-GSV message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_GSV_USB = 0x209100c7; //Output rate of the NMEA-GX-GSV message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RLM_I2C = 0x20910400; //Output rate of the NMEA-GX-RLM message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RLM_SPI = 0x20910404; //Output rate of the NMEA-GX-RLM message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RLM_UART1 = 0x20910401; //Output rate of the NMEA-GX-RLM message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RLM_UART2 = 0x20910402; //Output rate of the NMEA-GX-RLM message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RLM_USB = 0x20910403; //Output rate of the NMEA-GX-RLM message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RMC_I2C = 0x209100ab; //Output rate of the NMEA-GX-RMC message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RMC_SPI = 0x209100af; //Output rate of the NMEA-GX-RMC message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RMC_UART1 = 0x209100ac; //Output rate of the NMEA-GX-RMC message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RMC_UART2 = 0x209100ad; //Output rate of the NMEA-GX-RMC message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_RMC_USB = 0x209100ae; //Output rate of the NMEA-GX-RMC message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VLW_I2C = 0x209100e7; //Output rate of the NMEA-GX-VLW message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VLW_SPI = 0x209100eb; //Output rate of the NMEA-GX-VLW message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VLW_UART1 = 0x209100e8; //Output rate of the NMEA-GX-VLW message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VLW_UART2 = 0x209100e9; //Output rate of the NMEA-GX-VLW message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VLW_USB = 0x209100ea; //Output rate of the NMEA-GX-VLW message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VTG_I2C = 0x209100b0; //Output rate of the NMEA-GX-VTG message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VTG_SPI = 0x209100b4; //Output rate of the NMEA-GX-VTG message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VTG_UART1 = 0x209100b1; //Output rate of the NMEA-GX-VTG message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VTG_UART2 = 0x209100b2; //Output rate of the NMEA-GX-VTG message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_VTG_USB = 0x209100b3; //Output rate of the NMEA-GX-VTG message on port USB +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_I2C = 0x209100d8; //Output rate of the NMEA-GX-ZDA message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_SPI = 0x209100dc; //Output rate of the NMEA-GX-ZDA message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_UART1 = 0x209100d9; //Output rate of the NMEA-GX-ZDA message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_UART2 = 0x209100da; //Output rate of the NMEA-GX-ZDA message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_USB = 0x209100db; //Output rate of the NMEA-GX-ZDA message on port USB +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYP_I2C = 0x209100ec; //Output rate of the NMEA-GX-PUBX00 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYP_SPI = 0x209100f0; //Output rate of the NMEA-GX-PUBX00 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYP_UART1 = 0x209100ed; //Output rate of the NMEA-GX-PUBX00 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYP_UART2 = 0x209100ee; //Output rate of the NMEA-GX-PUBX00 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYP_USB = 0x209100ef; //Output rate of the NMEA-GX-PUBX00 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYS_I2C = 0x209100f1; //Output rate of the NMEA-GX-PUBX03 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYS_SPI = 0x209100f5; //Output rate of the NMEA-GX-PUBX03 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYS_UART1 = 0x209100f2; //Output rate of the NMEA-GX-PUBX03 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYS_UART2 = 0x209100f3; //Output rate of the NMEA-GX-PUBX03 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYS_USB = 0x209100f4; //Output rate of the NMEA-GX-PUBX03 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYT_I2C = 0x209100f6; //Output rate of the NMEA-GX-PUBX04 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYT_SPI = 0x209100fa; //Output rate of the NMEA-GX-PUBX04 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYT_UART1 = 0x209100f7; //Output rate of the NMEA-GX-PUBX04 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYT_UART2 = 0x209100f8; //Output rate of the NMEA-GX-PUBX04 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_PUBX_ID_POLYT_USB = 0x209100f9; //Output rate of the NMEA-GX-PUBX04 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_I2C = 0x209102bd; //Output rate of the RTCM-3X-TYPE1005 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_SPI = 0x209102c1; //Output rate of the RTCM-3X-TYPE1005 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_UART1 = 0x209102be;//Output rate of the RTCM-3X-TYPE1005 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_UART2 = 0x209102bf;//Output rate of the RTCM-3X-TYPE1005 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1005_USB = 0x209102c0; //Output rate of the RTCM-3X-TYPE1005 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_I2C = 0x2091035e; //Output rate of the RTCM-3X-TYPE1074 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_SPI = 0x20910362; //Output rate of the RTCM-3X-TYPE1074 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_UART1 = 0x2091035f;//Output rate of the RTCM-3X-TYPE1074 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_UART2 = 0x20910360;//Output rate of the RTCM-3X-TYPE1074 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1074_USB = 0x20910361; //Output rate of the RTCM-3X-TYPE1074 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_I2C = 0x209102cc; //Output rate of the RTCM-3X-TYPE1077 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_SPI = 0x209102d0; //Output rate of the RTCM-3X-TYPE1077 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_UART1 = 0x209102cd;//Output rate of the RTCM-3X-TYPE1077 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_UART2 = 0x209102ce;//Output rate of the RTCM-3X-TYPE1077 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1077_USB = 0x209102cf; //Output rate of the RTCM-3X-TYPE1077 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_I2C = 0x20910363; //Output rate of the RTCM-3X-TYPE1084 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_SPI = 0x20910367; //Output rate of the RTCM-3X-TYPE1084 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_UART1 = 0x20910364;//Output rate of the RTCM-3X-TYPE1084 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_UART2 = 0x20910365;//Output rate of the RTCM-3X-TYPE1084 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1084_USB = 0x20910366; //Output rate of the RTCM-3X-TYPE1084 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_I2C = 0x209102d1; //Output rate of the RTCM-3X-TYPE1087 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_SPI = 0x209102d5; //Output rate of the RTCM-3X-TYPE1087 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_UART1 = 0x209102d2;//Output rate of the RTCM-3X-TYPE1087 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_UART2 = 0x209102d3;//Output rate of the RTCM-3X-TYPE1087 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1087_USB = 0x209102d4; //Output rate of the RTCM-3X-TYPE1087 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_I2C = 0x20910368; //Output rate of the RTCM-3X-TYPE1094 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_SPI = 0x2091036c; //Output rate of the RTCM-3X-TYPE1094 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_UART1 = 0x20910369;//Output rate of the RTCM-3X-TYPE1094 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_UART2 = 0x2091036a;//Output rate of the RTCM-3X-TYPE1094 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1094_USB = 0x2091036b; //Output rate of the RTCM-3X-TYPE1094 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_I2C = 0x20910318; //Output rate of the RTCM-3X-TYPE1097 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_SPI = 0x2091031c; //Output rate of the RTCM-3X-TYPE1097 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_UART1 = 0x20910319;//Output rate of the RTCM-3X-TYPE1097 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_UART2 = 0x2091031a;//Output rate of the RTCM-3X-TYPE1097 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1097_USB = 0x2091031b; //Output rate of the RTCM-3X-TYPE1097 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_I2C = 0x2091036d; //Output rate of the RTCM-3X-TYPE1124 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_SPI = 0x20910371; //Output rate of the RTCM-3X-TYPE1124 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_UART1 = 0x2091036e;//Output rate of the RTCM-3X-TYPE1124 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_UART2 = 0x2091036f;//Output rate of the RTCM-3X-TYPE1124 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1124_USB = 0x20910370; //Output rate of the RTCM-3X-TYPE1124 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_I2C = 0x209102d6; //Output rate of the RTCM-3X-TYPE1127 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_SPI = 0x209102da; //Output rate of the RTCM-3X-TYPE1127 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_UART1 = 0x209102d7;//Output rate of the RTCM-3X-TYPE1127 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_UART2 = 0x209102d8;//Output rate of the RTCM-3X-TYPE1127 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1127_USB = 0x209102d9; //Output rate of the RTCM-3X-TYPE1127 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_I2C = 0x20910303; //Output rate of the RTCM-3X-TYPE1230 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_SPI = 0x20910307; //Output rate of the RTCM-3X-TYPE1230 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_UART1 = 0x20910304;//Output rate of the RTCM-3X-TYPE1230 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_UART2 = 0x20910305;//Output rate of the RTCM-3X-TYPE1230 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE1230_USB = 0x20910306; //Output rate of the RTCM-3X-TYPE1230 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_I2C = 0x209102fe;//Output rate of the RTCM-3X-TYPE4072_0 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_SPI = 0x20910302;//Output rate of the RTCM-3X-TYPE4072_0 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_UART1 = 0x209102ff; //Output rate of the RTCM-3X-TYPE4072_0 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_UART2 = 0x20910300; //Output rate of the RTCM-3X-TYPE4072_0 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_0_USB = 0x20910301;//Output rate of the RTCM-3X-TYPE4072_0 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_I2C = 0x20910381;//Output rate of the RTCM-3X-TYPE4072_1 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_SPI = 0x20910385;//Output rate of the RTCM-3X-TYPE4072_1 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_UART1 = 0x20910382; //Output rate of the RTCM-3X-TYPE4072_1 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_UART2 = 0x20910383; //Output rate of the RTCM-3X-TYPE4072_1 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_RTCM_3X_TYPE4072_1_USB = 0x20910384;//Output rate of the RTCM-3X-TYPE4072_1 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_LOG_INFO_I2C = 0x20910259; //Output rate of the UBX-LOG-INFO message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_LOG_INFO_SPI = 0x2091025d; //Output rate of the UBX-LOG-INFO message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_LOG_INFO_UART1 = 0x2091025a; //Output rate of the UBX-LOG-INFO message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_LOG_INFO_UART2 = 0x2091025b; //Output rate of the UBX-LOG-INFO message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_LOG_INFO_USB = 0x2091025c; //Output rate of the UBX-LOG-INFO message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_COMMS_I2C = 0x2091034f; //Output rate of the UBX-MON-COMMS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_COMMS_SPI = 0x20910353; //Output rate of the UBX-MON-COMMS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_COMMS_UART1 = 0x20910350; //Output rate of the UBX-MON-COMMS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_COMMS_UART2 = 0x20910351; //Output rate of the UBX-MON-COMMS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_COMMS_USB = 0x20910352; //Output rate of the UBX-MON-COMMS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW2_I2C = 0x209101b9; //Output rate of the UBX-MON-HW2 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW2_SPI = 0x209101bd; //Output rate of the UBX-MON-HW2 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW2_UART1 = 0x209101ba; //Output rate of the UBX-MON-HW2 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW2_UART2 = 0x209101bb; //Output rate of the UBX-MON-HW2 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW2_USB = 0x209101bc; //Output rate of the UBX-MON-HW2 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW3_I2C = 0x20910354; //Output rate of the UBX-MON-HW3 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW3_SPI = 0x20910358; //Output rate of the UBX-MON-HW3 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW3_UART1 = 0x20910355; //Output rate of the UBX-MON-HW3 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW3_UART2 = 0x20910356; //Output rate of the UBX-MON-HW3 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW3_USB = 0x20910357; //Output rate of the UBX-MON-HW3 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW_I2C = 0x209101b4; //Output rate of the UBX-MON-HW message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW_SPI = 0x209101b8; //Output rate of the UBX-MON-HW message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW_UART1 = 0x209101b5; //Output rate of the UBX-MON-HW message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW_UART2 = 0x209101b6; //Output rate of the UBX-MON-HW message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_HW_USB = 0x209101b7; //Output rate of the UBX-MON-HW message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_IO_I2C = 0x209101a5; //Output rate of the UBX-MON-IO message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_IO_SPI = 0x209101a9; //Output rate of the UBX-MON-IO message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_IO_UART1 = 0x209101a6; //Output rate of the UBX-MON-IO message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_IO_UART2 = 0x209101a7; //Output rate of the UBX-MON-IO message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_IO_USB = 0x209101a8; //Output rate of the UBX-MON-IO message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_I2C = 0x20910196; //Output rate of the UBX-MON-MSGPP message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_SPI = 0x2091019a; //Output rate of the UBX-MON-MSGPP message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_UART1 = 0x20910197; //Output rate of the UBX-MON-MSGPP message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_UART2 = 0x20910198; //Output rate of the UBX-MON-MSGPP message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_MSGPP_USB = 0x20910199; //Output rate of the UBX-MON-MSGPP message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RF_I2C = 0x20910359; //Output rate of the UBX-MON-RF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RF_SPI = 0x2091035d; //Output rate of the UBX-MON-RF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RF_UART1 = 0x2091035a; //Output rate of the UBX-MON-RF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RF_UART2 = 0x2091035b; //Output rate of the UBX-MON-RF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RF_USB = 0x2091035c; // Output rate of the UBX-MON-RF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_I2C = 0x209101a0; // Output rate of the UBX-MON-RXBUF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_SPI = 0x209101a4; // Output rate of the UBX-MON-RXBUF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_UART1 = 0x209101a1; // Output rate of the UBX-MON-RXBUF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_UART2 = 0x209101a2; // Output rate of the UBX-MON-RXBUF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXBUF_USB = 0x209101a3; // Output rate of the UBX-MON-RXBUF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXR_I2C = 0x20910187; // Output rate of the UBX-MON-RXR message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXR_SPI = 0x2091018b; // Output rate of the UBX-MON-RXR message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXR_UART1 = 0x20910188; // Output rate of the UBX-MON-RXR message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXR_UART2 = 0x20910189; // Output rate of the UBX-MON-RXR message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_RXR_USB = 0x2091018a; // Output rate of the UBX-MON-RXR message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_SPAN_I2C = 0x2091038b; // Output rate of the UBX-MON-SPAN message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_SPAN_SPI = 0x2091038f; // Output rate of the UBX-MON-SPAN message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_SPAN_UART1 = 0x2091038c; // Output rate of the UBX-MON-SPAN message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_SPAN_UART2 = 0x2091038d; // Output rate of the UBX-MON-SPAN message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_SPAN_USB = 0x2091038e; // Output rate of the UBX-MON-SPAN message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_I2C = 0x2091019b; // Output rate of the UBX-MON-TXBUF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_SPI = 0x2091019f; // Output rate of the UBX-MON-TXBUF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_UART1 = 0x2091019c; // Output rate of the UBX-MON-TXBUF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_UART2 = 0x2091019d; // Output rate of the UBX-MON-TXBUF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_MON_TXBUF_USB = 0x2091019e; // Output rate of the UBX-MON-TXBUF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ATT_I2C = 0x2091001f; // Output rate of the UBX_NAV_ATT message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ATT_SPI = 0x20910023; // Output rate of the UBX_NAV_ATT message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ATT_UART1 = 0x20910020; // Output rate of the UBX_NAV_ATT message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ATT_UART2 = 0x20910021; // Output rate of the UBX_NAV_ATT message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ATT_USB = 0x20910022; // Output rate of the UBX_NAV_ATT message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_I2C = 0x20910065; // Output rate of the UBX-NAV-CLOCK message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_SPI = 0x20910069; // Output rate of the UBX-NAV-CLOCK message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_UART1 = 0x20910066; // Output rate of the UBX-NAV-CLOCK message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_UART2 = 0x20910067; // Output rate of the UBX-NAV-CLOCK message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_CLOCK_USB = 0x20910068; // Output rate of the UBX-NAV-CLOCK message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_DOP_I2C = 0x20910038; // Output rate of the UBX-NAV-DOP message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_DOP_SPI = 0x2091003c; // Output rate of the UBX-NAV-DOP message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_DOP_UART1 = 0x20910039; // Output rate of the UBX-NAV-DOP message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_DOP_UART2 = 0x2091003a; // Output rate of the UBX-NAV-DOP message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_DOP_USB = 0x2091003b; // Output rate of the UBX-NAV-DOP message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_EOE_I2C = 0x2091015f; // Output rate of the UBX-NAV-EOE message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_EOE_SPI = 0x20910163; // Output rate of the UBX-NAV-EOE message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_EOE_UART1 = 0x20910160; // Output rate of the UBX-NAV-EOE message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_EOE_UART2 = 0x20910161; // Output rate of the UBX-NAV-EOE message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_EOE_USB = 0x20910162; // Output rate of the UBX-NAV-EOE message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_I2C = 0x209100a1; // Output rate of the UBX-NAV-GEOFENCE message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_SPI = 0x209100a5; // Output rate of the UBX-NAV-GEOFENCE message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_UART1 = 0x209100a2;// Output rate of the UBX-NAV-GEOFENCE message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_UART2 = 0x209100a3;// Output rate of the UBX-NAV-GEOFENCE message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_GEOFENCE_USB = 0x209100a4; // Output rate of the UBX-NAV-GEOFENCE message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_I2C = 0x2091002e;// Output rate of the UBX-NAV-HPPOSECEF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_SPI = 0x20910032;// Output rate of the UBX-NAV-HPPOSECEF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_UART1 = 0x2091002f;// Output rate of the UBX-NAV-HPPOSECEF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_UART2 = 0x20910030;// Output rate of the UBX-NAV-HPPOSECEF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSECEF_USB = 0x20910031;// Output rate of the UBX-NAV-HPPOSECEF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_I2C = 0x20910033; // Output rate of the UBX-NAV-HPPOSLLH message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_SPI = 0x20910037; // Output rate of the UBX-NAV-HPPOSLLH message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_UART1 = 0x20910034;// Output rate of the UBX-NAV-HPPOSLLH message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_UART2 = 0x20910035;// Output rate of the UBX-NAV-HPPOSLLH message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_HPPOSLLH_USB = 0x20910036; // Output rate of the UBX-NAV-HPPOSLLH message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ODO_I2C = 0x2091007e; // Output rate of the UBX-NAV-ODO message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ODO_SPI = 0x20910082; // Output rate of the UBX-NAV-ODO message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ODO_UART1 = 0x2091007f; // Output rate of the UBX-NAV-ODO message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ODO_UART2 = 0x20910080; // Output rate of the UBX-NAV-ODO message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ODO_USB = 0x20910081; // Output rate of the UBX-NAV-ODO message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ORB_I2C = 0x20910010; // Output rate of the UBX-NAV-ORB message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ORB_SPI = 0x20910014; // Output rate of the UBX-NAV-ORB message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ORB_UART1 = 0x20910011; // Output rate of the UBX-NAV-ORB message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ORB_UART2 = 0x20910012; // Output rate of the UBX-NAV-ORB message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_ORB_USB = 0x20910013; // Output rate of the UBX-NAV-ORB message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_I2C = 0x20910024; // Output rate of the UBX-NAV-POSECEF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_SPI = 0x20910028; // Output rate of the UBX-NAV-POSECEF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_UART1 = 0x20910025;// Output rate of the UBX-NAV-POSECEF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_UART2 = 0x20910026;// Output rate of the UBX-NAV-POSECEF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSECEF_USB = 0x20910027; // Output rate of the UBX-NAV-POSECEF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_I2C = 0x20910029; // Output rate of the UBX-NAV-POSLLH message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_SPI = 0x2091002d; // Output rate of the UBX-NAV-POSLLH message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_UART1 = 0x2091002a; // Output rate of the UBX-NAV-POSLLH message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_UART2 = 0x2091002b; // Output rate of the UBX-NAV-POSLLH message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_POSLLH_USB = 0x2091002c; // Output rate of the UBX-NAV-POSLLH message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_PVT_I2C = 0x20910006; // Output rate of the UBX-NAV-PVT message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_PVT_SPI = 0x2091000a; // Output rate of the UBX-NAV-PVT message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_PVT_UART1 = 0x20910007; // Output rate of the UBX-NAV-PVT message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_PVT_UART2 = 0x20910008; // Output rate of the UBX-NAV-PVT message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_PVT_USB = 0x20910009; // Output rate of the UBX-NAV-PVT message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_I2C = 0x2091008d; // Output rate of the UBX-NAV-RELPOSNED message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_SPI = 0x20910091;// Output rate of the UBX-NAV-RELPOSNED message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_UART1 = 0x2091008e;// Output rate of the UBX-NAV-RELPOSNED message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_UART2 = 0x2091008f;// Output rate of the UBX-NAV-RELPOSNED message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_RELPOSNED_USB = 0x20910090;// Output rate of the UBX-NAV-RELPOSNED message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SAT_I2C = 0x20910015; // Output rate of the UBX-NAV-SAT message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SAT_SPI = 0x20910019; // Output rate of the UBX-NAV-SAT message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SAT_UART1 = 0x20910016; // Output rate of the UBX-NAV-SAT message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SAT_UART2 = 0x20910017; // Output rate of the UBX-NAV-SAT message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SAT_USB = 0x20910018; // Output rate of the UBX-NAV-SAT message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SBAS_I2C = 0x2091006a; // Output rate of the UBX-NAV-SBAS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SBAS_SPI = 0x2091006e; // Output rate of the UBX-NAV-SBAS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SBAS_UART1 = 0x2091006b; // Output rate of the UBX-NAV-SBAS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SBAS_UART2 = 0x2091006c; // Output rate of the UBX-NAV-SBAS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SBAS_USB = 0x2091006d; // Output rate of the UBX-NAV-SBAS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SIG_I2C = 0x20910345; // Output rate of the UBX-NAV-SIG message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SIG_SPI = 0x20910349; // Output rate of the UBX-NAV-SIG message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SIG_UART1 = 0x20910346; // Output rate of the UBX-NAV-SIG message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SIG_UART2 = 0x20910347; // Output rate of the UBX-NAV-SIG message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SIG_USB = 0x20910348; // Output rate of the UBX-NAV-SIG message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SLAS_I2C = 0x20910336; // Output rate of the UBX-NAV-SLAS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SLAS_SPI = 0x2091033a; // Output rate of the UBX-NAV-SLAS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SLAS_UART1 = 0x20910337; // Output rate of the UBX-NAV-SLAS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SLAS_UART2 = 0x20910338; // Output rate of the UBX-NAV-SLAS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SLAS_USB = 0x20910339; // Output rate of the UBX-NAV-SLAS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_I2C = 0x2091001a; // Output rate of the UBX-NAV-STATUS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_SPI = 0x2091001e; // Output rate of the UBX-NAV-STATUS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_UART1 = 0x2091001b; // Output rate of the UBX-NAV-STATUS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_UART2 = 0x2091001c; // Output rate of the UBX-NAV-STATUS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_STATUS_USB = 0x2091001d; // Output rate of the UBX-NAV-STATUS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_I2C = 0x20910088; // Output rate of the UBX-NAV-SVIN message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_SPI = 0x2091008c; // Output rate of the UBX-NAV-SVIN message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_UART1 = 0x20910089; // Output rate of the UBX-NAV-SVIN message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_UART2 = 0x2091008a; // Output rate of the UBX-NAV-SVIN message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_SVIN_USB = 0x2091008b; // Output rate of the UBX-NAV-SVIN message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_I2C = 0x20910051; // Output rate of the UBX-NAV-TIMEBDS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_SPI = 0x20910055; // Output rate of the UBX-NAV-TIMEBDS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_UART1 = 0x20910052;// Output rate of the UBX-NAV-TIMEBDS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_UART2 = 0x20910053;// Output rate of the UBX-NAV-TIMEBDS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEBDS_USB = 0x20910054; // Output rate of the UBX-NAV-TIMEBDS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_I2C = 0x20910056; // Output rate of the UBX-NAV-TIMEGAL message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_SPI = 0x2091005a; // Output rate of the UBX-NAV-TIMEGAL message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_UART1 = 0x20910057;// Output rate of the UBX-NAV-TIMEGAL message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_UART2 = 0x20910058;// Output rate of the UBX-NAV-TIMEGAL message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGAL_USB = 0x20910059; // Output rate of the UBX-NAV-TIMEGAL message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_I2C = 0x2091004c; // Output rate of the UBX-NAV-TIMEGLO message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_SPI = 0x20910050; // Output rate of the UBX-NAV-TIMEGLO message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_UART1 = 0x2091004d;// Output rate of the UBX-NAV-TIMEGLO message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_UART2 = 0x2091004e;// Output rate of the UBX-NAV-TIMEGLO message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGLO_USB = 0x2091004f; // Output rate of the UBX-NAV-TIMEGLO message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_I2C = 0x20910047; // Output rate of the UBX-NAV-TIMEGPS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_SPI = 0x2091004b; // Output rate of the UBX-NAV-TIMEGPS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_UART1 = 0x20910048;// Output rate of the UBX-NAV-TIMEGPS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_UART2 = 0x20910049;// Output rate of the UBX-NAV-TIMEGPS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEGPS_USB = 0x2091004a; // Output rate of the UBX-NAV-TIMEGPS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_I2C = 0x20910060; // Output rate of the UBX-NAV-TIMELS message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_SPI = 0x20910064; // Output rate of the UBX-NAV-TIMELS message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_UART1 = 0x20910061; // Output rate of the UBX-NAV-TIMELS message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_UART2 = 0x20910062; // Output rate of the UBX-NAV-TIMELS message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMELS_USB = 0x20910063; // Output rate of the UBX-NAV-TIMELS message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEQZSS_I2C = 0x20910386; // Output rate of the UBX-NAV-TIMEQZSSmessage on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEQZSS_SPI = 0x2091038a; // Output rate of the UBX-NAV-TIMEQZSSmessage on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEQZSS_UART1 = 0x20910387;// Output rate of the UBX-NAV-TIMEQZSSmessage on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEQZSS_UART2 = 0x20910388;// Output rate of the UBX-NAV-TIMEQZSSmessage on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEQZSS_USB = 0x20910389; // Output rate of the UBX-NAV-TIMEQZSSmessage on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_I2C = 0x2091005b; // Output rate of the UBX-NAV-TIMEUTC message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_SPI = 0x2091005f; // Output rate of the UBX-NAV-TIMEUTC message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_UART1 = 0x2091005c;// Output rate of the UBX-NAV-TIMEUTC message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_UART2 = 0x2091005d;// Output rate of the UBX-NAV-TIMEUTC message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_TIMEUTC_USB = 0x2091005e; // Output rate of the UBX-NAV-TIMEUTC message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_I2C = 0x2091003d; // Output rate of the UBX-NAV-VELECEF message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_SPI = 0x20910041; // Output rate of the UBX-NAV-VELECEF message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_UART1 = 0x2091003e;// Output rate of the UBX-NAV-VELECEF message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_UART2 = 0x2091003f;// Output rate of the UBX-NAV-VELECEF message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELECEF_USB = 0x20910040; // Output rate of the UBX-NAV-VELECEF message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_I2C = 0x20910042; // Output rate of the UBX-NAV-VELNED message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_SPI = 0x20910046; // Output rate of the UBX-NAV-VELNED message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_UART1 = 0x20910043; // Output rate of the UBX-NAV-VELNED message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_UART2 = 0x20910044; // Output rate of the UBX-NAV-VELNED message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_NAV_VELNED_USB = 0x20910045; // Output rate of the UBX-NAV-VELNED message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_I2C = 0x20910204; // Output rate of the UBX-RXM-MEASX message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_SPI = 0x20910208; // Output rate of the UBX-RXM-MEASX message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_UART1 = 0x20910205; // Output rate of the UBX-RXM-MEASX message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_UART2 = 0x20910206; // Output rate of the UBX-RXM-MEASX message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_MEASX_USB = 0x20910207; // Output rate of the UBX-RXM-MEASX message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_I2C = 0x209102a4; // Output rate of the UBX-RXM-RAWX message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_SPI = 0x209102a8; // Output rate of the UBX-RXM-RAWX message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_UART1 = 0x209102a5; // Output rate of the UBX-RXM-RAWX message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_UART2 = 0x209102a6; // Output rate of the UBX-RXM-RAWX message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RAWX_USB = 0x209102a7; // Output rate of the UBX-RXM-RAWX message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RLM_I2C = 0x2091025e; // Output rate of the UBX-RXM-RLM message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RLM_SPI = 0x20910262; // Output rate of the UBX-RXM-RLM message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RLM_UART1 = 0x2091025f; // Output rate of the UBX-RXM-RLM message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RLM_UART2 = 0x20910260; // Output rate of the UBX-RXM-RLM message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RLM_USB = 0x20910261; // Output rate of the UBX-RXM-RLM message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_I2C = 0x20910268; // Output rate of the UBX-RXM-RTCM message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_SPI = 0x2091026c; // Output rate of the UBX-RXM-RTCM message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_UART1 = 0x20910269; // Output rate of the UBX-RXM-RTCM message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_UART2 = 0x2091026a; // Output rate of the UBX-RXM-RTCM message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_RTCM_USB = 0x2091026b; // Output rate of the UBX-RXM-RTCM message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_I2C = 0x20910231; // Output rate of the UBX-RXM-SFRBX message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_SPI = 0x20910235; // Output rate of the UBX-RXM-SFRBX message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_UART1 = 0x20910232; // Output rate of the UBX-RXM-SFRBX message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_UART2 = 0x20910233; // Output rate of the UBX-RXM-SFRBX message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_RXM_SFRBX_USB = 0x20910234; // Output rate of the UBX-RXM-SFRBX message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TM2_I2C = 0x20910178; // Output rate of the UBX-TIM-TM2 message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TM2_SPI = 0x2091017c; // Output rate of the UBX-TIM-TM2 message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TM2_UART1 = 0x20910179; // Output rate of the UBX-TIM-TM2 message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TM2_UART2 = 0x2091017a; // Output rate of the UBX-TIM-TM2 message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TM2_USB = 0x2091017b; // Output rate of the UBX-TIM-TM2 message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TP_I2C = 0x2091017d; // Output rate of the UBX-TIM-TP message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TP_SPI = 0x20910181; // Output rate of the UBX-TIM-TP message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TP_UART1 = 0x2091017e; // Output rate of the UBX-TIM-TP message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TP_UART2 = 0x2091017f; // Output rate of the UBX-TIM-TP message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_TP_USB = 0x20910180; // Output rate of the UBX-TIM-TP message on port USB +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_I2C = 0x20910092; // Output rate of the UBX-TIM-VRFY message on port I2C +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_SPI = 0x20910096; // Output rate of the UBX-TIM-VRFY message on port SPI +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_UART1 = 0x20910093; // Output rate of the UBX-TIM-VRFY message on port UART1 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_UART2 = 0x20910094; // Output rate of the UBX-TIM-VRFY message on port UART2 +const uint32_t UBLOX_CFG_MSGOUT_UBX_TIM_VRFY_USB = 0x20910095; // Output rate of the UBX-TIM-VRFY message on port USB + +//CFG-NAVHPG: High precision navigation configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_NAVHPG_DGNSSMODE = 0x20140011; // Differential corrections mode + +//CFG-NAVSPG: Standard precision navigation configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_NAVSPG_FIXMODE = 0x20110011; // Position fix mode +const uint32_t UBLOX_CFG_NAVSPG_INIFIX3D = 0x10110013; // Initial fix must be a 3D fix +const uint32_t UBLOX_CFG_NAVSPG_WKNROLLOVER = 0x30110017; // GPS week rollover number +const uint32_t UBLOX_CFG_NAVSPG_UTCSTANDARD = 0x2011001c; // UTC standard to be used +const uint32_t UBLOX_CFG_NAVSPG_DYNMODEL = 0x20110021; // Dynamic platform model +const uint32_t UBLOX_CFG_NAVSPG_ACKAIDING = 0x10110025; // Acknowledge assistance input messages +const uint32_t UBLOX_CFG_NAVSPG_USE_USRDAT = 0x10110061; // Use user geodetic datum parameters +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_MAJA = 0x50110062; // Geodetic datum semi-major axis +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_FLAT = 0x50110063; // Geodetic datum 1.0 flattening +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_DX = 0x40110064; // Geodetic datum X axis shift at the origin +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_DY = 0x40110065; // Geodetic datum Y axis shift at the origin +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_DZ = 0x40110066; // Geodetic datum Z axis shift at the origin +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_ROTX = 0x40110067; // arcsec Geodetic datum rotation about the X axis +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_ROTY = 0x40110068; // arcsec Geodetic datum rotation about the Y axis +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_ROTZ = 0x40110069; // arcsec Geodetic datum rotation about the Z axis +const uint32_t UBLOX_CFG_NAVSPG_USRDAT_SCALE = 0x4011006a; // ppm Geodetic datum scale factor +const uint32_t UBLOX_CFG_NAVSPG_INFIL_MINSVS = 0x201100a1; // Minimum number of satellites for navigation +const uint32_t UBLOX_CFG_NAVSPG_INFIL_MAXSVS = 0x201100a2; // Maximum number of satellites for navigation +const uint32_t UBLOX_CFG_NAVSPG_INFIL_MINCNO = 0x201100a3; // Minimum satellite signal level for navigation +const uint32_t UBLOX_CFG_NAVSPG_INFIL_MINELEV = 0x201100a4; // Minimum elevation for a GNSS satellite to be used in navigation +const uint32_t UBLOX_CFG_NAVSPG_INFIL_NCNOTHRS = 0x201100aa; // Number of satellites required to have C/N0 above const uint32_t UBLOX_CFG_NAVSPG-INFIL_CNOTHRS for a fix to be attempted +const uint32_t UBLOX_CFG_NAVSPG_INFIL_CNOTHRS = 0x201100ab; // C/N0 threshold for deciding whether to attempt a fix +const uint32_t UBLOX_CFG_NAVSPG_OUTFIL_PDOP = 0x301100b1; // Output filter position DOP mask (threshold) +const uint32_t UBLOX_CFG_NAVSPG_OUTFIL_TDOP = 0x301100b2; // Output filter time DOP mask (threshold) +const uint32_t UBLOX_CFG_NAVSPG_OUTFIL_PACC = 0x301100b3; // Output filter position accuracy mask (threshold) +const uint32_t UBLOX_CFG_NAVSPG_OUTFIL_TACC = 0x301100b4; // Output filter time accuracy mask (threshold) +const uint32_t UBLOX_CFG_NAVSPG_OUTFIL_FACC = 0x301100b5; // Output filter frequency accuracy mask (threshold) +const uint32_t UBLOX_CFG_NAVSPG_CONSTR_ALT = 0x401100c1; // Fixed altitude (mean sea level) for 2D fix mode +const uint32_t UBLOX_CFG_NAVSPG_CONSTR_ALTVAR = 0x401100c2; // Fixed altitude variance for 2D mode +const uint32_t UBLOX_CFG_NAVSPG_CONSTR_DGNSSTO = 0x201100c4; // DGNSS timeout + +//CFG-NMEA: NMEA protocol configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_NMEA_PROTVER = 0x20930001; // NMEA protocol version +const uint32_t UBLOX_CFG_NMEA_MAXSVS = 0x20930002; // Maximum number of SVs to report per Talker ID +const uint32_t UBLOX_CFG_NMEA_COMPAT = 0x10930003; // Enable compatibility mode +const uint32_t UBLOX_CFG_NMEA_CONSIDER = 0x10930004; // Enable considering mode +const uint32_t UBLOX_CFG_NMEA_LIMIT82 = 0x10930005; // Enable strict limit to 82 characters maximum NMEA message length +const uint32_t UBLOX_CFG_NMEA_HIGHPREC = 0x10930006; // Enable high precision mode +const uint32_t UBLOX_CFG_NMEA_SVNUMBERING = 0x20930007; // Display configuration for SVs that do not have value defined in NMEA +const uint32_t UBLOX_CFG_NMEA_FILT_GPS = 0x10930011; // Disable reporting of GPS satellites +const uint32_t UBLOX_CFG_NMEA_FILT_SBAS = 0x10930012; // Disable reporting of SBAS satellites +const uint32_t UBLOX_CFG_NMEA_FILT_GAL = 0x10930013; // Disable reporting of Galileo satellites +const uint32_t UBLOX_CFG_NMEA_FILT_QZSS = 0x10930015; // Disable reporting of QZSS satellites +const uint32_t UBLOX_CFG_NMEA_FILT_GLO = 0x10930016; // Disable reporting of GLONASS satellites +const uint32_t UBLOX_CFG_NMEA_FILT_BDS = 0x10930017; // Disable reporting of BeiDou satellites +const uint32_t UBLOX_CFG_NMEA_OUT_INVFIX = 0x10930021; // Enable position output for failed or invalid fixes +const uint32_t UBLOX_CFG_NMEA_OUT_MSKFIX = 0x10930022; // Enable position output for invalid fixes +const uint32_t UBLOX_CFG_NMEA_OUT_INVTIME = 0x10930023; // Enable time output for invalid times +const uint32_t UBLOX_CFG_NMEA_OUT_INVDATE = 0x10930024; // Enable date output for invalid dates +const uint32_t UBLOX_CFG_NMEA_OUT_ONLYGPS = 0x10930025; // Restrict output to GPS satellites only +const uint32_t UBLOX_CFG_NMEA_OUT_FROZENCOG = 0x10930026; // Enable course over ground output even if it is frozen +const uint32_t UBLOX_CFG_NMEA_MAINTALKERID = 0x20930031; // Main Talker ID +const uint32_t UBLOX_CFG_NMEA_GSVTALKERID = 0x20930032; // Talker ID for GSV NMEA messages +const uint32_t UBLOX_CFG_NMEA_BDSTALKERID = 0x30930033; // BeiDou Talker ID + +//CFG-ODO: Odometer and low-speed course over ground filter +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_ODO_USE_ODO = 0x10220001; // Use odometer +const uint32_t UBLOX_CFG_ODO_USE_COG = 0x10220002; // Use low-speed course over ground filter +const uint32_t UBLOX_CFG_ODO_OUTLPVEL = 0x10220003; // Output low-pass filtered velocity +const uint32_t UBLOX_CFG_ODO_OUTLPCOG = 0x10220004; // Output low-pass filtered course over ground (heading) +const uint32_t UBLOX_CFG_ODO_PROFILE = 0x20220005; // Odometer profile configuration +const uint32_t UBLOX_CFG_ODO_COGMAXSPEED = 0x20220021; // Upper speed limit for low-speed course over ground filter +const uint32_t UBLOX_CFG_ODO_COGMAXPOSACC = 0x20220022; // Maximum acceptable position accuracy for computing low-speed filtered course over ground +const uint32_t UBLOX_CFG_ODO_VELLPGAIN = 0x20220031; // Velocity low-pass filter level +const uint32_t UBLOX_CFG_ODO_COGLPGAIN = 0x20220032; // Course over ground low-pass filter level (at speed < 8 m/s) + +//CFG-QZSS: QZSS system configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_QZSS_USE_SLAS_DGNSS = 0x10370005; // Apply QZSS SLAS DGNSS corrections +const uint32_t UBLOX_CFG_QZSS_USE_SLAS_TESTMODE = 0x10370006; // Use QZSS SLAS data when it is in test mode (SLAS msg 0) +const uint32_t UBLOX_CFG_QZSS_USE_SLAS_RAIM_UNCORR = 0x10370007; // Raim out measurements that are not corrected by QZSS SLAS, if at least 5 measurements are corrected + +//CFG-RATE: Navigation and measurement rate configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_RATE_MEAS = 0x30210001; // Nominal time between GNSS measurements +const uint32_t UBLOX_CFG_RATE_NAV = 0x30210002; // Ratio of number of measurements to number of navigation solutions +const uint32_t UBLOX_CFG_RATE_TIMEREF = 0x20210003; // Time system to which measurements are aligned + +//CFG-RINV: Remote inventory +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_RINV_DUMP = 0x10c70001; // Dump data at startup +const uint32_t UBLOX_CFG_RINV_BINARY = 0x10c70002; // Data is binary +const uint32_t UBLOX_CFG_RINV_DATA_SIZE = 0x20c70003; // Size of data +const uint32_t UBLOX_CFG_RINV_CHUNK0 = 0x50c70004; // Data bytes 1-8 (LSB) +const uint32_t UBLOX_CFG_RINV_CHUNK1 = 0x50c70005; // Data bytes 9-16 +const uint32_t UBLOX_CFG_RINV_CHUNK2 = 0x50c70006; // Data bytes 17-240x44434241. +const uint32_t UBLOX_CFG_RINV_CHUNK3 = 0x50c70007; // Data bytes 25-30 (MSB) + +//CFG-RTCM: RTCM protocol configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_RTCM_DF003_OUT = 0x30090001; // RTCM DF003 (Reference station ID) output value +const uint32_t UBLOX_CFG_RTCM_DF003_IN = 0x30090008; // RTCM DF003 (Reference station ID) input value +const uint32_t UBLOX_CFG_RTCM_DF003_IN_FILTER = 0x20090009; // RTCM input filter configuration based on RTCM DF003 (Reference station ID) value + +//CFG-SBAS: SBAS configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_SBAS_USE_TESTMODE = 0x10360002; // Use SBAS data when it is in test mode (SBAS msg 0) +const uint32_t UBLOX_CFG_SBAS_USE_RANGING = 0x10360003; // Use SBAS GEOs as a ranging source (for navigation) +const uint32_t UBLOX_CFG_SBAS_USE_DIFFCORR = 0x10360004; // Use SBAS differential corrections +const uint32_t UBLOX_CFG_SBAS_USE_INTEGRITY = 0x10360005; // Use SBAS integrity information +const uint32_t UBLOX_CFG_SBAS_PRNSCANMASK = 0x50360006; // SBAS PRN search configuration + +//CFG-SIGNAL: Satellite systems (GNSS) signal configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_SIGNAL_GPS_ENA = 0x1031001f; // GPS enable +const uint32_t UBLOX_CFG_SIGNAL_GPS_L1CA_ENA = 0x10310001; // GPS L1C/A +const uint32_t UBLOX_CFG_SIGNAL_GPS_L2C_ENA = 0x10310003; // GPS L2C (only on u-blox F9 platform products) +const uint32_t UBLOX_CFG_SIGNAL_SBAS_ENA = 0x10310020; // SBAS enable +const uint32_t UBLOX_CFG_SIGNAL_SBAS_L1CA_ENA = 0x10310005; // SBAS L1C/A +const uint32_t UBLOX_CFG_SIGNAL_GAL_ENA = 0x10310021; // Galileo enable +const uint32_t UBLOX_CFG_SIGNAL_GAL_E1_ENA = 0x10310007; // Galileo E1 +const uint32_t UBLOX_CFG_SIGNAL_GAL_E5B_ENA = 0x1031000a; // Galileo E5b (only on u-blox F9 platform products) +const uint32_t UBLOX_CFG_SIGNAL_BDS_ENA = 0x10310022; // BeiDou Enable +const uint32_t UBLOX_CFG_SIGNAL_BDS_B1_ENA = 0x1031000d; // BeiDou B1I +const uint32_t UBLOX_CFG_SIGNAL_BDS_B2_ENA = 0x1031000e; // BeiDou B2I (only on u-blox F9 platform products) +const uint32_t UBLOX_CFG_SIGNAL_QZSS_ENA = 0x10310024; // QZSS enable +const uint32_t UBLOX_CFG_SIGNAL_QZSS_L1CA_ENA = 0x10310012; // QZSS L1C/A +const uint32_t UBLOX_CFG_SIGNAL_QZSS_L1S_ENA = 0x10310014; // QZSS L1S +const uint32_t UBLOX_CFG_SIGNAL_QZSS_L2C_ENA = 0x10310015; // QZSS L2C (only on u-blox F9 platform products) +const uint32_t UBLOX_CFG_SIGNAL_GLO_ENA = 0x10310025; // GLONASS enable +const uint32_t UBLOX_CFG_SIGNAL_GLO_L1_ENA = 0x10310018; // GLONASS L1 +const uint32_t UBLOX_CFG_SIGNAL_GLO_L2_ENA = 0x1031001a; // GLONASS L2 (only on u-blox F9 platform products) + +//CFG-SPI: Configuration of the SPI interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_SPI_MAXFF = 0x20640001; // Number of bytes containing 0xFF to receive before switching off reception. Range: 0 (mechanism off) - 63 +const uint32_t UBLOX_CFG_SPI_CPOLARITY = 0x10640002; // Clock polarity select: 0: Active Hight Clock, SCLK idles low, 1: Active Low Clock, SCLK idles high +const uint32_t UBLOX_CFG_SPI_CPHASE = 0x10640003; // Clock phase select: 0: Data captured on first edge of SCLK, 1: Data captured on second edge of SCLK +const uint32_t UBLOX_CFG_SPI_EXTENDEDTIMEOUT = 0x10640005; // Flag to disable timeouting the interface after 1.5s +const uint32_t UBLOX_CFG_SPI_ENABLED = 0x10640006; // Flag to indicate if the SPI interface should be enabled + +//CFG-SPIINPROT: Input protocol configuration of the SPI interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_SPIINPROT_UBX = 0x10790001; // Flag to indicate if UBX should be an input protocol on SPI +const uint32_t UBLOX_CFG_SPIINPROT_NMEA = 0x10790002; // Flag to indicate if NMEA should be an input protocol on SPI +const uint32_t UBLOX_CFG_SPIINPROT_RTCM3X = 0x10790004; // Flag to indicate if RTCM3X should be an input protocol on SPI + +//CFG-SPIOUTPROT: Output protocol configuration of the SPI interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_SPIOUTPROT_UBX = 0x107a0001; // Flag to indicate if UBX should be an output protocol on SPI +const uint32_t UBLOX_CFG_SPIOUTPROT_NMEA = 0x107a0002; // Flag to indicate if NMEA should be an output protocol on SPI +const uint32_t UBLOX_CFG_SPIOUTPROT_RTCM3X = 0x107a0004; // Flag to indicate if RTCM3X should be an output protocol on SPI + +//CFG-TMODE: Time mode configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_TMODE_MODE = 0x20030001; // Receiver mode +const uint32_t UBLOX_CFG_TMODE_POS_TYPE = 0x20030002; // Determines whether the ARP position is given in ECEF or LAT/LON/HEIGHT? +const uint32_t UBLOX_CFG_TMODE_ECEF_X = 0x40030003; // ECEF X coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_ECEF_Y = 0x40030004; // ECEF Y coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_ECEF_Z = 0x40030005; // ECEF Z coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_ECEF_X_HP = 0x20030006; // High-precision ECEF X coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_ECEF_Y_HP = 0x20030007; // High-precision ECEF Y coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_ECEF_Z_HP = 0x20030008; // High-precision ECEF Z coordinate of the ARP position. +const uint32_t UBLOX_CFG_TMODE_LAT = 0x40030009; // Latitude of the ARP position. +const uint32_t UBLOX_CFG_TMODE_LON = 0x4003000a; // Longitude of the ARP position. +const uint32_t UBLOX_CFG_TMODE_HEIGHT = 0x4003000b; // Height of the ARP position. +const uint32_t UBLOX_CFG_TMODE_LAT_HP = 0x2003000c; // High-precision latitude of the ARP position +const uint32_t UBLOX_CFG_TMODE_LON_HP = 0x2003000d; // High-precision longitude of the ARP position. +const uint32_t UBLOX_CFG_TMODE_HEIGHT_HP = 0x2003000e; // High-precision height of the ARP position. +const uint32_t UBLOX_CFG_TMODE_FIXED_POS_ACC = 0x4003000f; // Fixed position 3D accuracy +const uint32_t UBLOX_CFG_TMODE_SVIN_MIN_DUR = 0x40030010; // Survey-in minimum duration +const uint32_t UBLOX_CFG_TMODE_SVIN_ACC_LIMIT = 0x40030011; // Survey-in position accuracy limit + +//CFG-TP: Timepulse configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_TP_PULSE_DEF = 0x20050023; // Determines whether the time pulse is interpreted as frequency or period +const uint32_t UBLOX_CFG_TP_PULSE_LENGTH_DEF = 0x20050030; // Determines whether the time pulse length is interpreted as length[us] or pulse ratio[%] +const uint32_t UBLOX_CFG_TP_FREQ_TP1 = 0x40050024; // Time pulse frequency (TP1) +const uint32_t UBLOX_CFG_TP_FREQ_LOCK_TP1 = 0x40050025; // Time pulse frequency when locked to GNSS time (TP1) +const uint32_t UBLOX_CFG_TP_LEN_TP1 = 0x40050004; // Time pulse length (TP1) +const uint32_t UBLOX_CFG_TP_LEN_LOCK_TP1 = 0x40050005; // Time pulse length when locked to GNSS time (TP1) +const uint32_t UBLOX_CFG_TP_DUTY_TP1 = 0x5005002a; // Time pulse duty cycle (TP1) +const uint32_t UBLOX_CFG_TP_DUTY_LOCK_TP1 = 0x5005002b; // Time pulse duty cycle when locked to GNSS time (TP1) +const uint32_t UBLOX_CFG_TP_USER_DELAY_TP1 = 0x40050006; // User-configurable time pulse delay (TP1) +const uint32_t UBLOX_CFG_TP_TP1_ENA = 0x10050007; // Enable the first timepulse +const uint32_t UBLOX_CFG_TP_SYNC_GNSS_TP1 = 0x10050008; // Sync time pulse to GNSS time or local clock (TP1) +const uint32_t UBLOX_CFG_TP_USE_LOCKED_TP1 = 0x10050009; // Use locked parameters when possible (TP1) +const uint32_t UBLOX_CFG_TP_ALIGN_TO_TOW_TP1 = 0x1005000a; // Align time pulse to top of second (TP1) +const uint32_t UBLOX_CFG_TP_POL_TP1 = 0x1005000b; // Set time pulse polarity (TP1) +const uint32_t UBLOX_CFG_TP_TIMEGRID_TP1 = 0x2005000c; // Time grid to use (TP1) + +//CFG-TXREADY: TX ready configuration +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_TXREADY_ENABLED = 0x10a20001; // Flag to indicate if TX ready pin mechanism should be enabled +const uint32_t UBLOX_CFG_TXREADY_POLARITY = 0x10a20002; // The polarity of the TX ready pin: false:high- active, true:low-active +const uint32_t UBLOX_CFG_TXREADY_PIN = 0x20a20003; // Pin number to use for the TX ready functionality +const uint32_t UBLOX_CFG_TXREADY_THRESHOLD = 0x30a20004; // Amount of data that should be ready on the interface before triggering the TX ready pin +const uint32_t UBLOX_CFG_TXREADY_INTERFACE = 0x20a20005; // Interface where the TX ready feature should be linked to + +//CFG-UART1: Configuration of the UART1 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART1_BAUDRATE = 0x40520001; // The baud rate that should be configured on the UART1 +const uint32_t UBLOX_CFG_UART1_STOPBITS = 0x20520002; // Number of stopbits that should be used on UART1 +const uint32_t UBLOX_CFG_UART1_DATABITS = 0x20520003; // Number of databits that should be used on UART1 +const uint32_t UBLOX_CFG_UART1_PARITY = 0x20520004; // Parity mode that should be used on UART1 +const uint32_t UBLOX_CFG_UART1_ENABLED = 0x10520005; // Flag to indicate if the UART1 should be enabled + +//CFG-UART1INPROT: Input protocol configuration of the UART1 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART1INPROT_UBX = 0x10730001; // Flag to indicate if UBX should be an input protocol on UART1 +const uint32_t UBLOX_CFG_UART1INPROT_NMEA = 0x10730002; // Flag to indicate if NMEA should be an input protocol on UART1 +const uint32_t UBLOX_CFG_UART1INPROT_RTCM3X = 0x10730004; // Flag to indicate if RTCM3X should be an input protocol on UART1 + +//CFG-UART1OUTPROT: Output protocol configuration of the UART1 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART1OUTPROT_UBX = 0x10740001; // Flag to indicate if UBX should be an output protocol on UART1 +const uint32_t UBLOX_CFG_UART1OUTPROT_NMEA = 0x10740002; // Flag to indicate if NMEA should be an output protocol on UART1 +const uint32_t UBLOX_CFG_UART1OUTPROT_RTCM3X = 0x10740004; // Flag to indicate if RTCM3X should be an output protocol on UART1 + +//CFG-UART2: Configuration of the UART2 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART2_BAUDRATE = 0x40530001; // The baud rate that should be configured on the UART2 +const uint32_t UBLOX_CFG_UART2_STOPBITS = 0x20530002; // Number of stopbits that should be used on UART2 +const uint32_t UBLOX_CFG_UART2_DATABITS = 0x20530003; // Number of databits that should be used on UART2 +const uint32_t UBLOX_CFG_UART2_PARITY = 0x20530004; // Parity mode that should be used on UART2 +const uint32_t UBLOX_CFG_UART2_ENABLED = 0x10530005; // Flag to indicate if the UART2 should be enabled +const uint32_t UBLOX_CFG_UART2_REMAP = 0x10530006; // UART2 Remapping + +//CFG-UART2INPROT: Input protocol configuration of the UART2 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART2INPROT_UBX = 0x10750001; // Flag to indicate if UBX should be an input protocol on UART2 +const uint32_t UBLOX_CFG_UART2INPROT_NMEA = 0x10750002; // Flag to indicate if NMEA should be an input protocol on UART2 +const uint32_t UBLOX_CFG_UART2INPROT_RTCM3X = 0x10750004; // Flag to indicate if RTCM3X should be an input protocol on UART2 + +//CFG-UART2OUTPROT: Output protocol configuration of the UART2 interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_UART2OUTPROT_UBX = 0x10760001; // Flag to indicate if UBX should be an output protocol on UART2 +const uint32_t UBLOX_CFG_UART2OUTPROT_NMEA = 0x10760002; // Flag to indicate if NMEA should be an output protocol on UART2 +const uint32_t UBLOX_CFG_UART2OUTPROT_RTCM3X = 0x10760004; // Flag to indicate if RTCM3X should be an output protocol on UART2 + +//CFG-USB: Configuration of the USB interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_USB_ENABLED = 0x10650001; // Flag to indicate if the USB interface should be enabled +const uint32_t UBLOX_CFG_USB_SELFPOW = 0x10650002; // Self-powered device +const uint32_t UBLOX_CFG_USB_VENDOR_ID = 0x3065000a; // Vendor ID +const uint32_t UBLOX_CFG_USB_PRODUCT_ID = 0x3065000b; // Vendor ID +const uint32_t UBLOX_CFG_USB_POWER = 0x3065000c; // Power consumption +const uint32_t UBLOX_CFG_USB_VENDOR_STR0 = 0x5065000d; // Vendor string characters 0-7 +const uint32_t UBLOX_CFG_USB_VENDOR_STR1 = 0x5065000e; // Vendor string characters 8-15 +const uint32_t UBLOX_CFG_USB_VENDOR_STR2 = 0x5065000f; // Vendor string characters 16-23 +const uint32_t UBLOX_CFG_USB_VENDOR_STR3 = 0x50650010; // Vendor string characters 24-31 +const uint32_t UBLOX_CFG_USB_PRODUCT_STR0 = 0x50650011; // Product string characters 0-7 +const uint32_t UBLOX_CFG_USB_PRODUCT_STR1 = 0x50650012; // Product string characters 8-15 +const uint32_t UBLOX_CFG_USB_PRODUCT_STR2 = 0x50650013; // Product string characters 16-23 +const uint32_t UBLOX_CFG_USB_PRODUCT_STR3 = 0x50650014; // Product string characters 24-31 +const uint32_t UBLOX_CFG_USB_SERIAL_NO_STR0 = 0x50650015; // Serial number string characters 0-7 +const uint32_t UBLOX_CFG_USB_SERIAL_NO_STR1 = 0x50650016; // Serial number string characters 8-15 +const uint32_t UBLOX_CFG_USB_SERIAL_NO_STR2 = 0x50650017; // Serial number string characters 16-23 +const uint32_t UBLOX_CFG_USB_SERIAL_NO_STR3 = 0x50650018; // Serial number string characters 24-31 + +//CFG-USBINPROT: Input protocol configuration of the USB interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_USBINPROT_UBX = 0x10770001; // Flag to indicate if UBX should be an input protocol on USB +const uint32_t UBLOX_CFG_USBINPROT_NMEA = 0x10770002; // Flag to indicate if NMEA should be an input protocol on USB +const uint32_t UBLOX_CFG_USBINPROT_RTCM3X = 0x10770004; // Flag to indicate if RTCM3X should be an input protocol on USB + +//CFG-USBOUTPROT: Output protocol configuration of the USB interface +//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +const uint32_t UBLOX_CFG_USBOUTPROT_UBX = 0x10780001; // Flag to indicate if UBX should be an output protocol on USB +const uint32_t UBLOX_CFG_USBOUTPROT_NMEA = 0x10780002; // Flag to indicate if NMEA should be an output protocol on USB +const uint32_t UBLOX_CFG_USBOUTPROT_RTCM3X = 0x10780004; // Flag to indicate if RTCM3X should be an output protocol on USB + +#endif diff --git a/src/u-blox_structs.h b/src/u-blox_structs.h new file mode 100644 index 0000000..068597b --- /dev/null +++ b/src/u-blox_structs.h @@ -0,0 +1,1792 @@ +/* + This is a library written for the u-blox ZED-F9P and NEO-M8P-2 + SparkFun sells these at its website: www.sparkfun.com + Do you like this library? Help support SparkFun. Buy a board! + https://www.sparkfun.com/products/16481 + https://www.sparkfun.com/products/15136 + https://www.sparkfun.com/products/15005 + https://www.sparkfun.com/products/15733 + https://www.sparkfun.com/products/15193 + https://www.sparkfun.com/products/15210 + + Original version by Nathan Seidle @ SparkFun Electronics, September 6th, 2018 + v2.0 rework by Paul Clark @ SparkFun Electronics, December 31st, 2020 + + This library handles configuring and handling the responses + from a u-blox GPS module. Works with most modules from u-blox including + the Zed-F9P, NEO-M8P-2, NEO-M9N, ZOE-M8Q, SAM-M8Q, and many others. + + https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library + + Development environment specifics: + Arduino IDE 1.8.13 + + SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT). + The MIT License (MIT) + Copyright (c) 2016 SparkFun Electronics + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to + do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef __u_blox_structs_h__ +#define __u_blox_structs_h__ + +#include "SparkFun_Ublox_Arduino_Library.h" + +#ifndef DEF_NUM_SENS +#define DEF_NUM_SENS 7 // The maximum number of ESF sensors +#endif + +//Additional flags and pointers that need to be stored with each message type +struct ubxAutomaticFlags +{ + union + { + uint8_t all; + struct + { + uint8_t automatic : 1; // Will this message be delivered and parsed "automatically" (without polling) + uint8_t implicitUpdate : 1; // Is the update triggered by accessing stale data (=true) or by a call to checkUblox (=false) + uint8_t addToFileBuffer : 1; // Should the raw UBX data be added to the file buffer? + uint8_t callbackCopyValid : 1; // Is the copy of the data struct used by the callback valid/fresh? + } bits; + } flags; +}; + +// UBX-NAV-POSECEF (0x01 0x01): Position solution in ECEF +const uint16_t UBX_NAV_POSECEF_LEN = 20; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + int32_t ecefX; // ECEF X coordinate: cm + int32_t ecefY; // ECEF Y coordinate: cm + int32_t ecefZ; // ECEF Z coordinate: cm + uint32_t pAcc; // Position Accuracy Estimate: cm +} UBX_NAV_POSECEF_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t ecefX : 1; + uint32_t ecefY : 1; + uint32_t ecefZ : 1; + uint32_t pAcc : 1; + } bits; + } moduleQueried; +} UBX_NAV_POSECEF_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_POSECEF_data_t data; + UBX_NAV_POSECEF_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_POSECEF_data_t); + UBX_NAV_POSECEF_data_t *callbackData; +} UBX_NAV_POSECEF_t; + +// UBX-NAV-POSLLH (0x01 0x02): Geodetic position solution +const uint16_t UBX_NAV_POSLLH_LEN = 28; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + int32_t lon; // Longitude: Degrees * 1e-7 + int32_t lat; // Latitude: Degrees * 1e-7 + int32_t height; // Height above ellipsoid: mm + int32_t hMSL; // Height above mean sea level: mm + uint32_t hAcc; // Horizontal Accuracy Estimate: mm + uint32_t vAcc; // Vertical Accuracy Estimate: mm +} UBX_NAV_POSLLH_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t lon : 1; + uint32_t lat : 1; + uint32_t height : 1; + uint32_t hMSL : 1; + uint32_t hAcc : 1; + uint32_t vAcc : 1; + } bits; + } moduleQueried; +} UBX_NAV_POSLLH_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_POSLLH_data_t data; + UBX_NAV_POSLLH_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_POSLLH_data_t); + UBX_NAV_POSLLH_data_t *callbackData; +} UBX_NAV_POSLLH_t; + +// UBX-NAV-STATUS (0x01 0x03): Receiver navigation status +const uint16_t UBX_NAV_STATUS_LEN = 16; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + uint8_t gpsFix; // GPSfix Type: 0x00 = no fix; 0x01 = dead reckoning only; 0x02 = 2D-fix; 0x03 = 3D-fix + // 0x04 = GPS + dead reckoning combined; 0x05 = Time only fix; 0x06..0xff = reserved + union + { + uint8_t all; + struct + { + uint8_t gpsFixOk : 1; // 1 = position and velocity valid and within DOP and ACC Masks. + uint8_t diffSoln : 1; // 1 = differential corrections were applied + uint8_t wknSet : 1; // 1 = Week Number valid (see Time Validity section for details) + uint8_t towSet : 1; // 1 = Time of Week valid (see Time Validity section for details) + } bits; + } flags; + union + { + uint8_t all; + struct + { + uint8_t diffCorr : 1; // 1 = differential corrections available + uint8_t carrSolnValid : 1; // 1 = valid carrSoln + uint8_t reserved : 4; + uint8_t mapMatching : 2; // map matching status: 00: none + // 01: valid but not used, i.e. map matching data was received, but was too old + // 10: valid and used, map matching data was applied + // 11: valid and used, map matching data was applied. + } bits; + } fixStat; + union + { + uint8_t all; + struct + { + uint8_t psmState : 2; // power save mode state + // 0: ACQUISITION [or when psm disabled] + // 1: TRACKING + // 2: POWER OPTIMIZED TRACKING + // 3: INACTIVE + uint8_t reserved1 : 1; + uint8_t spoofDetState : 2; // Spoofing detection state + // 0: Unknown or deactivated + // 1: No spoofing indicated + // 2: Spoofing indicated + // 3: Multiple spoofing indications + uint8_t reserved2 : 1; + uint8_t carrSoln : 2; // Carrier phase range solution status: + // 0: no carrier phase range solution + // 1: carrier phase range solution with floating ambiguities + // 2: carrier phase range solution with fixed ambiguities + } bits; + } flags2; + uint32_t ttff; // Time to first fix (millisecond time tag): ms + uint32_t msss; // Milliseconds since Startup / Reset: ms +} UBX_NAV_STATUS_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t gpsFix : 1; + + uint32_t gpsFixOk : 1; + uint32_t diffSoln : 1; + uint32_t wknSet : 1; + uint32_t towSet : 1; + + uint32_t diffCorr : 1; + uint32_t carrSolnValid : 1; + uint32_t mapMatching : 1; + + uint32_t psmState : 1; + uint32_t spoofDetState : 1; + uint32_t carrSoln : 1; + + uint32_t ttff : 1; + uint32_t msss : 1; + } bits; + } moduleQueried; +} UBX_NAV_STATUS_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_STATUS_data_t data; + UBX_NAV_STATUS_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_STATUS_data_t); + UBX_NAV_STATUS_data_t *callbackData; +} UBX_NAV_STATUS_t; + +// UBX-NAV-DOP (0x01 0x04): Dilution of precision +const uint16_t UBX_NAV_DOP_LEN = 18; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + uint16_t gDOP; // Geometric DOP: * 0.01 + uint16_t pDOP; // Position DOP: * 0.01 + uint16_t tDOP; // Time DOP: * 0.01 + uint16_t vDOP; // Vertical DOP: * 0.01 + uint16_t hDOP; // Horizontal DOP: * 0.01 + uint16_t nDOP; // Northing DOP: * 0.01 + uint16_t eDOP; // Easting DOP: * 0.01 +} UBX_NAV_DOP_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t gDOP : 1; + uint32_t pDOP : 1; + uint32_t tDOP : 1; + uint32_t vDOP : 1; + uint32_t hDOP : 1; + uint32_t nDOP : 1; + uint32_t eDOP : 1; + } bits; + } moduleQueried; +} UBX_NAV_DOP_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_DOP_data_t data; + UBX_NAV_DOP_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_DOP_data_t); + UBX_NAV_DOP_data_t *callbackData; +} UBX_NAV_DOP_t; + +// UBX-NAV-ATT (0x01 0x05): Attitude solution +const uint16_t UBX_NAV_ATT_LEN = 32; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + uint8_t version; // Message version (0x00 for this version) + uint8_t reserved1[3]; + int32_t roll; // Vehicle roll: Degrees * 1e-5 + int32_t pitch; // Vehicle pitch: Degrees * 1e-5 + int32_t heading; // Vehicle heading: Degrees * 1e-5 + uint32_t accRoll; // Vehicle roll accuracy (if null, roll angle is not available): Degrees * 1e-5 + uint32_t accPitch; // Vehicle pitch accuracy (if null, roll angle is not available): Degrees * 1e-5 + uint32_t accHeading; // Vehicle heading accuracy (if null, roll angle is not available): Degrees * 1e-5 +} UBX_NAV_ATT_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t version : 1; + uint32_t roll : 1; + uint32_t pitch : 1; + uint32_t heading : 1; + uint32_t accRoll : 1; + uint32_t accPitch : 1; + uint32_t accHeading : 1; + } bits; + } moduleQueried; +} UBX_NAV_ATT_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_ATT_data_t data; + UBX_NAV_ATT_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_ATT_data_t); + UBX_NAV_ATT_data_t *callbackData; +} UBX_NAV_ATT_t; + +// UBX-NAV-PVT (0x01 0x07): Navigation position velocity time solution +const uint16_t UBX_NAV_PVT_LEN = 92; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + uint16_t year; // Year (UTC) + uint8_t month; // Month, range 1..12 (UTC) + uint8_t day; // Day of month, range 1..31 (UTC) + uint8_t hour; // Hour of day, range 0..23 (UTC) + uint8_t min; // Minute of hour, range 0..59 (UTC) + uint8_t sec; // Seconds of minute, range 0..60 (UTC) + union + { + uint8_t all; + struct + { + uint8_t validDate : 1; // 1 = valid UTC Date + uint8_t validTime : 1; // 1 = valid UTC time of day + uint8_t fullyResolved : 1; // 1 = UTC time of day has been fully resolved (no seconds uncertainty). + uint8_t validMag : 1; // 1 = valid magnetic declination + } bits; + } valid; + uint32_t tAcc; // Time accuracy estimate (UTC): ns + int32_t nano; // Fraction of second, range -1e9 .. 1e9 (UTC): ns + uint8_t fixType; // GNSSfix Type: + // 0: no fix + // 1: dead reckoning only + // 2: 2D-fix + // 3: 3D-fix + // 4: GNSS + dead reckoning combined + // 5: time only fix + union + { + uint8_t all; + struct + { + uint8_t gnssFixOK : 1; // 1 = valid fix (i.e within DOP & accuracy masks) + uint8_t diffSoln : 1; // 1 = differential corrections were applied + uint8_t psmState : 3; + uint8_t headVehValid : 1; // 1 = heading of vehicle is valid, only set if the receiver is in sensor fusion mode + uint8_t carrSoln : 2; // Carrier phase range solution status: + // 0: no carrier phase range solution + // 1: carrier phase range solution with floating ambiguities + // 2: carrier phase range solution with fixed ambiguities + } bits; + } flags; + union + { + uint8_t all; + struct + { + uint8_t reserved : 5; + uint8_t confirmedAvai : 1; // 1 = information about UTC Date and Time of Day validity confirmation is available + uint8_t confirmedDate : 1; // 1 = UTC Date validity could be confirmed + uint8_t confirmedTime : 1; // 1 = UTC Time of Day could be confirmed + } bits; + } flags2; + uint8_t numSV; // Number of satellites used in Nav Solution + int32_t lon; // Longitude: deg * 1e-7 + int32_t lat; // Latitude: deg * 1e-7 + int32_t height; // Height above ellipsoid: mm + int32_t hMSL; // Height above mean sea level: mm + uint32_t hAcc; // Horizontal accuracy estimate: mm + uint32_t vAcc; // Vertical accuracy estimate: mm + int32_t velN; // NED north velocity: mm/s + int32_t velE; // NED east velocity: mm/s + int32_t velD; // NED down velocity: mm/s + int32_t gSpeed; // Ground Speed (2-D): mm/s + int32_t headMot; // Heading of motion (2-D): deg * 1e-5 + uint32_t sAcc; // Speed accuracy estimate: mm/s + uint32_t headAcc; // Heading accuracy estimate (both motion and vehicle): deg * 1e-5 + uint16_t pDOP; // Position DOP * 0.01 + union + { + uint8_t all; + struct + { + uint8_t invalidLlh : 1; // 1 = Invalid lon, lat, height and hMSL + } bits; + } flags3; + uint8_t reserved1[5]; + int32_t headVeh; // Heading of vehicle (2-D): deg * 1e-5 + int16_t magDec; // Magnetic declination: deg * 1e-2 + uint16_t magAcc; // Magnetic declination accuracy: deg * 1e-2 +} UBX_NAV_PVT_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t year : 1; + uint32_t month : 1; + uint32_t day : 1; + uint32_t hour : 1; + uint32_t min : 1; + uint32_t sec : 1; + + uint32_t validDate : 1; + uint32_t validTime : 1; + uint32_t fullyResolved : 1; + uint32_t validMag : 1; + + uint32_t tAcc : 1; + uint32_t nano : 1; + uint32_t fixType : 1; + uint32_t gnssFixOK : 1; + uint32_t diffSoln : 1; + uint32_t psmState : 1; + uint32_t headVehValid : 1; + uint32_t carrSoln : 1; + + uint32_t confirmedAvai : 1; + uint32_t confirmedDate : 1; + uint32_t confirmedTime : 1; + + uint32_t numSV : 1; + uint32_t lon : 1; + uint32_t lat : 1; + uint32_t height : 1; + uint32_t hMSL : 1; + uint32_t hAcc : 1; + uint32_t vAcc : 1; + uint32_t velN : 1; + uint32_t velE : 1; + } bits; + } moduleQueried1; + union + { + uint32_t all; + struct + { + uint32_t velD : 1; + uint32_t gSpeed : 1; + uint32_t headMot : 1; + uint32_t sAcc : 1; + uint32_t headAcc : 1; + uint32_t pDOP : 1; + + uint32_t invalidLlh : 1; + + uint32_t headVeh : 1; + uint32_t magDec : 1; + uint32_t magAcc : 1; + } bits; + } moduleQueried2; +} UBX_NAV_PVT_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_PVT_data_t data; + UBX_NAV_PVT_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_PVT_data_t); + UBX_NAV_PVT_data_t *callbackData; +} UBX_NAV_PVT_t; + +// UBX-NAV-ODO (0x01 0x09): Odometer solution +const uint16_t UBX_NAV_ODO_LEN = 20; + +typedef struct +{ + uint8_t version; // Message version (0x00 for this version) + uint8_t reserved1[3]; + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + uint32_t distance; // Ground distance since last reset: m + uint32_t totalDistance; // Total cumulative ground distance: m + uint32_t distanceStd; // Ground distance accuracy (1-sigma): m +} UBX_NAV_ODO_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t version : 1; + uint32_t iTOW : 1; + uint32_t distance : 1; + uint32_t totalDistance : 1; + uint32_t distanceStd : 1; + } bits; + } moduleQueried; +} UBX_NAV_ODO_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_ODO_data_t data; + UBX_NAV_ODO_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_ODO_data_t); + UBX_NAV_ODO_data_t *callbackData; +} UBX_NAV_ODO_t; + +// UBX-NAV-VELECEF (0x01 0x11): Velocity solution in ECEF +const uint16_t UBX_NAV_VELECEF_LEN = 20; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + int32_t ecefVX; // ECEF X velocity: cm/s + int32_t ecefVY; // ECEF Y velocity: cm/s + int32_t ecefVZ; // ECEF Z velocity: cm/s + uint32_t sAcc; // Speed accuracy estimate: cm/s +} UBX_NAV_VELECEF_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t ecefVX : 1; + uint32_t ecefVY : 1; + uint32_t ecefVZ : 1; + uint32_t sAcc : 1; + } bits; + } moduleQueried; +} UBX_NAV_VELECEF_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_VELECEF_data_t data; + UBX_NAV_VELECEF_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_VELECEF_data_t); + UBX_NAV_VELECEF_data_t *callbackData; +} UBX_NAV_VELECEF_t; + +// UBX-NAV-VELNED (0x01 0x12): Velocity solution in NED frame +const uint16_t UBX_NAV_VELNED_LEN = 36; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + int32_t velN; // North velocity component: cm/s + int32_t velE; // East velocity component: cm/s + int32_t velD; // Down velocity component: cm/s + uint32_t speed; // Speed (3-D): cm/s + uint32_t gSpeed; // Ground Speed (2-D): cm/s + int32_t heading; // Heading of motion 2-D: Degrees * 1e-5 + uint32_t sAcc; // Speed accuracy estimate: cm/s + uint32_t cAcc; // Course/Heading accuracy estimate: Degrees * 1e-5 +} UBX_NAV_VELNED_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t velN : 1; + uint32_t velE : 1; + uint32_t velD : 1; + uint32_t speed : 1; + uint32_t gSpeed : 1; + uint32_t heading : 1; + uint32_t sAcc : 1; + uint32_t cAcc : 1; + } bits; + } moduleQueried; +} UBX_NAV_VELNED_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_VELNED_data_t data; + UBX_NAV_VELNED_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_VELNED_data_t); + UBX_NAV_VELNED_data_t *callbackData; +} UBX_NAV_VELNED_t; + +// UBX-NAV-HPPOSECEF (0x01 0x13): High precision position solution in ECEF +const uint16_t UBX_NAV_HPPOSECEF_LEN = 28; + +typedef struct +{ + uint8_t version; // Message version (0x00 for this version) + uint8_t reserved1[3]; + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + int32_t ecefX; // ECEF X coordinate: cm + int32_t ecefY; // ECEF Y coordinate: cm + int32_t ecefZ; // ECEF Z coordinate: cm + int8_t ecefXHp; // High precision component of ECEF X coordinate: mm * 0.1 + int8_t ecefYHp; // High precision component of ECEF Y coordinate: mm * 0.1 + int8_t ecefZHp; // High precision component of ECEF Z coordinate: mm * 0.1 + union + { + uint8_t all; + struct + { + uint8_t invalidEcef : 1; // 1 = Invalid ecefX, ecefY, ecefZ, ecefXHp, ecefYHp and ecefZHp + } bits; + } flags; + uint32_t pAcc; // Position Accuracy Estimate: mm * 0.1 +} UBX_NAV_HPPOSECEF_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t version : 1; + uint32_t iTOW : 1; + uint32_t ecefX : 1; + uint32_t ecefY : 1; + uint32_t ecefZ : 1; + uint32_t ecefXHp : 1; + uint32_t ecefYHp : 1; + uint32_t ecefZHp : 1; + + uint32_t invalidEcef : 1; + + uint32_t pAcc : 1; + } bits; + } moduleQueried; +} UBX_NAV_HPPOSECEF_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_HPPOSECEF_data_t data; + UBX_NAV_HPPOSECEF_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_HPPOSECEF_data_t); + UBX_NAV_HPPOSECEF_data_t *callbackData; +} UBX_NAV_HPPOSECEF_t; + +// UBX-NAV-HPPOSLLH (0x01 0x14): High precision geodetic position solution +const uint16_t UBX_NAV_HPPOSLLH_LEN = 36; + +typedef struct +{ + uint8_t version; // Message version (0x00 for this version) + uint8_t reserved1[2]; + union + { + uint8_t all; + struct + { + uint8_t invalidLlh : 1; // 1 = Invalid lon, lat, height, hMSL, lonHp, latHp, heightHp and hMSLHp + } bits; + } flags; + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + int32_t lon; // Longitude: deg * 1e-7 + int32_t lat; // Latitude: deg * 1e-7 + int32_t height; // Height above ellipsoid: mm + int32_t hMSL; // Height above mean sea level: mm + int8_t lonHp; // High precision component of longitude: deg * 1e-9 + int8_t latHp; // High precision component of latitude: deg * 1e-9 + int8_t heightHp; // High precision component of height above ellipsoid: mm * 0.1 + int8_t hMSLHp; // High precision component of height above mean sea level: mm * 0.1 + uint32_t hAcc; // Horizontal accuracy estimate: mm * 0.1 + uint32_t vAcc; // Vertical accuracy estimate: mm * 0.1 +} UBX_NAV_HPPOSLLH_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t version : 1; + + uint32_t invalidLlh : 1; + + uint32_t iTOW : 1; + uint32_t lon : 1; + uint32_t lat : 1; + uint32_t height : 1; + uint32_t hMSL : 1; + uint32_t lonHp : 1; + uint32_t latHp : 1; + uint32_t heightHp : 1; + uint32_t hMSLHp : 1; + uint32_t hAcc : 1; + uint32_t vAcc : 1; + } bits; + } moduleQueried; +} UBX_NAV_HPPOSLLH_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_HPPOSLLH_data_t data; + UBX_NAV_HPPOSLLH_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_HPPOSLLH_data_t); + UBX_NAV_HPPOSLLH_data_t *callbackData; +} UBX_NAV_HPPOSLLH_t; + +// UBX-NAV-TIMEUTC (0x01 0x21): UTC time solution +const uint16_t UBX_NAV_TIMEUTC_LEN = 20; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + uint32_t tAcc; // Time accuracy estimate (UTC): ns + int32_t nano; // Fraction of second, range -1e9 .. 1e9 (UTC): ns + uint16_t year; // Year (UTC) + uint8_t month; // Month, range 1..12 (UTC) + uint8_t day; // Day of month, range 1..31 (UTC) + uint8_t hour; // Hour of day, range 0..23 (UTC) + uint8_t min; // Minute of hour, range 0..59 (UTC) + uint8_t sec; // Seconds of minute, range 0..60 (UTC) + union + { + uint8_t all; + struct + { + uint8_t validTOW : 1; // 1 = Valid Time of Week + uint8_t validWKN : 1; // 1 = Valid Week Number + uint8_t validUTC : 1; // 1 = Valid UTC Time + uint8_t reserved : 1; + uint8_t utcStandard : 4; // UTC standard identifier + } bits; + } valid; +} UBX_NAV_TIMEUTC_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t tAcc : 1; + uint32_t nano : 1; + uint32_t year : 1; + uint32_t month : 1; + uint32_t day : 1; + uint32_t hour : 1; + uint32_t min : 1; + uint32_t sec : 1; + + uint32_t validTOW : 1; + uint32_t validWKN : 1; + uint32_t validUTC : 1; + uint32_t utcStandard : 1; + } bits; + } moduleQueried; +} UBX_NAV_TIMEUTC_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_TIMEUTC_data_t data; + UBX_NAV_TIMEUTC_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_TIMEUTC_data_t); + UBX_NAV_TIMEUTC_data_t *callbackData; +} UBX_NAV_TIMEUTC_t; + +// UBX-NAV-CLOCK (0x01 0x22): Clock solution +const uint16_t UBX_NAV_CLOCK_LEN = 20; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + int32_t clkB; // Clock bias: ns + int32_t clkD; // Clock drift: ns/s + uint32_t tAcc; // Time accuracy estimate: ns + uint32_t fAcc; // Frequency accuracy estimate: ps/s +} UBX_NAV_CLOCK_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t clkB : 1; + uint32_t clkD : 1; + uint32_t tAcc : 1; + uint32_t fAcc : 1; + } bits; + } moduleQueried; +} UBX_NAV_CLOCK_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_CLOCK_data_t data; + UBX_NAV_CLOCK_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_CLOCK_data_t); + UBX_NAV_CLOCK_data_t *callbackData; +} UBX_NAV_CLOCK_t; + +// UBX-NAV-SVIN (0x01 0x3B): Survey-in data +const uint16_t UBX_NAV_SVIN_LEN = 40; + +typedef struct +{ + uint8_t version; // Message version (0x00 for this version) + uint8_t reserved1[3]; + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + uint32_t dur; // Passed survey-in observation time: s + int32_t meanX; // Current survey-in mean position ECEF X coordinate: cm + int32_t meanY; // Current survey-in mean position ECEF Y coordinate: cm + int32_t meanZ; // Current survey-in mean position ECEF Z coordinate: cm + int8_t meanXHP; // Current high-precision survey-in mean position ECEF X coordinate: mm * 0.1 + int8_t meanYHP; // Current high-precision survey-in mean position ECEF Y coordinate: mm * 0.1 + int8_t meanZHP; // Current high-precision survey-in mean position ECEF Z coordinate: mm * 0.1 + uint8_t reserved2; + uint32_t meanAcc; // Current survey-in mean position accuracy: mm * 0.1 + uint32_t obs; // Number of position observations used during survey-in + int8_t valid; // Survey-in position validity flag, 1 = valid, otherwise 0 + int8_t active; // Survey-in in progress flag, 1 = in-progress, otherwise 0 + uint8_t reserved3[2]; +} UBX_NAV_SVIN_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t version : 1; + uint32_t iTOW : 1; + uint32_t dur : 1; + uint32_t meanX : 1; + uint32_t meanY : 1; + uint32_t meanZ : 1; + uint32_t meanXHP : 1; + uint32_t meanYHP : 1; + uint32_t meanZHP : 1; + uint32_t meanAcc : 1; + uint32_t obs : 1; + uint32_t valid : 1; + uint32_t active : 1; + } bits; + } moduleQueried; +} UBX_NAV_SVIN_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_SVIN_data_t data; + UBX_NAV_SVIN_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_SVIN_data_t); + UBX_NAV_SVIN_data_t *callbackData; +} UBX_NAV_SVIN_t; + +// UBX-NAV-RELPOSNED (0x01 0x3C): Relative positioning information in NED frame +// Note: +// RELPOSNED on the M8 is only 40 bytes long +// RELPOSNED on the F9 is 64 bytes long and contains much more information +const uint16_t UBX_NAV_RELPOSNED_LEN = 40; +const uint16_t UBX_NAV_RELPOSNED_LEN_F9 = 64; + +typedef struct +{ + uint8_t version; // Message version (0x00 for this version) + uint8_t reserved0; + uint16_t refStationId; // Reference Station ID + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + int32_t relPosN; // North component of relative position vector: cm + int32_t relPosE; // East component of relative position vector: cm + int32_t relPosD; // Down component of relative position vector: cm + int32_t relPosLength; // Length of the relative position vector: cm + int32_t relPosHeading; // Heading of the relative position vector: Degrees * 1e-5 + uint8_t reserved1[4]; + int8_t relPosHPN; // High-precision North component of relative position vector: mm * 0.1 + int8_t relPosHPE; // High-precision East component of relative position vector: mm * 0.1 + int8_t relPosHPD; // High-precision Down component of relative position vector: mm * 0.1 + int8_t relPosHPLength; // High-precision component of the length of the relative position vector: mm * 0.1 + uint32_t accN; // Accuracy of relative position North component: mm * 0.1 + uint32_t accE; // Accuracy of relative position East component: mm * 0.1 + uint32_t accD; // Accuracy of relative position Down component: mm * 0.1 + uint32_t accLength; // Accuracy of length of the relative position vector: mm * 0.1 + uint32_t accHeading; // Accuracy of heading of the relative position vector: Degrees * 1e-5 + uint8_t reserved2[4]; + union + { + uint32_t all; + struct + { + uint32_t gnssFixOK : 1; // A valid fix (i.e within DOP & accuracy masks) + uint32_t diffSoln : 1; // 1 if differential corrections were applied + uint32_t relPosValid : 1; // 1 if relative position components and accuracies are valid + uint32_t carrSoln : 2; // Carrier phase range solution status: + // 0 = no carrier phase range solution + // 1 = carrier phase range solution with floating ambiguities + // 2 = carrier phase range solution with fixed ambiguities + uint32_t isMoving : 1; // 1 if the receiver is operating in moving baseline mode + uint32_t refPosMiss : 1; // 1 if extrapolated reference position was used to compute moving baseline solution this epoch + uint32_t refObsMiss : 1; // 1 if extrapolated reference observations were used to compute moving baseline solution this epoch + uint32_t relPosHeadingValid : 1; // 1 if relPosHeading is valid + uint32_t relPosNormalized : 1; // 1 if the components of the relative position vector (including the high-precision parts) are normalized + } bits; + } flags; +} UBX_NAV_RELPOSNED_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t version : 1; + uint32_t refStationId : 1; + uint32_t iTOW : 1; + uint32_t relPosN : 1; + uint32_t relPosE : 1; + uint32_t relPosD : 1; + uint32_t relPosLength : 1; + uint32_t relPosHeading : 1; + uint32_t relPosHPN : 1; + uint32_t relPosHPE : 1; + uint32_t relPosHPD : 1; + uint32_t relPosHPLength : 1; + uint32_t accN : 1; + uint32_t accE : 1; + uint32_t accD : 1; + uint32_t accLength : 1; + uint32_t accHeading : 1; + + uint32_t gnssFixOK : 1; + uint32_t diffSoln : 1; + uint32_t relPosValid : 1; + uint32_t carrSoln : 1; + uint32_t isMoving : 1; + uint32_t refPosMiss : 1; + uint32_t refObsMiss : 1; + uint32_t relPosHeadingValid : 1; + uint32_t relPosNormalized : 1; + } bits; + } moduleQueried; +} UBX_NAV_RELPOSNED_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_NAV_RELPOSNED_data_t data; + UBX_NAV_RELPOSNED_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_NAV_RELPOSNED_data_t); + UBX_NAV_RELPOSNED_data_t *callbackData; +} UBX_NAV_RELPOSNED_t; + +// RXM-specific structs + +// UBX-RXM-SFRBX (0x02 0x13): Broadcast navigation data subframe +// Note: length is variable +// Note: on protocol version 17: numWords is (0..16) +// on protocol version 18+: numWords is (0..10) +const uint8_t UBX_RXM_SFRBX_MAX_WORDS = 16; +const uint16_t UBX_RXM_SFRBX_MAX_LEN = 8 + (4 * UBX_RXM_SFRBX_MAX_WORDS); + +typedef struct +{ + uint8_t gnssId; // GNSS identifier + uint8_t svId; // Satellite identifier + uint8_t reserved1; + uint8_t freqId; // GLONASS frequency slot + uint8_t numWords; // The number of data words contained in this message (0..16) + uint8_t chn; // The tracking channel number the message was received on + uint8_t version; // Message version (0x01 for this version) + uint8_t reserved2; + uint32_t dwrd[UBX_RXM_SFRBX_MAX_WORDS]; // The data words +} UBX_RXM_SFRBX_data_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_RXM_SFRBX_data_t data; + boolean moduleQueried; + void (*callbackPointer)(UBX_RXM_SFRBX_data_t); + UBX_RXM_SFRBX_data_t *callbackData; +} UBX_RXM_SFRBX_t; + +// UBX-RXM-RAWX (0x02 0x15): Multi-GNSS raw measurement data +// Note: length is variable +const uint8_t UBX_RXM_RAWX_MAX_BLOCKS = 64; // GUESS! TO DO: find the correct value for this +const uint16_t UBX_RXM_RAWX_MAX_LEN = 16 + (32 * UBX_RXM_RAWX_MAX_BLOCKS); + +typedef struct +{ + uint8_t rcvTow[8]; // Measurement time of week in receiver local time [64-bit float] + uint16_t week; // GPS week number + int8_t leapS; // GPS leap seconds + uint8_t numMeas; // Numnber of measurements to follow + union + { + uint8_t all; + struct + { + uint8_t leapSec : 1; // Leap seconds have been determined + uint8_t clkReset : 1; // Clock reset applied + } bits; + } recStat; + uint8_t version; // Message version (0x01 for this version) + uint8_t reserved1[2]; +} UBX_RXM_RAWX_header_t; + +typedef struct +{ + uint8_t prMes[8]; // Pseudorange measurement: m [64-bit float] + uint8_t cpMes[8]; // Carrier phase measurement: cycles [64-bit float] + uint8_t doMes[4]; // Doppler measurement: Hz [32-bit float] + uint8_t gnssId; // GNSS identifier + uint8_t svId; // Satellite identifier + uint8_t sigId; // New signal identifier + uint8_t freqId; // GLONASS frequency slot + uint16_t lockTime; // Carrier phase locktime counter: ms + uint8_t cno; // Carrier-to-noise density ratio: dB-Hz + uint8_t prStdev; // Estimated pseudorange measurement standard deviation: m * 0.01 * 2^n [4-bit] + uint8_t cpStdev; // Estimated carrier phase measurement standard deviation: cycles * 0.004 [4-bit] + uint8_t doStdev; // Estimated Doppler measurement standard deviation: Hz * 0.002 * 2^n [4-bit] + union + { + uint8_t all; + struct + { + uint8_t prValid : 1; // Pseudorange valid + uint8_t cpValid : 1; // Carrier phase valid + uint8_t halfCyc : 1; // Half cycle valid + uint8_t subHalfCyc : 1; // Half cycle subtracted from phase + } bits; + } trkStat; + uint8_t reserved2; +} UBX_RXM_RAWX_block_t; + +typedef struct +{ + UBX_RXM_RAWX_header_t header; + UBX_RXM_RAWX_block_t blocks[UBX_RXM_RAWX_MAX_BLOCKS]; +} UBX_RXM_RAWX_data_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_RXM_RAWX_data_t data; + boolean moduleQueried; + void (*callbackPointer)(UBX_RXM_RAWX_data_t); + UBX_RXM_RAWX_data_t *callbackData; +} UBX_RXM_RAWX_t; + +// CFG-specific structs + +// UBX-CFG-RATE (0x06 0x08): Navigation/measurement rate settings +const uint16_t UBX_CFG_RATE_LEN = 6; + +typedef struct +{ + uint16_t measRate; // The elapsed time between GNSS measurements, which defines the rate: ms + uint16_t navRate; // The ratio between the number of measurements and the number of navigation solutions: cycles + uint16_t timeRef; // The time system to which measurements are aligned: 0: UTC; 1: GPS; 2: GLONASS; 3: BeiDou; 4: Galileo +} UBX_CFG_RATE_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t measRate : 1; + uint32_t navRate : 1; + uint32_t timeRef : 1; + } bits; + } moduleQueried; +} UBX_CFG_RATE_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_CFG_RATE_data_t data; + UBX_CFG_RATE_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_CFG_RATE_data_t); + UBX_CFG_RATE_data_t *callbackData; +} UBX_CFG_RATE_t; + +// TIM-specific structs + +// UBX-TIM-TM2 (0x0D 0x03): Time mark data +const uint16_t UBX_TIM_TM2_LEN = 28; + +typedef struct +{ + uint8_t ch; // Channel (i.e. EXTINT) upon which the pulse was measured + union + { + uint8_t all; + struct + { + uint8_t mode : 1; // 0=single; 1=running + uint8_t run : 1; // 0=armed; 1=stopped + uint8_t newFallingEdge : 1; // New falling edge detected + uint8_t timeBase : 2; // 0=Time base is Receiver time; 1=Time base is GNSS time; 2=Time base is UTC + uint8_t utc : 1; // 0=UTC not available; 1=UTC available + uint8_t time : 1; // 0=Time is not valid; 1=Time is valid (Valid GNSS fix) + uint8_t newRisingEdge : 1; // New rising edge detected + } bits; + } flags; + uint16_t count; // Rising edge counter + uint16_t wnR; // Week number of last rising edge + uint16_t wnF; // Week number of last falling edge + uint32_t towMsR; // TOW of rising edge: ms + uint32_t towSubMsR; // Millisecond fraction of tow of rising edge: ns + uint32_t towMsF; // TOW of falling edge: ms + uint32_t towSubMsF; // Millisecond fraction of tow of falling edge: ns + uint32_t accEst; // Accuracy estimate: ns +} UBX_TIM_TM2_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t ch : 1; + uint32_t mode : 1; + uint32_t run : 1; + uint32_t newFallingEdge : 1; + uint32_t timeBase : 1; + uint32_t utc : 1; + uint32_t time : 1; + uint32_t newRisingEdge : 1; + + uint32_t count : 1; + uint32_t wnR : 1; + uint32_t wnF : 1; + uint32_t towMsR : 1; + uint32_t towSubMsR : 1; + uint32_t towMsF : 1; + uint32_t towSubMsF : 1; + uint32_t accEst : 1; + } bits; + } moduleQueried; +} UBX_TIM_TM2_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_TIM_TM2_data_t data; + UBX_TIM_TM2_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_TIM_TM2_data_t); + UBX_TIM_TM2_data_t *callbackData; +} UBX_TIM_TM2_t; + +// ESF-specific structs + +// UBX-ESF-ALG (0x10 0x14): IMU alignment information +const uint16_t UBX_ESF_ALG_LEN = 16; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the HNR epoch: ms + uint8_t version; // Message version (0x01 for this version) + union + { + uint8_t all; + struct + { + uint8_t autoMntAlgOn : 1; // Automatic IMU-mount alignment on/off bit + uint8_t status : 3; // Status of the IMU-mount alignment + // 0: user-defined/fixed angles are used + // 1: IMU-mount roll/pitch angles alignment is ongoing + // 2: IMU-mount roll/pitch/yaw angles alignment is ongoing + // 3: coarse IMU-mount alignment are used + // 4: fine IMU-mount alignment are used + } bits; + } flags; + union + { + uint8_t all; + struct + { + uint8_t tiltAlgError : 1; // IMU-mount tilt (roll and/or pitch) alignment error (0: no error, 1: error) + uint8_t yawAlgError : 1; // IMU-mount yaw alignment error (0: no error, 1: error) + uint8_t angleError : 1; // IMU-mount misalignment Euler angle singularity error (0: no error, 1: error) + } bits; + } error; + uint8_t reserved1; + uint32_t yaw; // IMU-mount yaw angle [0, 360]: Degrees * 1e-2 + int16_t pitch; // IMU-mount pitch angle [-90, 90]: Degrees * 1e-2 + int16_t roll; // IMU-mount roll angle [-180, 180]: Degrees * 1e-2 +} UBX_ESF_ALG_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t version : 1; + + uint32_t autoMntAlgOn : 1; + uint32_t status : 1; + + uint32_t tiltAlgError : 1; + uint32_t yawAlgError : 1; + uint32_t angleError : 1; + + uint32_t yaw : 1; + uint32_t pitch : 1; + uint32_t roll : 1; + } bits; + } moduleQueried; +} UBX_ESF_ALG_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_ESF_ALG_data_t data; + UBX_ESF_ALG_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_ESF_ALG_data_t); + UBX_ESF_ALG_data_t *callbackData; +} UBX_ESF_ALG_t; + +// UBX-ESF-INS (0x10 0x15): Vehicle dynamics information +const uint16_t UBX_ESF_INS_LEN = 36; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t version : 8; // Message version (0x01 for this version) + uint32_t xAngRateValid : 1; // Compensated x-axis angular rate data validity flag (0: not valid, 1: valid) + uint32_t yAngRateValid : 1; // Compensated y-axis angular rate data validity flag (0: not valid, 1: valid) + uint32_t zAngRateValid : 1; // Compensated z-axis angular rate data validity flag (0: not valid, 1: valid) + uint32_t xAccelValid : 1; // Compensated x-axis acceleration data validity flag (0: not valid, 1: valid) + uint32_t yAccelValid : 1; // Compensated y-axis acceleration data validity flag (0: not valid, 1: valid) + uint32_t zAccelValid : 1; // Compensated z-axis acceleration data validity flag (0: not valid, 1: valid) + } bits; + } bitfield0; + uint8_t reserved1[4]; + uint32_t iTOW; // GPS time of week of the HNR epoch: ms + int32_t xAngRate; // Compensated x-axis angular rate: Degrees/s * 1e-3 + int32_t yAngRate; // Compensated y-axis angular rate: Degrees/s * 1e-3 + int32_t zAngRate; // Compensated z-axis angular rate: Degrees/s * 1e-3 + int32_t xAccel; // Compensated x-axis acceleration (gravity-free): m/s^2 * 1e-2 + int32_t yAccel; // Compensated y-axis acceleration (gravity-free): m/s^2 * 1e-2 + int32_t zAccel; // Compensated z-axis acceleration (gravity-free): m/s^2 * 1e-2 +} UBX_ESF_INS_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t version : 1; + uint32_t xAngRateValid : 1; + uint32_t yAngRateValid : 1; + uint32_t zAngRateValid : 1; + uint32_t xAccelValid : 1; + uint32_t yAccelValid : 1; + uint32_t zAccelValid : 1; + + uint32_t iTOW : 1; + uint32_t xAngRate : 1; + uint32_t yAngRate : 1; + uint32_t zAngRate : 1; + uint32_t xAccel : 1; + uint32_t yAccel : 1; + uint32_t zAccel : 1; + } bits; + } moduleQueried; +} UBX_ESF_INS_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_ESF_INS_data_t data; + UBX_ESF_INS_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_ESF_INS_data_t); + UBX_ESF_INS_data_t *callbackData; +} UBX_ESF_INS_t; + +// UBX-ESF-MEAS (0x10 0x02): External sensor fusion measurements +// Note: length is variable +const uint16_t UBX_ESF_MEAS_MAX_LEN = 8 + (4 * DEF_NUM_SENS) + 4; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t dataField : 24; // Data + uint32_t dataType : 6; // Type of data (0 = no data; 1..63 = data type) + } bits; + } data; +} UBX_ESF_MEAS_sensorData_t; + +typedef struct +{ + uint32_t timeTag; // Time tag of measurement generated by external sensor + union + { + uint16_t all; + struct + { + uint16_t timeMarkSent : 2; // Time mark signal was supplied just prior to sending this message: + // 0 = none, 1 = on Ext0, 2 = on Ext1 + uint16_t timeMarkEdge : 1; // Trigger on rising (0) or falling (1) edge of time mark signal + uint16_t calibTtagValid : 1; // Calibration time tag available. Always set to zero. + uint16_t reserved : 7; + uint16_t numMeas : 5; // Number of measurements contained in this message (optional, can be obtained from message size) + } bits; + } flags; + uint16_t id; // Identification number of data provider + UBX_ESF_MEAS_sensorData_t data[DEF_NUM_SENS]; + uint32_t calibTtag; // OPTIONAL: Receiver local time calibrated: ms +} UBX_ESF_MEAS_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t timeMarkSent : 1; + uint32_t timeMarkEdge : 1; + uint32_t calibTtagValid : 1; + uint32_t numMeas : 1; + + uint32_t id : 1; + + uint32_t data : DEF_NUM_SENS; + + uint32_t calibTtag : 1; + } bits; + } moduleQueried; +} UBX_ESF_MEAS_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_ESF_MEAS_data_t data; + UBX_ESF_MEAS_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_ESF_MEAS_data_t); + UBX_ESF_MEAS_data_t *callbackData; +} UBX_ESF_MEAS_t; + +// UBX-ESF-RAW (0x10 0x03): Raw sensor measurements +// Note: length is variable +const uint16_t UBX_ESF_RAW_MAX_LEN = 4 + (8 * DEF_NUM_SENS); + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t dataField : 24; // Data + uint32_t dataType : 8; // Type of data (0 = no data; 1..255 = data type) + } bits; + } data; + uint32_t sTag; // Sensor time tag +} UBX_ESF_RAW_sensorData_t; + +typedef struct +{ + uint8_t reserved1[4]; + UBX_ESF_RAW_sensorData_t data[DEF_NUM_SENS]; +} UBX_ESF_RAW_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t data : DEF_NUM_SENS; + } bits; + } moduleQueried; +} UBX_ESF_RAW_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_ESF_RAW_data_t data; + UBX_ESF_RAW_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_ESF_RAW_data_t); + UBX_ESF_RAW_data_t *callbackData; +} UBX_ESF_RAW_t; + +// UBX-ESF-STATUS (0x10 0x10): External sensor fusion status +// Note: length is variable +const uint16_t UBX_ESF_STATUS_MAX_LEN = 16 + (4 * DEF_NUM_SENS); + +typedef struct +{ + union + { + uint8_t all; + struct + { + uint8_t type : 6; // Sensor data type + uint8_t used : 1; // If set, sensor data is used for the current sensor fusion solution + uint8_t ready : 1; // If set, sensor is set up (configuration is available or not required) but not used for computing the current sensor fusion solution. + } bits; + } sensStatus1; + union + { + uint8_t all; + struct + { + uint8_t calibStatus : 2; // 00: Sensor is not calibrated + // 01: Sensor is calibrating + // 10/11: Sensor is calibrated + uint8_t timeStatus : 2; // 00: No data + // 01: Reception of the first byte used to tag the measurement + // 10: Event input used to tag the measurement + // 11: Time tag provided with the data + } bits; + } sensStatus2; + uint8_t freq; // Observation frequency: Hz + union + { + uint8_t all; + struct + { + uint8_t badMeas : 1; // Bad measurements detected + uint8_t badTTag : 1; // Bad measurement time-tags detected + uint8_t missingMeas : 1; // Missing or time-misaligned measurements detected + uint8_t noisyMeas : 1; // High measurement noise-level detected + } bits; + } faults; +} UBX_ESF_STATUS_sensorStatus_t; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the HNR epoch: ms + uint8_t version; // Message version (0x02 for this version) + uint8_t reserved1[7]; + uint8_t fusionMode; // Fusion mode: + // 0: Initialization mode: receiver is initializing some unknown values required for doing sensor fusion + // 1: Fusion mode: GNSS and sensor data are used for navigation solution computation + // 2: Suspended fusion mode: sensor fusion is temporarily disabled due to e.g. invalid sensor data or detected ferry + // 3: Disabled fusion mode: sensor fusion is permanently disabled until receiver reset due e.g. to sensor error + uint8_t reserved2[2]; + uint8_t numSens; // Number of sensors + UBX_ESF_STATUS_sensorStatus_t status[DEF_NUM_SENS]; +} UBX_ESF_STATUS_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t version : 1; + uint32_t fusionMode : 1; + uint32_t numSens : 1; + + uint32_t status : DEF_NUM_SENS; + } bits; + } moduleQueried; +} UBX_ESF_STATUS_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_ESF_STATUS_data_t data; + UBX_ESF_STATUS_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_ESF_STATUS_data_t); + UBX_ESF_STATUS_data_t *callbackData; +} UBX_ESF_STATUS_t; + +// HNR-specific structs + +// UBX-HNR-PVT (0x28 0x00): High rate output of PVT solution +const uint16_t UBX_HNR_PVT_LEN = 72; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the HNR epoch: ms + uint16_t year; // Year (UTC) + uint8_t month; // Month, range 1..12 (UTC) + uint8_t day; // Day of month, range 1..31 (UTC) + uint8_t hour; // Hour of day, range 0..23 (UTC) + uint8_t min; // Minute of hour, range 0..59 (UTC) + uint8_t sec; // Seconds of minute, range 0..60 (UTC) + union + { + uint8_t all; + struct + { + uint8_t validDate : 1; // 1 = Valid UTC Date + uint8_t validTime : 1; // 1 = Valid UTC Time of Day + uint8_t fullyResolved : 1; // 1 = UTC Time of Day has been fully resolved + } bits; + } valid; + int32_t nano; // Fraction of second (UTC): ns + uint8_t gpsFix; // GPSfix Type, range 0..5 + // 0x00 = No Fix + // 0x01 = Dead Reckoning only + // 0x02 = 2D-Fix + // 0x03 = 3D-Fix + // 0x04 = GPS + dead reckoning combined + // 0x05 = Time only fix + // 0x06..0xff: reserved + union + { + uint8_t all; + struct + { + uint8_t gpsFixOK : 1; // >1 = Fix within limits (e.g. DOP & accuracy) + uint8_t diffSoln : 1; // 1 = DGPS used + uint8_t WKNSET : 1; // 1 = Valid GPS week number + uint8_t TOWSET : 1; // 1 = Valid GPS time of week (iTOW & fTOW) + uint8_t headVehValid : 1; // 1= Heading of vehicle is valid + } bits; + } flags; + uint8_t reserved1[2]; + int32_t lon; // Longitude: Degrees * 1e-7 + int32_t lat; // Latitude: Degrees * 1e-7 + int32_t height; // Height above ellipsoid: mm + int32_t hMSL; // Height above MSL: mm + int32_t gSpeed; // Ground Speed (2-D): mm/s + int32_t speed; // Speed (3-D): mm/s + int32_t headMot; // Heading of motion (2-D): Degrees * 1e-5 + int32_t headVeh; // Heading of vehicle (2-D): Degrees * 1e-5 + uint32_t hAcc; // Horizontal accuracy: mm + uint32_t vAcc; // Vertical accuracy: mm + uint32_t sAcc; // Speed accuracy: mm/s + uint32_t headAcc; // Heading accuracy: Degrees * 1e-5 + uint8_t reserved2[4]; +} UBX_HNR_PVT_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t year : 1; + uint32_t month : 1; + uint32_t day : 1; + uint32_t hour : 1; + uint32_t min : 1; + uint32_t sec : 1; + + uint32_t validDate : 1; + uint32_t validTime : 1; + uint32_t fullyResolved : 1; + + uint32_t nano : 1; + uint32_t gpsFix : 1; + + uint32_t gpsFixOK : 1; + uint32_t diffSoln : 1; + uint32_t WKNSET : 1; + uint32_t TOWSET : 1; + uint32_t headVehValid : 1; + + uint32_t lon : 1; + uint32_t lat : 1; + uint32_t height : 1; + uint32_t hMSL : 1; + uint32_t gSpeed : 1; + uint32_t speed : 1; + uint32_t headMot : 1; + uint32_t headVeh : 1; + uint32_t hAcc : 1; + uint32_t vAcc : 1; + uint32_t sAcc : 1; + uint32_t headAcc : 1; + } bits; + } moduleQueried; +} UBX_HNR_PVT_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_HNR_PVT_data_t data; + UBX_HNR_PVT_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_HNR_PVT_data_t); + UBX_HNR_PVT_data_t *callbackData; +} UBX_HNR_PVT_t; + +// UBX-HNR-ATT (0x28 0x01): Attitude solution +const uint16_t UBX_HNR_ATT_LEN = 32; + +typedef struct +{ + uint32_t iTOW; // GPS time of week of the navigation epoch: ms + uint8_t version; + uint8_t reserved1[3]; + int32_t roll; // Vehicle roll: Degrees * 1e-5 + int32_t pitch; // Vehicle pitch: Degrees * 1e-5 + int32_t heading; // Vehicle heading: Degrees * 1e-5 + uint32_t accRoll; // Vehicle roll accuracy: Degrees * 1e-5 + uint32_t accPitch; // Vehicle pitch accuracy: Degrees * 1e-5 + uint32_t accHeading; // Vehicle heading accuracy: Degrees * 1e-5 +} UBX_HNR_ATT_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t iTOW : 1; + uint32_t version : 1; + uint32_t roll : 1; + uint32_t pitch : 1; + uint32_t heading : 1; + uint32_t accRoll : 1; + uint32_t accPitch : 1; + uint32_t accHeading : 1; + } bits; + } moduleQueried; +} UBX_HNR_ATT_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_HNR_ATT_data_t data; + UBX_HNR_ATT_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_HNR_ATT_data_t); + UBX_HNR_ATT_data_t *callbackData; +} UBX_HNR_ATT_t; + +// UBX-HNR-INS (0x28 0x02): Vehicle dynamics information +const uint16_t UBX_HNR_INS_LEN = 36; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t version : 8; // Message version (0x00 for this version) + uint32_t xAngRateValid : 1; // Compensated x-axis angular rate data validity flag (0: not valid, 1: valid) + uint32_t yAngRateValid : 1; // Compensated y-axis angular rate data validity flag (0: not valid, 1: valid) + uint32_t zAngRateValid : 1; // Compensated z-axis angular rate data validity flag (0: not valid, 1: valid) + uint32_t xAccelValid : 1; // Compensated x-axis acceleration data validity flag (0: not valid, 1: valid) + uint32_t yAccelValid : 1; // Compensated y-axis acceleration data validity flag (0: not valid, 1: valid) + uint32_t zAccelValid : 1; // Compensated z-axis acceleration data validity flag (0: not valid, 1: valid) + } bits; + } bitfield0; + uint8_t reserved1[4]; + uint32_t iTOW; // GPS time of week of the HNR epoch: ms + int32_t xAngRate; // Compensated x-axis angular rate: Degrees/s * 1e-3 + int32_t yAngRate; // Compensated y-axis angular rate: Degrees/s * 1e-3 + int32_t zAngRate; // Compensated z-axis angular rate: Degrees/s * 1e-3 + int32_t xAccel; // Compensated x-axis acceleration (with gravity): m/s^2 * 1e-2 + int32_t yAccel; // Compensated y-axis acceleration (with gravity): m/s^2 * 1e-2 + int32_t zAccel; // Compensated z-axis acceleration (with gravity): m/s^2 * 1e-2 +} UBX_HNR_INS_data_t; + +typedef struct +{ + union + { + uint32_t all; + struct + { + uint32_t all : 1; + + uint32_t version : 1; + uint32_t xAngRateValid : 1; + uint32_t yAngRateValid : 1; + uint32_t zAngRateValid : 1; + uint32_t xAccelValid : 1; + uint32_t yAccelValid : 1; + uint32_t zAccelValid : 1; + + uint32_t iTOW : 1; + uint32_t xAngRate : 1; + uint32_t yAngRate : 1; + uint32_t zAngRate : 1; + uint32_t xAccel : 1; + uint32_t yAccel : 1; + uint32_t zAccel : 1; + } bits; + } moduleQueried; +} UBX_HNR_INS_moduleQueried_t; + +typedef struct +{ + ubxAutomaticFlags automaticFlags; + UBX_HNR_INS_data_t data; + UBX_HNR_INS_moduleQueried_t moduleQueried; + void (*callbackPointer)(UBX_HNR_INS_data_t); + UBX_HNR_INS_data_t *callbackData; +} UBX_HNR_INS_t; + +#endif