├── .gitattributes ├── CMakeLists.txt ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── README.md ├── examples ├── Example10_SetRoverMode │ └── Example10_SetRoverMode.ino ├── Example11_UsbNMEA │ └── Example11_UsbNMEA.ino ├── Example12_FactoryReset │ └── Example12_FactoryReset.ino ├── Example13_SendCommand │ └── Example13_SendCommand.ino ├── Example14_SetPPS │ └── Example14_SetPPS.ino ├── Example15_QueryDevice │ └── Example15_QueryDevice.ino ├── Example16_PollForValidRTCMMessages │ └── Example16_PollForValidRTCMMessages.ino ├── Example17_Parser │ └── Example17_Parser.ino ├── Example18_CheckConfig │ └── Example18_CheckConfig.ino ├── Example19_Enable_Galileo_HAS │ └── Example19_Enable_Galileo_HAS.ino ├── Example1_PositionVelocityTime │ └── Example1_PositionVelocityTime.ino ├── Example20_NTRIP_Client │ ├── Example20_NTRIP_Client.ino │ └── secrets.h ├── Example21_NTRIP_Server │ ├── Example21_NTRIP_Server.ino │ └── secrets.h ├── Example2_DirectConnect │ └── Example2_DirectConnect.ino ├── Example3_ECEFAndStats │ └── Example3_ECEFAndStats.ino ├── Example4_EnableNMEA_5Hz │ └── Example4_EnableNMEA_5Hz.ino ├── Example5_EnableRTCM │ └── Example5_EnableRTCM.ino ├── Example6_AverageBase │ └── Example6_AverageBase.ino ├── Example7_FixedBase │ └── Example7_FixedBase.ino ├── Example8_SetConstellations │ └── Example8_SetConstellations.ino └── Example9_SignalElevation │ └── Example9_SignalElevation.ino ├── keywords.txt ├── library.properties └── src ├── SparkFun_Unicore_GNSS_Arduino_Library.cpp ├── SparkFun_Unicore_GNSS_Arduino_Library.h └── unicore_structs.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCDIRS 2 | "src" 3 | ) 4 | 5 | set(COMPONENT_ADD_INCLUDEDIRS 6 | "src" 7 | ) 8 | 9 | set(COMPONENT_REQUIRES 10 | "arduino-esp32" 11 | "SparkFun_Extensible_Message_Parser" 12 | ) 13 | 14 | register_component() -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Subject of the issue 2 | 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. 3 | 4 | ### Your workbench 5 | * What development board or microcontroller are you using? 6 | * What version of hardware or breakout board are you using? 7 | * How is the breakout board wired to your microcontroller? 8 | * How is everything being powered? 9 | * Are there any additional details that may help us help you? 10 | 11 | ### Steps to reproduce 12 | Tell us how to reproduce this issue. Please post stripped down example code demonstrating your issue. 13 | 14 | ### Expected behavior 15 | Tell us what should happen 16 | 17 | ### Actual behavior 18 | Tell us what happens instead 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | SparkFun License Information 2 | ============================ 3 | 4 | SparkFun uses two different licenses for our files — one for hardware and one for code. 5 | 6 | Hardware 7 | --------- 8 | 9 | **SparkFun hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).** 10 | 11 | Note: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-sa/4.0/legalcode). 12 | 13 | You are free to: 14 | 15 | Share — copy and redistribute the material in any medium or format 16 | Adapt — remix, transform, and build upon the material 17 | for any purpose, even commercially. 18 | The licensor cannot revoke these freedoms as long as you follow the license terms. 19 | Under the following terms: 20 | 21 | 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. 22 | ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 23 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 24 | Notices: 25 | 26 | 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. 27 | 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. 28 | 29 | 30 | Code 31 | -------- 32 | 33 | **SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT).** 34 | 35 | The MIT License (MIT) 36 | 37 | Copyright (c) 2016 SparkFun Electronics 38 | 39 | Permission is hereby granted, free of charge, to any person obtaining a copy 40 | of this software and associated documentation files (the "Software"), to deal 41 | in the Software without restriction, including without limitation the rights 42 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 43 | copies of the Software, and to permit persons to whom the Software is 44 | furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included in all 47 | copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 50 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 51 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 52 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 53 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 54 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 55 | SOFTWARE. 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SparkFun Unicore GNSS Arduino Library 2 | =========================================================== 3 | 4 | [![SparkFun Triband GNSS RTK Breakout - UM980](https://cdn.sparkfun.com/r/600-600/assets/parts/2/3/4/8/1/23286-UM980-Triband-GNSS-Breakout-Feature.jpg)](https://www.sparkfun.com/products/23286) 5 | 6 | [*SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286)*](https://www.sparkfun.com/products/23286) 7 | 8 | 9 | 10 | Repository Contents 11 | ------------------- 12 | 13 | * **/examples** - Example sketches for the library (.ino). Run these from the Arduino IDE. 14 | * **/src** - Source files for the library (.cpp, .h). 15 | * **keywords.txt** - Keywords from this library that will be highlighted in the Arduino IDE. 16 | * **library.properties** - General library properties for the Arduino package manager. 17 | 18 | Documentation 19 | -------------- 20 | 21 | * **[Installing an Arduino Library Guide](https://learn.sparkfun.com/tutorials/installing-an-arduino-library)** - Basic information on how to install an Arduino library. 22 | * **[Hookup Guide](https://docs.sparkfun.com/SparkFun_UM980_Triband_GNSS_RTK_Breakout/)** - Basic hookup guide for the UM980. 23 | * **[Product Repository](https://github.com/sparkfun/SparkFun_UM980_Triband_GNSS_RTK_Breakout)** - Main repository (including hardware files) for the UM980. 24 | 25 | License Information 26 | ------------------- 27 | 28 | This product is _**open source**_! 29 | 30 | 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! 31 | 32 | 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. 33 | 34 | Distributed as-is; no warranty is given. 35 | 36 | - Your friends at SparkFun. 37 | -------------------------------------------------------------------------------- /examples/Example10_SetRoverMode/Example10_SetRoverMode.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Set the UM980 RTK station mode 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to set the station mode to Survey, UAV, or Automotive. 9 | These examples are targeted for an ESP32 platform but any platform that has multiple 10 | serial UARTs should be compatible. 11 | 12 | Feel like supporting open source hardware? 13 | Buy a board from SparkFun! 14 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 15 | 16 | Hardware Connections: 17 | Connect RX2 of the UM980 to pin 4 on the ESP32 18 | Connect TX2 of the UM980 to pin 13 on the ESP32 19 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 20 | Note: Almost any ESP32 pins can be used for serial. 21 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 22 | */ 23 | 24 | int pin_UART1_TX = 4; 25 | int pin_UART1_RX = 13; 26 | 27 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 28 | 29 | UM980 myGNSS; 30 | 31 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 32 | 33 | void setup() 34 | { 35 | Serial.begin(115200); 36 | delay(250); 37 | Serial.println(); 38 | Serial.println("SparkFun UM980 Example 10"); 39 | 40 | //We must start the serial port before using it in the library 41 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 42 | 43 | //myGNSS.enableDebugging(); // Print all debug to Serial 44 | 45 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 46 | { 47 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 48 | while (true); 49 | } 50 | Serial.println("UM980 detected!"); 51 | 52 | //The user can set a station mode of Survey, UAV, and Automotive. These change the nature of the RTK 53 | //fix algorithm. "Survey mode is suitable for high-precision application scenarios which require higher 54 | //positioning accuracy but with lower dynamic features, such as surveying and mapping, precision agriculture, etc." 55 | myGNSS.setModeRoverSurvey(); 56 | //myGNSS.setModeRoverUAV(); 57 | //myGNSS.setModeRoverAutomotive(); 58 | 59 | myGNSS.saveConfiguration(); //Save the current configuration into non-volatile memory (NVM) 60 | 61 | Serial.println("Configuration complete"); 62 | } 63 | 64 | void loop() 65 | { 66 | if (SerialGNSS.available()) 67 | Serial.write(SerialGNSS.read()); 68 | } 69 | -------------------------------------------------------------------------------- /examples/Example11_UsbNMEA/Example11_UsbNMEA.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Enable NMEA out COM1 (USB-C) for viewing in u-center (u-blox's software) or UPrecise (Unicore's software) 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This sketch turns on all the major NMEA sentences at 2Hz and prints the 9 | messages over the USB C port (COM1 on the UM980). This is useful for viewing the GNSS 10 | data in a program like u-center. 11 | These examples are targeted for an ESP32 platform but any platform that has multiple 12 | serial UARTs should be compatible. 13 | 14 | Feel like supporting open source hardware? 15 | Buy a board from SparkFun! 16 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 17 | 18 | Hardware Connections: 19 | Connect a USB C cable to the UM980 breakout board 20 | Connect RX2 of the UM980 to pin 4 on the ESP32 21 | Connect TX2 of the UM980 to pin 13 on the ESP32 22 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 23 | Open a terminal on the CH340 COM port at 115200bps 24 | NMEA will be displayed at 2Hz 25 | 26 | Note: Almost any ESP32 pins can be used for serial. 27 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 28 | */ 29 | 30 | int pin_UART1_TX = 4; 31 | int pin_UART1_RX = 13; 32 | 33 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 34 | 35 | UM980 myGNSS; 36 | 37 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 38 | 39 | void setup() 40 | { 41 | Serial.begin(115200); 42 | delay(250); 43 | Serial.println(); 44 | Serial.println("SparkFun UM980 Example 11"); 45 | 46 | //We must start the serial port before using it in the library 47 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 48 | 49 | myGNSS.enableDebugging(); // Print all debug to Serial 50 | 51 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 52 | { 53 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 54 | while (true); 55 | } 56 | Serial.println("UM980 detected!"); 57 | 58 | bool response = true; 59 | 60 | //Turn off all NMEA, RTCM, and any other messages that may be reporting periodic 61 | response &= myGNSS.disableOutput(); 62 | 63 | float outputRate = 0.5; //0.5 = 2 reports per second. 64 | 65 | char comPort[] = "COM1"; 66 | response &= myGNSS.setNMEAPortMessage("GPGGA", comPort, outputRate); 67 | response &= myGNSS.setNMEAPortMessage("GPGSA", comPort, outputRate); 68 | response &= myGNSS.setNMEAPortMessage("GPGST", comPort, outputRate); 69 | response &= myGNSS.setNMEAPortMessage("GPGSV", comPort, outputRate); 70 | response &= myGNSS.setNMEAPortMessage("GPRMC", comPort, outputRate); 71 | response &= myGNSS.saveConfiguration(); //Save the current configuration into non-volatile memory (NVM) 72 | 73 | //If any one command fails, it will force response to false 74 | if(response == false) 75 | { 76 | Serial.println("UM980 failed to configure. Freezing..."); 77 | while(true); 78 | } 79 | 80 | Serial.println("NMEA now reporting on USB-C serial port"); 81 | } 82 | 83 | void loop() 84 | { 85 | while(SerialGNSS.available()) 86 | Serial.write(SerialGNSS.read()); 87 | } 88 | -------------------------------------------------------------------------------- /examples/Example12_FactoryReset/Example12_FactoryReset.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Reset the UM980 to factory defaults 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to set the UM980 back to defaults. 9 | These examples are targeted for an ESP32 platform but any platform that has multiple 10 | serial UARTs should be compatible. 11 | 12 | Feel like supporting open source hardware? 13 | Buy a board from SparkFun! 14 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 15 | 16 | Hardware Connections: 17 | Connect RX2 of the UM980 to pin 4 on the ESP32 18 | Connect TX2 of the UM980 to pin 13 on the ESP32 19 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 20 | Note: Almost any ESP32 pins can be used for serial. 21 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 22 | */ 23 | 24 | int pin_UART1_TX = 4; 25 | int pin_UART1_RX = 13; 26 | 27 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 28 | 29 | UM980 myGNSS; 30 | 31 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 32 | 33 | unsigned long lastCheck = 0; 34 | 35 | void setup() 36 | { 37 | Serial.begin(115200); 38 | delay(250); 39 | Serial.println(); 40 | Serial.println("SparkFun UM980 Example 12"); 41 | 42 | //We must start the serial port before using it in the library 43 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 44 | 45 | //myGNSS.enableDebugging(); // Print all debug to Serial 46 | 47 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 48 | { 49 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 50 | while (true); 51 | } 52 | Serial.println("UM980 detected!"); 53 | 54 | while (Serial.available()) Serial.read(); //Clear RX buffer 55 | Serial.println("Press any key to factory reset the UM980"); 56 | while (Serial.available() == 0) delay(1); //Wait for user to press a button 57 | 58 | // Clear saved configurations, satellite ephemerides, position information, and reset baud rate to 115200bps. 59 | if (myGNSS.factoryReset() == true) 60 | Serial.println("UM980 now reset to factory defaults"); 61 | else 62 | Serial.println("Error resetting UM980 to factory defaults"); 63 | 64 | Serial.println("Waiting for UM980 to reboot"); 65 | 66 | while (1) 67 | { 68 | delay(1000); //Wait for device to reboot 69 | if (myGNSS.isConnected() == true) break; 70 | else Serial.println("Device still rebooting"); 71 | } 72 | 73 | Serial.println("UM980 has completed reset"); 74 | 75 | // Resetting the receiver will clear the satellite ephemerides, position information, satellite 76 | // almanacs, ionosphere parameters and UTC parameters saved in the receiver. 77 | //myGNSS.reset(); 78 | 79 | //Turn off all NMEA, RTCM, and any other message that may be reporting periodically 80 | //myGNSS.disableOutput(); 81 | 82 | // Saves the current configuration into non-volatile memory (NVM) 83 | //myGNSS.saveConfiguration(); 84 | } 85 | 86 | void loop() 87 | { 88 | myGNSS.update(); //Regularly call to parse any new data 89 | 90 | if (millis() - lastCheck > 1000) 91 | { 92 | lastCheck = millis(); 93 | 94 | //The get methods are updated whenever new data is parsed with the update() call. 95 | //By default, this data is updated once per second. 96 | 97 | Serial.print("Lat/Long/Alt: "); 98 | Serial.print(myGNSS.getLatitude(), 11); 99 | Serial.print("/"); 100 | Serial.print(myGNSS.getLongitude(), 11); 101 | Serial.print("/"); 102 | Serial.print(myGNSS.getAltitude(), 4); 103 | Serial.println(); 104 | 105 | Serial.print("Horizontal Speed: "); 106 | Serial.print(myGNSS.getHorizontalSpeed()); 107 | Serial.print("m/s Vertical Speed: "); 108 | Serial.print(myGNSS.getVerticalSpeed()); 109 | Serial.print("m/s Direction from North: "); 110 | Serial.print(myGNSS.getTrackGround()); 111 | Serial.print("(degrees)"); 112 | Serial.println(); 113 | 114 | Serial.print("Date (yyyy/mm/dd): "); 115 | Serial.print(myGNSS.getYear()); 116 | Serial.print("/"); 117 | if (myGNSS.getMonth() < 10) 118 | Serial.print("0"); 119 | Serial.print(myGNSS.getMonth()); 120 | Serial.print("/"); 121 | if (myGNSS.getDay() < 10) 122 | Serial.print("0"); 123 | Serial.print(myGNSS.getDay()); 124 | 125 | Serial.print(" Time (hh:mm:dd): "); 126 | if (myGNSS.getHour() < 10) 127 | Serial.print("0"); 128 | Serial.print(myGNSS.getHour()); 129 | Serial.print(":"); 130 | if (myGNSS.getMinute() < 10) 131 | Serial.print("0"); 132 | Serial.print(myGNSS.getMinute()); 133 | Serial.print(":"); 134 | if (myGNSS.getSecond() < 10) 135 | Serial.print("0"); 136 | Serial.print(myGNSS.getSecond()); 137 | Serial.println(); 138 | 139 | Serial.print("Satellites in view: "); 140 | Serial.print(myGNSS.getSIV()); 141 | Serial.println(); 142 | 143 | Serial.println(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /examples/Example13_SendCommand/Example13_SendCommand.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Sending direct commands to the UM980 GNSS receiver 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | While the SparkFun UM980 Arduino library covers most of the features in the UM980, there 9 | may be a special command that is needed but not supported. This sketch shows how to send commands direct. 10 | 11 | These examples are targeted for an ESP32 platform but any platform that has multiple 12 | serial UARTs should be compatible. 13 | 14 | Feel like supporting open source hardware? 15 | Buy a board from SparkFun! 16 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 17 | 18 | Hardware Connections: 19 | Connect RX2 of the UM980 to pin 4 on the ESP32 20 | Connect TX2 of the UM980 to pin 13 on the ESP32 21 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 22 | Note: Almost any ESP32 pins can be used for serial. 23 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 24 | */ 25 | 26 | int pin_UART1_TX = 4; 27 | int pin_UART1_RX = 13; 28 | 29 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 30 | 31 | UM980 myGNSS; 32 | 33 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 34 | 35 | void setup() 36 | { 37 | Serial.begin(115200); 38 | delay(250); 39 | Serial.println(); 40 | Serial.println("SparkFun UM980 Example 13"); 41 | 42 | //We must start the serial port before using it in the library 43 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 44 | 45 | myGNSS.enableDebugging(); // Print all debug to Serial 46 | 47 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 48 | { 49 | Serial.println("UM980 failed to respond. Check ports and baud rates."); 50 | while (1); 51 | } 52 | Serial.println("UM980 detected!"); 53 | 54 | //Turn off all NMEA, RTCM, and any other message that may be reporting periodically 55 | myGNSS.disableOutput(); 56 | 57 | //sendCommand() sends the string directly and checks for the OK response 58 | //Returns true if the OK was detected 59 | if(myGNSS.sendCommand("UNMASK GPS") == true) Serial.println("GPS enabled"); 60 | else Serial.println("GPS unmask error"); 61 | 62 | myGNSS.sendCommand("MASK 10 GPS"); //Set the elevation mask angle as 10 degrees for GPS 63 | } 64 | 65 | void loop() 66 | { 67 | 68 | } 69 | -------------------------------------------------------------------------------- /examples/Example14_SetPPS/Example14_SetPPS.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Configuring the pulse-per-second signal 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | The example shows how to configure the PPS signal's width, frequency, and polarity. 9 | These examples are targeted for an ESP32 platform but any platform that has multiple 10 | serial UARTs should be compatible. 11 | 12 | Feel like supporting open source hardware? 13 | Buy a board from SparkFun! 14 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 15 | 16 | Hardware Connections: 17 | Connect RX2 of the UM980 to pin 4 on the ESP32 18 | Connect TX2 of the UM980 to pin 13 on the ESP32 19 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 20 | Note: Almost any ESP32 pins can be used for serial. 21 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 22 | */ 23 | 24 | int pin_UART1_TX = 4; 25 | int pin_UART1_RX = 13; 26 | 27 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 28 | 29 | UM980 myGNSS; 30 | 31 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 32 | 33 | void setup() 34 | { 35 | Serial.begin(115200); 36 | delay(250); 37 | Serial.println(); 38 | Serial.println("SparkFun UM980 Example 14"); 39 | 40 | //We must start the serial port before using it in the library 41 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 42 | 43 | myGNSS.enableDebugging(); // Print all debug to Serial 44 | 45 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 46 | { 47 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 48 | while (true); 49 | } 50 | Serial.println("UM980 detected!"); 51 | 52 | //Enable PPS signal with a width of 100ms, and a period of 1 second 53 | //Valid periodMilliseconds values: 50, 100, 200, ..., 20000. So there is a max freq of 20Hz. 54 | myGNSS.enablePPS(100000, 1000); //widthMicroseconds, periodMilliseconds 55 | 56 | //You can also set the polarity, RF delay, and user delay. 57 | //myGNSS.enablePPS(100000, 1000, false, 0, 0); //widthMicroseconds, periodMilliseconds, positivePolarity, rfDelayNanoSeconds, userDelayNanoseconds 58 | 59 | myGNSS.saveConfiguration(); //Save the current configuration into non-volatile memory (NVM) 60 | 61 | Serial.println("The PPS LED should now be blinking 100ms on, 900ms off"); 62 | } 63 | 64 | void loop() 65 | { 66 | 67 | } 68 | -------------------------------------------------------------------------------- /examples/Example15_QueryDevice/Example15_QueryDevice.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Get the current configuration, version, mode, and mask from UM980 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to query a device for its version and unique ID. 9 | These examples are targeted for an ESP32 platform but any platform that has multiple 10 | serial UARTs should be compatible. 11 | 12 | Feel like supporting open source hardware? 13 | Buy a board from SparkFun! 14 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 15 | 16 | Hardware Connections: 17 | Connect RX2 of the UM980 to pin 4 on the ESP32 18 | Connect TX2 of the UM980 to pin 13 on the ESP32 19 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 20 | Note: Almost any ESP32 pins can be used for serial. 21 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 22 | */ 23 | 24 | int pin_UART1_TX = 4; 25 | int pin_UART1_RX = 13; 26 | 27 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 28 | 29 | UM980 myGNSS; 30 | 31 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 32 | 33 | void setup() 34 | { 35 | Serial.begin(115200); 36 | delay(250); 37 | Serial.println(); 38 | Serial.println("UM980 comm over ESP UART1"); 39 | 40 | //We must start the serial port before using it in the library 41 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 42 | 43 | //myGNSS.enableDebugging(); // Print all debug to Serial 44 | 45 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 46 | { 47 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 48 | while (true); 49 | } 50 | Serial.println("UM980 detected!"); 51 | 52 | //Turn off all NMEA, RTCM, and any other message that may be reporting periodically 53 | myGNSS.disableOutput(); 54 | 55 | Serial.print("Model Type: "); 56 | Serial.print(myGNSS.getModelType()); 57 | Serial.println(); 58 | 59 | Serial.print("Unique Module ID: "); 60 | Serial.print(myGNSS.getID()); 61 | Serial.println(); 62 | 63 | Serial.print("Firmware Version: "); 64 | Serial.print(myGNSS.getVersion()); 65 | Serial.println(); 66 | 67 | Serial.print("Firmware Compile Time: "); 68 | Serial.print(myGNSS.getCompileTime()); 69 | Serial.println(); 70 | 71 | Serial.print("Full Version Report: "); 72 | Serial.print(myGNSS.getVersionFull()); 73 | Serial.println(); 74 | } 75 | 76 | void loop() 77 | { 78 | if (Serial.available()) ESP.restart(); 79 | } -------------------------------------------------------------------------------- /examples/Example16_PollForValidRTCMMessages/Example16_PollForValidRTCMMessages.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Detect which RTCM messages are supported - currently 55! 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | The documentation for the UM980 does not explicitly state which RTCM messages are supported 9 | but by sending the enable command to the module we can insinuate which are supported. 10 | This sketch sends the 'RTCMxxxx' command and looks for an OK. 11 | The messages that successfully receive an OK will be reported at the completion of the sketch. 12 | These examples are targeted for an ESP32 platform but any platform that has multiple 13 | serial UARTs should be compatible. 14 | 15 | Feel like supporting open source hardware? 16 | Buy a board from SparkFun! 17 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 18 | 19 | Hardware Connections: 20 | Connect RX2 of the UM980 to pin 4 on the ESP32 21 | Connect TX2 of the UM980 to pin 13 on the ESP32 22 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 23 | Note: Almost any ESP32 pins can be used for serial. 24 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 25 | 26 | 68 RTCM messages supported: 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1009, 1010, 1011, 1012, 1013, 1019, 1020, 27 | 1033, 1042, 1044, 1045, 1046, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 28 | 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1111, 1112, 1113, 1114, 1115, 29 | 1116, 1117, 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 30 | 31 | */ 32 | 33 | int pin_UART1_TX = 4; 34 | int pin_UART1_RX = 13; 35 | 36 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 37 | 38 | UM980 myGNSS; 39 | 40 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 41 | 42 | void setup() 43 | { 44 | Serial.begin(115200); 45 | delay(250); 46 | Serial.println(); 47 | Serial.println("SparkFun UM980 Example 16"); 48 | 49 | //We must start the serial port before using it in the library 50 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 51 | 52 | // myGNSS.enableDebugging(); // Print all debug to Serial 53 | 54 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 55 | { 56 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 57 | while (true); 58 | } 59 | Serial.println("UM980 detected!"); 60 | 61 | //Turn off all NMEA, RTCM, and any other message that may be reporting periodically 62 | myGNSS.disableOutput(); 63 | 64 | uint16_t supportedMessages[200] = {0}; 65 | int supportedCount = 0; 66 | 67 | int delayBetweenCommands = 10; 68 | char comPort[] = "COM3"; //Enable on a different port than the one we are using 69 | 70 | Serial.println("Scanning 1000 to 1300"); 71 | for (int x = 1000 ; x <= 1300 ; x++) 72 | { 73 | char myTest[100] = {0}; 74 | sprintf(myTest, "RTCM%04d", x); 75 | if(myGNSS.setNMEAPortMessage(myTest, comPort, 1) == true) 76 | supportedMessages[supportedCount++] = x; 77 | delay(delayBetweenCommands); 78 | } 79 | 80 | if(supportedCount > 0) 81 | { 82 | Serial.println(); 83 | Serial.print(supportedCount); 84 | Serial.print(" RTCM messages supported: "); 85 | for(int x = 0 ; x < supportedCount ; x++) 86 | { 87 | Serial.print(supportedMessages[x]); 88 | Serial.print(", "); 89 | } 90 | Serial.println(); 91 | } 92 | } 93 | 94 | void loop() 95 | { 96 | if(Serial.available()) ESP.restart(); 97 | } 98 | -------------------------------------------------------------------------------- /examples/Example17_Parser/Example17_Parser.ino: -------------------------------------------------------------------------------- 1 | /* 2 | This example configures the UM980 inside the Torch. 3 | 4 | NMEA, some RTCM, and BESTNAV (binary) are enabled and output at 1Hz. 5 | */ 6 | 7 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 8 | #include //http://librarymanager/All#SparkFun_Extensible_Message_Parser 9 | 10 | //---------------------------------------- 11 | // Constants 12 | //---------------------------------------- 13 | 14 | int pin_UART1_TX = 4; 15 | int pin_UART1_RX = 13; 16 | 17 | #define COMPILE_CAPTURE_RAW_DATA_STREAM 0 18 | #define COMPILE_PARSER_STATE_DISPLAY 0 19 | #define CAPTURE_BYTE_COUNT 8192 // 0 means infinite 20 | 21 | #define NMEA_PARSER_INDEX 0 22 | #define UNICORE_HASH_PARSER_INDEX 1 23 | #define RTCM_PARSER_INDEX 2 24 | #define UNICORE_BINARY_PARSER_INDEX 3 25 | 26 | // Build the table listing all of the parsers 27 | SEMP_PARSE_ROUTINE const parserTable[] = 28 | { 29 | sempNmeaPreamble, 30 | sempUnicoreHashPreamble, 31 | sempRtcmPreamble, 32 | sempUnicoreBinaryPreamble, 33 | }; 34 | const int parserCount = sizeof(parserTable) / sizeof(parserTable[0]); 35 | 36 | const char * const parserNames[] = 37 | { 38 | "NMEA Parser", 39 | "Unicore Hash (#) Parser", 40 | "RTCM Parser", 41 | "Unicore Binary Parser", 42 | }; 43 | const int parserNameCount = sizeof(parserNames) / sizeof(parserNames[0]); 44 | 45 | #define CONVERT_TO_C_BYTES_PER_LINE 8 // Must be a power of 2 46 | #define CONVERT_TO_C_BYTES_PER_MASK (CONVERT_TO_C_BYTES_PER_LINE - 1) 47 | 48 | //---------------------------------------- 49 | // Locals 50 | //---------------------------------------- 51 | 52 | UM980 myGNSS; 53 | 54 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 55 | 56 | unsigned long lastCheck = 0; 57 | 58 | uint32_t dataOffset; // Offset in rawDataStream array 59 | int32_t offset; // invalidCharacterBuffer offset 60 | uint8_t invalidCharacterBuffer[20000]; // Data that was not parsed 61 | SEMP_PARSE_STATE *parse; // State of the parsers 62 | 63 | //---------------------------------------- 64 | // UM980 Initialization Example 65 | //---------------------------------------- 66 | 67 | void setup() 68 | { 69 | Serial.begin(115200); 70 | delay(250); 71 | Serial.println(); 72 | Serial.println("SparkFun UM980 Example 17"); 73 | 74 | //We must start the serial port before using it in the library 75 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 76 | 77 | // Enable output from the Unicore GNSS library 78 | // myGNSS.enableDebugging(); // Print all debug to Serial 79 | 80 | // Enable various debug options in the Unicore GNSS library 81 | // myGNSS.enablePrintParserTransitions(); 82 | // myGNSS.enablePrintBadChecksums(); 83 | // myGNSS.enablePrintRxMessages(); 84 | // myGNSS.enableRxMessageDump(); 85 | 86 | // Initialize the Unicore GNSS 87 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 88 | { 89 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 90 | while (true); 91 | } 92 | Serial.println("UM980 detected!"); 93 | 94 | // Enable low level debugging for the parser in the Unicore GNSS library 95 | // myGNSS.enableParserDebug(); 96 | // myGNSS.enableParserErrors(); 97 | // myGNSS.printParserConfiguration(); 98 | 99 | // Clear saved configurations, satellite ephemerides, position information, and reset baud rate to 115200bps. 100 | resetUM980(); 101 | 102 | bool response = true; 103 | 104 | float outputRate = 1; //0.5 = 2 reports per second. 105 | 106 | char comPort[] = "COM3"; //UM980 UART3 is connected to ESP32 UART1 on the Torch 107 | 108 | //Enable some RTCM, NMEA, and a UM980 binary command called BESTNAVB 109 | response &= myGNSS.setRTCMPortMessage("RTCM1005", comPort, outputRate); 110 | response &= myGNSS.setRTCMPortMessage("RTCM1074", comPort, outputRate); 111 | 112 | response &= myGNSS.setNMEAPortMessage("GPGGA", comPort, outputRate); 113 | response &= myGNSS.setNMEAPortMessage("GPGSA", comPort, outputRate); 114 | response &= myGNSS.setNMEAPortMessage("GPGST", comPort, outputRate); 115 | response &= myGNSS.setNMEAPortMessage("GPGSV", comPort, outputRate); 116 | response &= myGNSS.setNMEAPortMessage("GPRMC", comPort, outputRate); 117 | response &= myGNSS.saveConfiguration(); //Save the current configuration into non-volatile memory (NVM) 118 | 119 | //If any one command fails, it will force response to false 120 | if (response == false) 121 | { 122 | Serial.println("UM980 failed to configure. Freezing..."); 123 | while (true); 124 | } 125 | 126 | // Display the firmware version 127 | Serial.printf("%s", myGNSS.getVersionFull()); 128 | 129 | //sendCommand() sends the string directly and checks for the OK response 130 | //Returns true if the OK was detected 131 | //"BESTNAVB 1" starts the output of BESTNAV binary message at 1Hz on the COM port 132 | //we are connected to (in this case COM3) 133 | if (myGNSS.sendCommand("BESTNAVB 1") == true) 134 | Serial.println("BESTNAVB enabled"); 135 | else 136 | Serial.println("BESTNAVB error"); 137 | 138 | // Initialize the parser 139 | parse = sempBeginParser(parserTable, parserCount, 140 | parserNames, parserNameCount, 141 | 0, 3000, processMessage, "Example 17 Parser"); 142 | if (!parse) 143 | reportFatalError("Failed to initialize the parser"); 144 | 145 | if (COMPILE_CAPTURE_RAW_DATA_STREAM) 146 | { 147 | // Disable parser output 148 | sempDisableErrorOutput(parse); 149 | Serial.println("const uint8_t rawDataStream[] ="); 150 | Serial.println("{"); 151 | } 152 | else 153 | // Enable debugging for the parser 154 | sempEnableDebugOutput(parse); 155 | 156 | Serial.println("Mixture of NMEA, RTCM, and UM980 binary now reporting. Have fun!"); 157 | Serial.println("----------------------------------------------------------------"); 158 | } 159 | 160 | void loop() 161 | { 162 | const char * startState; 163 | const char * endState; 164 | 165 | // Read the raw data one byte at a time and update the parser state 166 | // based on the incoming byte 167 | while (SerialGNSS.available()) 168 | { 169 | // Read the byte from the UM980 170 | uint8_t incoming = SerialGNSS.read(); 171 | 172 | // Remember this data byte 173 | // Ignore CR and LF following NMEA checksum 174 | if ((parse->type != 0) || ((incoming != '\r') && (incoming != '\n'))) 175 | invalidCharacterBuffer[offset++] = incoming; 176 | 177 | // Parse this byte 178 | startState = getParseStateName(parse); 179 | sempParseNextByte(parse, incoming); 180 | endState = getParseStateName(parse); 181 | 182 | // Build a rawDataStream array 183 | if (COMPILE_CAPTURE_RAW_DATA_STREAM) 184 | { 185 | if ((!CAPTURE_BYTE_COUNT) 186 | || (dataOffset < CAPTURE_BYTE_COUNT)) 187 | // Add the incoming data byte to the rawDataStream 188 | convertToC(incoming); 189 | else if (dataOffset == CAPTURE_BYTE_COUNT) 190 | { 191 | // Finish the rawDataStream array 192 | convertToCComment(true); 193 | dataOffset += 1; 194 | } 195 | } 196 | 197 | // Display the state changes 198 | else if (COMPILE_PARSER_STATE_DISPLAY) 199 | Serial.printf("Ex17: 0x%02x (%c), state: (%p) %s --> %s (%p)\r\n", 200 | incoming, 201 | ((incoming >= ' ') && (incoming < 0x7f)) ? incoming : '.', 202 | startState, startState, endState, endState); 203 | } 204 | } 205 | 206 | void resetUM980() 207 | { 208 | if (myGNSS.factoryReset() == true) 209 | Serial.println("UM980 now reset to factory defaults"); 210 | else 211 | Serial.println("Error resetting UM980 to factory defaults"); 212 | 213 | Serial.println("Waiting for UM980 to reboot"); 214 | 215 | while (1) 216 | { 217 | delay(1000); //Wait for device to reboot 218 | if (myGNSS.isConnected() == true) 219 | break; 220 | Serial.println("Device still rebooting"); 221 | } 222 | 223 | Serial.println("UM980 has completed reset"); 224 | } 225 | 226 | //---------------------------------------- 227 | // Support Routines 228 | //---------------------------------------- 229 | 230 | // Display the contents of a buffer 231 | void dumpBuffer(const uint8_t *buffer, uint16_t length) 232 | { 233 | int bytes; 234 | const uint8_t *end; 235 | int index; 236 | char line[128]; 237 | uint16_t offset; 238 | 239 | end = &buffer[length]; 240 | offset = 0; 241 | while (buffer < end) 242 | { 243 | // Determine the number of bytes to display on the line 244 | bytes = end - buffer; 245 | if (bytes > (16 - (offset & 0xf))) 246 | bytes = 16 - (offset & 0xf); 247 | 248 | // Display the offset 249 | sprintf(line, "0x%08lx: ", offset); 250 | 251 | // Skip leading bytes 252 | for (index = 0; index < (offset & 0xf); index++) 253 | sprintf(&line[strlen(line)], " "); 254 | 255 | // Display the data bytes 256 | for (index = 0; index < bytes; index++) 257 | sprintf(&line[strlen(line)], "%02x ", buffer[index]); 258 | 259 | // Separate the data bytes from the ASCII 260 | for (; index < (16 - (offset & 0xf)); index++) 261 | sprintf(&line[strlen(line)], " "); 262 | sprintf(&line[strlen(line)], " "); 263 | 264 | // Skip leading bytes 265 | for (index = 0; index < (offset & 0xf); index++) 266 | sprintf(&line[strlen(line)], " "); 267 | 268 | // Display the ASCII values 269 | for (index = 0; index < bytes; index++) 270 | sprintf(&line[strlen(line)], "%c", ((buffer[index] < ' ') || (buffer[index] >= 0x7f)) ? '.' : buffer[index]); 271 | Serial.println(line); 272 | 273 | // Set the next line of data 274 | buffer += bytes; 275 | offset += bytes; 276 | } 277 | } 278 | 279 | // Print the error message every 15 seconds 280 | void reportFatalError(const char *errorMsg) 281 | { 282 | while (1) 283 | { 284 | Serial.print("HALTED: "); 285 | Serial.print(errorMsg); 286 | Serial.println(); 287 | sleep(15); 288 | } 289 | } 290 | 291 | //---------------------------------------- 292 | // Parser Support Routines 293 | //---------------------------------------- 294 | 295 | // Call back from within parser, for end of message 296 | // Process a complete message incoming from parser 297 | void processMessage(SEMP_PARSE_STATE *parse, uint16_t type) 298 | { 299 | SEMP_SCRATCH_PAD *scratchPad = (SEMP_SCRATCH_PAD *)parse->scratchPad; 300 | char *typeName; 301 | 302 | // Dump the received messages 303 | if (!COMPILE_CAPTURE_RAW_DATA_STREAM) 304 | { 305 | // Display any invalid data 306 | if (offset > parse->length) 307 | { 308 | Serial.println(); 309 | Serial.println("Unknown data"); 310 | dumpBuffer(invalidCharacterBuffer, offset - parse->length); 311 | } 312 | offset = 0; 313 | 314 | // Display the raw message 315 | // The type value is the index into the raw data array 316 | Serial.println(); 317 | Serial.printf("Valid %s message: 0x%04x (%d) bytes\r\n", 318 | parserNames[type], parse->length, parse->length); 319 | dumpBuffer(parse->buffer, parse->length); 320 | } 321 | } 322 | 323 | // Translate the state value into an ASCII state name 324 | const char *getParseStateName(SEMP_PARSE_STATE *parse) 325 | { 326 | const char *name; 327 | 328 | do 329 | { 330 | name = sempNmeaGetStateName(parse); 331 | if (name) 332 | break; 333 | name = sempNmeaGetStateName(parse); 334 | if (name) 335 | break; 336 | name = sempRtcmGetStateName(parse); 337 | if (name) 338 | break; 339 | name = sempUnicoreBinaryGetStateName(parse); 340 | if (name) 341 | break; 342 | name = sempUnicoreHashGetStateName(parse); 343 | if (name) 344 | break; 345 | name = sempGetStateName(parse); 346 | } while (0); 347 | return name; 348 | } 349 | 350 | //---------------------------------------- 351 | // Raw Data Stream Support Routines 352 | //---------------------------------------- 353 | 354 | // Add a comment to the end of the line with the data offset 355 | void convertToCComment(bool lastLine) 356 | { 357 | static uint32_t lastOffset; 358 | 359 | uint32_t bytesOnLine; 360 | bool done; 361 | uint32_t nextOffset; 362 | uint32_t startOffset; 363 | 364 | // Determine the various offsets 365 | 366 | done = lastLine; 367 | nextOffset = dataOffset; 368 | bytesOnLine = nextOffset - lastOffset; 369 | lastOffset = dataOffset; 370 | if (bytesOnLine) 371 | done = false; 372 | if (!done) 373 | { 374 | // Determine the offset for the unfinished line 375 | startOffset = nextOffset - bytesOnLine; 376 | 377 | // Add spaces to align the comments 378 | while (bytesOnLine++ < CONVERT_TO_C_BYTES_PER_LINE) 379 | Serial.print(" "); 380 | 381 | // Add the end of line comment 382 | Serial.printf(" // 0x%08x (%d)\r\n", startOffset, startOffset); 383 | } 384 | 385 | // Finish the data structure 386 | if (lastLine) 387 | { 388 | Serial.print("}; "); 389 | 390 | // Add spaces to align the comments 391 | for (bytesOnLine = 0; bytesOnLine < CONVERT_TO_C_BYTES_PER_LINE; bytesOnLine++) 392 | Serial.print(" "); 393 | 394 | // Add the end of line comment 395 | Serial.printf(" // 0x%08x (%d)\r\n", nextOffset, nextOffset); 396 | } 397 | } 398 | 399 | // Add a data byte to the rawDataStream array, print the value in hex 400 | // Add an end of line comment with the offset from the beginning of the 401 | // buffer 402 | void convertToC(uint8_t data) 403 | { 404 | // Indent the line 405 | if (!(dataOffset & CONVERT_TO_C_BYTES_PER_MASK)) 406 | Serial.print(" "); 407 | 408 | // Add the data byte 409 | Serial.printf(" 0x%02x,", data); 410 | dataOffset += 1; 411 | 412 | // Add the comment at the end of the line 413 | if (!(dataOffset & CONVERT_TO_C_BYTES_PER_MASK)) 414 | convertToCComment(false); 415 | } 416 | -------------------------------------------------------------------------------- /examples/Example18_CheckConfig/Example18_CheckConfig.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Checking if a config item is set 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: May 9th, 2024 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to query a UM980 GNSS module to see if a particular string 9 | shows up in the response to CONFIG. 10 | 11 | Below is an example response from the CONFIG command: 12 | 13 | $command,config,response: OK*54 14 | $CONFIG,ANTENNA,CONFIG ANTENNA POWERON*7A 15 | $CONFIG,NMEAVERSION,CONFIG NMEAVERSION V410*47 16 | $CONFIG,RTK,CONFIG RTK TIMEOUT 120*6C 17 | $CONFIG,RTK,CONFIG RTK RELIABILITY 3 1*76 18 | $CONFIG,PPP,CONFIG PPP TIMEOUT 120*6C 19 | $CONFIG,DGPS,CONFIG DGPS TIMEOUT 300*6C 20 | $CONFIG,RTCMB1CB2A,CONFIG RTCMB1CB2A ENABLE*25 21 | $CONFIG,ANTENNADELTAHEN,CONFIG ANTENNADELTAHEN 0.0000 0.0000 0.0000*3A 22 | $CONFIG,PPS,CONFIG PPS ENABLE GPS POSITIVE 500000 1000 0 0*6E 23 | $CONFIG,SIGNALGROUP,CONFIG SIGNALGROUP 2*16 24 | $CONFIG,ANTIJAM,CONFIG ANTIJAM AUTO*2B 25 | $CONFIG,AGNSS,CONFIG AGNSS DISABLE*70 26 | $CONFIG,BASEOBSFILTER,CONFIG BASEOBSFILTER DISABLE*70 27 | $CONFIG,COM1,CONFIG COM1 115200*23 28 | $CONFIG,COM2,CONFIG COM2 115200*23 29 | $CONFIG,COM3,CONFIG COM3 115200*23 30 | 31 | These examples are targeted for an ESP32 platform but any platform that has multiple 32 | serial UARTs should be compatible. 33 | 34 | Feel like supporting open source hardware? 35 | Buy a board from SparkFun! 36 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 37 | 38 | Hardware Connections: 39 | Connect RX2 (green wire) of the UM980 to pin 4 on the ESP32 40 | Connect TX2 (orange wire) of the UM980 to pin 13 on the ESP32 41 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 42 | Note: Almost any ESP32 pins can be used for serial. 43 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 44 | 45 | */ 46 | 47 | int pin_UART1_TX = 4; 48 | int pin_UART1_RX = 13; 49 | 50 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 51 | 52 | UM980 myGNSS; 53 | 54 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 55 | 56 | unsigned long lastCheck = 0; 57 | 58 | void setup() 59 | { 60 | Serial.begin(115200); 61 | delay(250); 62 | Serial.println(); 63 | Serial.println("SparkFun UM980 Example 18"); 64 | 65 | //The CONFIG response can be ~500 bytes overrunning the ESP32 RX buffer of 256 bytes 66 | //Increase the size of the RX buffer 67 | SerialGNSS.setRxBufferSize(1024); 68 | 69 | //We must start the serial port before using it in the library 70 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 71 | 72 | //myGNSS.enableDebugging(); // Print all debug to Serial 73 | 74 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 75 | { 76 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 77 | while (true); 78 | } 79 | Serial.println("UM980 detected!"); 80 | 81 | //The library can't identify specific settings but we can see if a specific setting 82 | //is present in the response to the CONFIG command 83 | //See example response above 84 | 85 | if (myGNSS.isConfigurationPresent("COM3 115200") == true) 86 | Serial.println("COM3 set to 115200"); 87 | else 88 | Serial.println("COM3 NOT set to 115200"); 89 | 90 | if (myGNSS.isConfigurationPresent("CONFIG PPP ENABLE E6-HAS") == true) 91 | Serial.println("E6 Enabled"); 92 | else 93 | Serial.println("E6 not enabled"); 94 | 95 | if (myGNSS.isConfigurationPresent("CONFIG SIGNALGROUP 2") == true) 96 | Serial.println("Signal group 2 enabled"); 97 | else 98 | Serial.println("Signal group 2 not enabled"); 99 | } 100 | 101 | void loop() 102 | { 103 | 104 | } 105 | -------------------------------------------------------------------------------- /examples/Example19_Enable_Galileo_HAS/Example19_Enable_Galileo_HAS.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Enable Galileo High Accuracy Service: https://gssc.esa.int/navipedia/index.php/Galileo_High_Accuracy_Service_(HAS) 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: May 9th, 2024 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | The UM980 is capable of receiving the new Galileo E6 signal and obtain a sub 0.2m precision 9 | location fix. This example shows how to enable E6 and HAS 10 | 11 | These examples are targeted for an ESP32 platform but any platform that has multiple 12 | serial UARTs should be compatible. 13 | 14 | Feel like supporting open source hardware? 15 | Buy a board from SparkFun! 16 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 17 | 18 | Hardware Connections: 19 | Connect RX2 (green wire) of the UM980 to pin 4 on the ESP32 20 | Connect TX2 (orange wire) of the UM980 to pin 13 on the ESP32 21 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 22 | Note: Almost any ESP32 pins can be used for serial. 23 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 24 | 25 | */ 26 | 27 | int pin_UART1_TX = 4; 28 | int pin_UART1_RX = 13; 29 | 30 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 31 | 32 | UM980 myGNSS; 33 | 34 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 35 | 36 | unsigned long lastCheck = 0; 37 | 38 | unsigned long startTime = 0; 39 | unsigned long convergingStartTime = 0; 40 | unsigned long timeToConvergence = 0; 41 | 42 | void setup() 43 | { 44 | Serial.begin(115200); 45 | delay(250); 46 | Serial.println(); 47 | Serial.println("SparkFun UM980 Example 19"); 48 | 49 | //The CONFIG response can be ~500 bytes over runing the ESP32 RX buffer of 256 bytes 50 | //Increase the size of the RX buffer 51 | SerialGNSS.setRxBufferSize(1024); 52 | 53 | //We must start the serial port before using it in the library 54 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 55 | 56 | //myGNSS.enableDebugging(); // Print all debug to Serial 57 | 58 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 59 | { 60 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 61 | while (true); 62 | } 63 | Serial.println("UM980 detected!"); 64 | 65 | //E6 reception requires version 11833 or greater 66 | int um980Version = String(myGNSS.getVersion()).toInt(); //Convert the string response to a value 67 | 68 | Serial.print("UM980 firmware version: v"); 69 | Serial.println(um980Version); 70 | 71 | if (um980Version < 11833) 72 | { 73 | Serial.println("E6 requires 11833 or newer. Please update the firmware on your UM980. Freezing..."); 74 | while (true); 75 | } 76 | else 77 | Serial.println("Firmware is E6-PPP compatible."); 78 | 79 | if (myGNSS.isConfigurationPresent("CONFIG SIGNALGROUP 2") == false) 80 | { 81 | if (myGNSS.sendCommand("CONFIG SIGNALGROUP 2") == false) 82 | Serial.println("Signal group 2 command failed"); 83 | else 84 | { 85 | Serial.println("Enabling signal group 2 causes the UM980 to reboot. This can take a few seconds."); 86 | 87 | while (1) 88 | { 89 | delay(1000); //Wait for device to reboot 90 | if (myGNSS.isConnected() == true) break; 91 | else Serial.println("Device still rebooting"); 92 | } 93 | 94 | Serial.println("UM980 has completed reboot."); 95 | } 96 | } 97 | else 98 | Serial.println("Signal group 2 already enabled"); 99 | 100 | if (myGNSS.isConfigurationPresent("CONFIG PPP ENABLE E6-HAS") == false) 101 | { 102 | if (myGNSS.sendCommand("CONFIG PPP ENABLE E6-HAS") == true) 103 | Serial.println("E6 service enabled"); 104 | else 105 | Serial.println("E6 config error"); 106 | } 107 | else 108 | Serial.println("E6 service already enabled"); 109 | 110 | startTime = millis(); 111 | 112 | Serial.println("E6 PPP should now be enabled. Sit back and watch the LLA deviations decrease below 0.2m!"); 113 | Serial.println("r) Reset ESP"); 114 | Serial.println("R) Factory reset UM980"); 115 | } 116 | 117 | void loop() 118 | { 119 | if (Serial.available()) 120 | { 121 | byte incoming = Serial.read(); 122 | if (incoming == 'r') 123 | ESP.restart(); 124 | else if (incoming == 'R') 125 | um980Reset(); 126 | } 127 | 128 | 129 | myGNSS.update(); //Regularly call to parse any new data 130 | 131 | if (millis() - lastCheck > 1000) 132 | { 133 | lastCheck = millis(); 134 | printUpdate(); 135 | } 136 | } 137 | 138 | void printUpdate() 139 | { 140 | Serial.print("Lat/Long/Alt: "); 141 | Serial.print(myGNSS.getLatitude(), 11); //Accurate 11 decimal places 142 | Serial.print("/"); 143 | Serial.print(myGNSS.getLongitude(), 11); 144 | Serial.print("/"); 145 | Serial.print(myGNSS.getAltitude(), 4); //Accurate to 4 decimal places 146 | Serial.println(); 147 | 148 | Serial.print("Deviation of Lat/Long/Alt (m): "); 149 | Serial.print(myGNSS.getLatitudeDeviation(), 4); 150 | Serial.print("/"); 151 | Serial.print(myGNSS.getLongitudeDeviation(), 4); 152 | Serial.print("/"); 153 | Serial.println(myGNSS.getAltitudeDeviation(), 4); 154 | 155 | Serial.print("Satellites in view: "); 156 | Serial.print(myGNSS.getSIV()); 157 | Serial.println(); 158 | 159 | int positionType = myGNSS.getPositionType(); 160 | Serial.print("Position Type: "); 161 | Serial.print(positionType); 162 | Serial.print(" - "); 163 | if (positionType == 0) Serial.print("No solution"); 164 | else if (positionType == 8) Serial.print("Velocity computed using instantaneous Doppler"); 165 | else if (positionType == 16) Serial.print("Single point positioning"); 166 | else if (positionType == 17) Serial.print("Pseudorange differential solution"); 167 | else if (positionType == 18) Serial.print("SBAS positioning"); 168 | else if (positionType == 32) Serial.print("L1 float solution"); 169 | else if (positionType == 33) Serial.print("Ionosphere-free float solution"); 170 | else if (positionType == 34) Serial.print("Narrow-lane float solution"); 171 | else if (positionType == 48) Serial.print("L1 fixed solution"); 172 | else if (positionType == 49) Serial.print("Wide-lane fixed solution"); 173 | else if (positionType == 50) Serial.print("Narrow-lane fixed solution"); 174 | else if (positionType == 68) 175 | { 176 | Serial.print("PPP solution converging"); 177 | 178 | if (convergingStartTime == 0) convergingStartTime = millis(); 179 | if (convergingStartTime > 0) 180 | { 181 | Serial.print(" - Seconds in converging phase: "); 182 | Serial.print((millis() - (convergingStartTime - startTime)) / 1000.0, 0); 183 | Serial.print("s"); 184 | } 185 | } 186 | else if (positionType == 69) 187 | { 188 | Serial.print("Precise Point Positioning"); 189 | 190 | if (timeToConvergence == 0) timeToConvergence = millis(); 191 | if (timeToConvergence > 0) 192 | { 193 | Serial.print(" - Seconds in converging phase: "); 194 | Serial.print((millis() - (convergingStartTime - startTime)) / 1000.0, 0); 195 | Serial.print("s"); 196 | 197 | Serial.print(" - Total time to convergence: "); 198 | Serial.print((timeToConvergence - startTime) / 1000.0, 0); 199 | Serial.print("s"); 200 | } 201 | } 202 | else Serial.print("Unknown"); 203 | Serial.println(); 204 | 205 | Serial.println(); 206 | } 207 | 208 | void um980Reset() 209 | { 210 | while (Serial.available()) Serial.read(); //Clear RX buffer 211 | Serial.println("Press any key to factory reset the UM980"); 212 | while (Serial.available() == 0) delay(1); //Wait for user to press a button 213 | 214 | // Clear saved configurations, satellite ephemerides, position information, and reset baud rate to 115200bps. 215 | if (myGNSS.factoryReset() == true) 216 | Serial.println("UM980 now reset to factory defaults"); 217 | else 218 | Serial.println("Error resetting UM980 to factory defaults"); 219 | 220 | Serial.println("Waiting for UM980 to reboot"); 221 | 222 | while (1) 223 | { 224 | delay(1000); //Wait for device to reboot 225 | if (myGNSS.isConnected() == true) break; 226 | else Serial.println("Device still rebooting"); 227 | } 228 | 229 | Serial.println("UM980 has completed reset"); 230 | } -------------------------------------------------------------------------------- /examples/Example1_PositionVelocityTime/Example1_PositionVelocityTime.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Reading Position and Time via Serial 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to query a UM980 GNSS module for its position and time data. 9 | These examples are targeted for an ESP32 platform but any platform that has multiple 10 | serial UARTs should be compatible. 11 | 12 | Note: Lat/Lon are doubles and the UM980 outputs 11 digits after the decimal. 13 | 14 | Feel like supporting open source hardware? 15 | Buy a board from SparkFun! 16 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 17 | 18 | Hardware Connections: 19 | Connect RX2 (green wire) of the UM980 to pin 4 on the ESP32 20 | Connect TX2 (orange wire) of the UM980 to pin 13 on the ESP32 21 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 22 | Note: Almost any ESP32 pins can be used for serial. 23 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 24 | 25 | */ 26 | 27 | int pin_UART1_TX = 4; 28 | int pin_UART1_RX = 13; 29 | 30 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 31 | 32 | UM980 myGNSS; 33 | 34 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 35 | 36 | unsigned long lastCheck = 0; 37 | 38 | void setup() 39 | { 40 | Serial.begin(115200); 41 | delay(250); 42 | Serial.println(); 43 | Serial.println("SparkFun UM980 Example 1"); 44 | 45 | //We must start the serial port before using it in the library 46 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 47 | 48 | //myGNSS.enableDebugging(); // Print all debug to Serial 49 | 50 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 51 | { 52 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 53 | while (true); 54 | } 55 | Serial.println("UM980 detected!"); 56 | } 57 | 58 | void loop() 59 | { 60 | myGNSS.update(); //Regularly call to parse any new data 61 | 62 | if (millis() - lastCheck > 1000) 63 | { 64 | lastCheck = millis(); 65 | 66 | //The get methods are updated whenever new data is parsed with the update() call. 67 | //By default, this data is updated once per second. 68 | 69 | Serial.print("Lat/Long/Alt: "); 70 | Serial.print(myGNSS.getLatitude(), 11); //Accurate 11 decimal places 71 | Serial.print("/"); 72 | Serial.print(myGNSS.getLongitude(), 11); 73 | Serial.print("/"); 74 | Serial.print(myGNSS.getAltitude(), 4); //Accurate to 4 decimal places 75 | Serial.println(); 76 | 77 | Serial.print("Horizontal Speed: "); 78 | Serial.print(myGNSS.getHorizontalSpeed()); 79 | Serial.print("m/s Vertical Speed: "); 80 | Serial.print(myGNSS.getVerticalSpeed()); 81 | Serial.print("m/s Direction from North: "); 82 | Serial.print(myGNSS.getTrackGround()); 83 | Serial.print("(degrees)"); 84 | Serial.println(); 85 | 86 | Serial.print("Date (yyyy/mm/dd): "); 87 | Serial.print(myGNSS.getYear()); 88 | Serial.print("/"); 89 | if (myGNSS.getMonth() < 10) 90 | Serial.print("0"); 91 | Serial.print(myGNSS.getMonth()); 92 | Serial.print("/"); 93 | if (myGNSS.getDay() < 10) 94 | Serial.print("0"); 95 | Serial.print(myGNSS.getDay()); 96 | 97 | Serial.print(" Time (hh:mm:dd): "); 98 | if (myGNSS.getHour() < 10) 99 | Serial.print("0"); 100 | Serial.print(myGNSS.getHour()); 101 | Serial.print(":"); 102 | if (myGNSS.getMinute() < 10) 103 | Serial.print("0"); 104 | Serial.print(myGNSS.getMinute()); 105 | Serial.print(":"); 106 | if (myGNSS.getSecond() < 10) 107 | Serial.print("0"); 108 | Serial.print(myGNSS.getSecond()); 109 | Serial.print("."); 110 | if (myGNSS.getMillisecond() < 100) 111 | Serial.print("0"); 112 | if (myGNSS.getMillisecond() < 10) 113 | Serial.print("0"); 114 | Serial.print(myGNSS.getMillisecond()); 115 | Serial.println(); 116 | 117 | Serial.print("Satellites in view: "); 118 | Serial.print(myGNSS.getSIV()); 119 | Serial.println(); 120 | 121 | Serial.println(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /examples/Example20_NTRIP_Client/Example20_NTRIP_Client.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Connect to NTRIP Caster to obtain corrections 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: January 31, 2025 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to connect to an NTRIP Caster and push RTCM to the UM980 to 9 | obtain an RTK Fix. 10 | 11 | Feel like supporting open source hardware? 12 | Buy a board from SparkFun! 13 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 14 | 15 | Hardware Connections: 16 | Connect RX2 of the UM980 to pin 4 on the ESP32 17 | Connect TX2 of the UM980 to pin 13 on the ESP32 18 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 19 | Note: Almost any ESP32 pins can be used for serial. 20 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 21 | */ 22 | 23 | #include 24 | #include "secrets.h" 25 | 26 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 27 | UM980 myGNSS; 28 | 29 | #define pin_UART_TX 4 30 | #define pin_UART_RX 13 31 | 32 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 33 | 34 | //The ESP32 core has a built in base64 library but not every platform does 35 | //We'll use an external lib if necessary. 36 | #if defined(ARDUINO_ARCH_ESP32) 37 | #include "base64.h" //Built-in ESP32 library 38 | #else 39 | #include //nfriendly library from https://github.com/adamvr/arduino-base64, will work with any platform 40 | #endif 41 | 42 | //Global variables 43 | //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 44 | long lastReceivedRTCM_ms = 0; //5 RTCM messages take approximately ~300ms to arrive at 115200bps 45 | int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster 46 | 47 | bool transmitLocation = true; //By default we will transmit the units location via GGA sentence. 48 | int timeBetweenGGAUpdate_ms = 10000; //GGA is required for Rev2 NTRIP casters. Don't transmit but once every 10 seconds 49 | long lastTransmittedGGA_ms = 0; 50 | 51 | //Used for GGA sentence parsing from incoming NMEA 52 | bool ggaSentenceStarted = false; 53 | bool ggaSentenceComplete = false; 54 | bool ggaTransmitComplete = false; //Goes true once we transmit GGA to the caster 55 | 56 | char ggaSentence[128] = {0}; 57 | byte ggaSentenceSpot = 0; 58 | int ggaSentenceEndSpot = 0; 59 | //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 60 | 61 | void setup() 62 | { 63 | Serial.begin(115200); 64 | delay(250); 65 | Serial.println(); 66 | Serial.println("SparkFun UM980 Example"); 67 | 68 | //We must start the serial port before using it in the library 69 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART_RX, pin_UART_TX); 70 | 71 | //myGNSS.enableDebugging(); // Print all debug to Serial 72 | 73 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 74 | { 75 | Serial.println("UM980 failed to respond. Check ports and baud rates."); 76 | while (1); 77 | } 78 | Serial.println("UM980 detected!"); 79 | 80 | myGNSS.disableOutput(); // Disables all messages on this port 81 | 82 | myGNSS.setModeRoverSurvey(); 83 | 84 | //Enable the basic 5 NMEA sentences including GGA for the NTRIP Caster at 1Hz 85 | myGNSS.setNMEAPortMessage("GPGGA", 1); 86 | myGNSS.setNMEAPortMessage("GPGSA", 1); 87 | myGNSS.setNMEAPortMessage("GPGST", 1); 88 | myGNSS.setNMEAPortMessage("GPGSV", 1); 89 | myGNSS.setNMEAPortMessage("GPRMC", 1); 90 | 91 | myGNSS.saveConfiguration(); //Save the current configuration into non-volatile memory (NVM) 92 | 93 | Serial.println("GNSS Configuration complete"); 94 | 95 | Serial.print("Connecting to local WiFi"); 96 | WiFi.begin(ssid, password); 97 | while (WiFi.status() != WL_CONNECTED) 98 | { 99 | delay(500); 100 | Serial.print("."); 101 | } 102 | Serial.println(); 103 | 104 | Serial.print("WiFi connected with IP: "); 105 | Serial.println(WiFi.localIP()); 106 | 107 | //Clear any serial characters from the buffer 108 | while (Serial.available()) 109 | Serial.read(); 110 | } 111 | 112 | void loop() 113 | { 114 | if (Serial.available()) 115 | { 116 | beginClient(); 117 | while (Serial.available()) 118 | Serial.read(); //Empty buffer of any newline chars 119 | } 120 | 121 | Serial.println("Press any key to start NTRIP Client."); 122 | 123 | delay(1000); 124 | } 125 | 126 | //Connect to the NTRIP Caster, receive RTCM, and push it to the GNSS module 127 | void beginClient() 128 | { 129 | WiFiClient ntripClient; 130 | long rtcmCount = 0; 131 | 132 | Serial.println("Subscribing to Caster. Press key to stop"); 133 | delay(10); //Wait for any serial to arrive 134 | while (Serial.available()) 135 | Serial.read(); //Flush 136 | 137 | // Break if we receive a character from the user 138 | while (Serial.available() == 0) 139 | { 140 | //Connect if we are not already. Limit to 5s between attempts. 141 | if (ntripClient.connected() == false) 142 | { 143 | Serial.print("Opening socket to "); 144 | Serial.println(casterHost); 145 | 146 | if (ntripClient.connect(casterHost, casterPort) == false) //Attempt connection 147 | { 148 | Serial.println("Connection to caster failed"); 149 | return; 150 | } 151 | else 152 | { 153 | Serial.print("Connected to "); 154 | Serial.print(casterHost); 155 | Serial.print(": "); 156 | Serial.println(casterPort); 157 | 158 | Serial.print("Requesting NTRIP Data from mount point "); 159 | Serial.println(mountPoint); 160 | 161 | const int SERVER_BUFFER_SIZE = 512; 162 | char serverRequest[SERVER_BUFFER_SIZE + 1]; 163 | 164 | snprintf(serverRequest, 165 | SERVER_BUFFER_SIZE, 166 | "GET /%s HTTP/1.0\r\nUser-Agent: NTRIP SparkFun UM980 Client v1.0\r\n", 167 | mountPoint); 168 | 169 | char credentials[512]; 170 | if (strlen(casterUser) == 0) 171 | { 172 | strncpy(credentials, "Accept: */*\r\nConnection: close\r\n", sizeof(credentials)); 173 | } 174 | else 175 | { 176 | //Pass base64 encoded user:pw 177 | char userCredentials[sizeof(casterUser) + sizeof(casterUserPW) + 1]; //The ':' takes up a spot 178 | snprintf(userCredentials, sizeof(userCredentials), "%s:%s", casterUser, casterUserPW); 179 | 180 | Serial.print("Sending credentials: "); 181 | Serial.println(userCredentials); 182 | 183 | #if defined(ARDUINO_ARCH_ESP32) 184 | //Encode with ESP32 built-in library 185 | base64 b; 186 | String strEncodedCredentials = b.encode(userCredentials); 187 | char encodedCredentials[strEncodedCredentials.length() + 1]; 188 | strEncodedCredentials.toCharArray(encodedCredentials, sizeof(encodedCredentials)); //Convert String to char array 189 | #else 190 | //Encode with nfriendly library 191 | int encodedLen = base64_enc_len(strlen(userCredentials)); 192 | char encodedCredentials[encodedLen]; //Create array large enough to house encoded data 193 | base64_encode(encodedCredentials, userCredentials, strlen(userCredentials)); //Note: Input array is consumed 194 | #endif 195 | 196 | snprintf(credentials, sizeof(credentials), "Authorization: Basic %s\r\n", encodedCredentials); 197 | } 198 | strncat(serverRequest, credentials, SERVER_BUFFER_SIZE); 199 | strncat(serverRequest, "\r\n", SERVER_BUFFER_SIZE); 200 | 201 | Serial.print("serverRequest size: "); 202 | Serial.print(strlen(serverRequest)); 203 | Serial.print(" of "); 204 | Serial.print(sizeof(serverRequest)); 205 | Serial.println(" bytes available"); 206 | 207 | Serial.println("Sending server request:"); 208 | Serial.println(serverRequest); 209 | ntripClient.write(serverRequest, strlen(serverRequest)); 210 | 211 | //Wait for response 212 | unsigned long timeout = millis(); 213 | while (ntripClient.available() == 0) 214 | { 215 | if (millis() - timeout > 5000) 216 | { 217 | Serial.println("Caster timed out!"); 218 | ntripClient.stop(); 219 | return; 220 | } 221 | delay(10); 222 | } 223 | 224 | //Check reply 225 | bool connectionSuccess = false; 226 | char response[512]; 227 | int responseSpot = 0; 228 | while (ntripClient.available()) 229 | { 230 | if (responseSpot == sizeof(response) - 1) 231 | break; 232 | 233 | response[responseSpot++] = ntripClient.read(); 234 | if (strstr(response, "200") != nullptr) //Look for '200 OK' 235 | connectionSuccess = true; 236 | if (strstr(response, "401") != nullptr) //Look for '401 Unauthorized' 237 | { 238 | Serial.println("Hey - your credentials look bad! Check you caster username and password."); 239 | connectionSuccess = false; 240 | } 241 | } 242 | response[responseSpot] = '\0'; 243 | 244 | Serial.print("Caster responded with: "); 245 | Serial.println(response); 246 | 247 | if (connectionSuccess == false) 248 | { 249 | Serial.print("Failed to connect to "); 250 | Serial.println(casterHost); 251 | return; 252 | } 253 | else 254 | { 255 | Serial.print("Connected to "); 256 | Serial.println(casterHost); 257 | lastReceivedRTCM_ms = millis(); //Reset timeout 258 | ggaTransmitComplete = true; //Reset to start polling for new GGA data 259 | } 260 | } //End attempt to connect 261 | } //End connected == false 262 | 263 | if (ntripClient.connected() == true) 264 | { 265 | uint8_t rtcmData[512 * 4]; //Most incoming data is around 500 bytes but may be larger 266 | rtcmCount = 0; 267 | 268 | //Print any available RTCM data 269 | while (ntripClient.available()) 270 | { 271 | //Serial.write(ntripClient.read()); //Pipe to serial port is fine but beware, it's a lot of binary data 272 | rtcmData[rtcmCount++] = ntripClient.read(); 273 | if (rtcmCount == sizeof(rtcmData)) 274 | break; 275 | } 276 | 277 | if (rtcmCount > 0) 278 | { 279 | lastReceivedRTCM_ms = millis(); 280 | 281 | //Write incoming RTCM directly to UM980 282 | SerialGNSS.write(rtcmData, rtcmCount); 283 | Serial.print("RTCM pushed to GNSS: "); 284 | Serial.println(rtcmCount); 285 | } 286 | } 287 | 288 | //Write incoming NMEA back out to serial port and check for incoming GGA sentence 289 | while (SerialGNSS.available()) 290 | { 291 | byte incoming = SerialGNSS.read(); 292 | processNMEA(incoming); 293 | Serial.write(incoming); 294 | } 295 | 296 | //Provide the caster with our current position as needed 297 | if (ntripClient.connected() == true 298 | && transmitLocation == true 299 | && (millis() - lastTransmittedGGA_ms) > timeBetweenGGAUpdate_ms 300 | && ggaSentenceComplete == true 301 | && ggaTransmitComplete == false) 302 | { 303 | Serial.print("Pushing GGA to server: "); 304 | Serial.println(ggaSentence); 305 | 306 | lastTransmittedGGA_ms = millis(); 307 | 308 | //Push our current GGA sentence to caster 309 | ntripClient.print(ggaSentence); 310 | ntripClient.print("\r\n"); 311 | 312 | ggaTransmitComplete = true; 313 | } 314 | 315 | //Close socket if we don't have new data for 10s 316 | if (millis() - lastReceivedRTCM_ms > maxTimeBeforeHangup_ms) 317 | { 318 | Serial.println("RTCM timeout. Disconnecting..."); 319 | if (ntripClient.connected() == true) 320 | ntripClient.stop(); 321 | return; 322 | } 323 | 324 | delay(10); 325 | } 326 | 327 | Serial.println("User pressed a key"); 328 | Serial.println("Disconnecting..."); 329 | ntripClient.stop(); 330 | } 331 | 332 | //As each NMEA character comes in you can specify what to do with it 333 | //We will look for and copy the GGA sentence 334 | void processNMEA(char incoming) 335 | { 336 | //Take the incoming char from the GNSS and check to see if we should record it or not 337 | if (incoming == '$' && ggaTransmitComplete == true) 338 | { 339 | ggaSentenceStarted = true; 340 | ggaSentenceSpot = 0; 341 | ggaSentenceEndSpot = sizeof(ggaSentence); 342 | ggaSentenceComplete = false; 343 | } 344 | 345 | if (ggaSentenceStarted == true) 346 | { 347 | ggaSentence[ggaSentenceSpot++] = incoming; 348 | 349 | //Make sure we don't go out of bounds 350 | if (ggaSentenceSpot == sizeof(ggaSentence)) 351 | { 352 | //Start over 353 | ggaSentenceStarted = false; 354 | } 355 | //Verify this is the GGA setence 356 | else if (ggaSentenceSpot == 5 && incoming != 'G') 357 | { 358 | //Ignore this sentence, start over 359 | ggaSentenceStarted = false; 360 | } 361 | else if (incoming == '*') 362 | { 363 | //We're near the end. Keep listening for two more bytes to complete the CRC 364 | ggaSentenceEndSpot = ggaSentenceSpot + 2; 365 | } 366 | else if (ggaSentenceSpot == ggaSentenceEndSpot) 367 | { 368 | ggaSentence[ggaSentenceSpot] = '\0'; //Terminate this string 369 | ggaSentenceComplete = true; 370 | ggaTransmitComplete = false; //We are ready for transmission 371 | 372 | //Serial.print("GGA Parsed - "); 373 | //Serial.println(ggaSentence); 374 | 375 | //Start over 376 | ggaSentenceStarted = false; 377 | } 378 | } 379 | } -------------------------------------------------------------------------------- /examples/Example20_NTRIP_Client/secrets.h: -------------------------------------------------------------------------------- 1 | //Your WiFi credentials 2 | const char ssid[] = "Roving"; 3 | const char password[] = "sparkfun"; 4 | 5 | //RTK2Go works well and is free 6 | const char casterHost[] = "rtk2go.com"; 7 | const uint16_t casterPort = 2101; 8 | const char casterUser[] = "yes you have to fill this in@somewhere.com"; //User must provide their own email address to use RTK2Go 9 | const char casterUserPW[] = ""; 10 | const char mountPoint[] = "bldr_SparkFun1"; //The mount point you want to get data from 11 | 12 | //Emlid Caster also works well and is free 13 | //const char casterHost[] = "caster.emlid.com"; 14 | //const uint16_t casterPort = 2101; 15 | //const char casterUser[] = "u99696"; //User name and pw must be obtained through their web portal 16 | //const char casterUserPW[] = "466zez"; 17 | //const char mountPoint[] = "MP1979"; //The mount point you want to get data from 18 | -------------------------------------------------------------------------------- /examples/Example21_NTRIP_Server/Example21_NTRIP_Server.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Connect to NTRIP Caster and send RTCM as an NTRIP Server 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: January 31, 2025 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to gather RTCM data from the GNSS receiver and push it to a casting service over WiFi. 9 | It's confusing, but the Arduino is acting as a 'server' to a 'caster'. In this case we will 10 | use RTK2Go.com as our caster because it is free. A rover (car, surveyor stick, etc) can 11 | then connect to RTK2Go as a 'client' and get the RTCM data it needs to achieve an RTK Fix. 12 | 13 | You will need to register your mountpoint here: http://www.rtk2go.com/new-reservation/ 14 | (They'll probably block the credentials we include in this example) 15 | 16 | To see if your mountpoint is active go here: http://rtk2go.com:2101/ 17 | 18 | This is a proof of concept. Serving RTCM to a caster over WiFi is useful when you need to 19 | set up a high-precision base station. 20 | 21 | Feel like supporting open source hardware? 22 | Buy a board from SparkFun! 23 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 24 | 25 | Hardware Connections: 26 | Connect RX2 of the UM980 to pin 4 on the ESP32 27 | Connect TX2 of the UM980 to pin 13 on the ESP32 28 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 29 | Note: Almost any ESP32 pins can be used for serial. 30 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 31 | */ 32 | 33 | #include 34 | #include "secrets.h" 35 | WiFiClient ntripCaster; 36 | 37 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 38 | UM980 myGNSS; 39 | 40 | #define pin_UART_TX 4 41 | #define pin_UART_RX 13 42 | 43 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 44 | 45 | //Global Variables 46 | //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 47 | long lastSentRTCM_ms = 0; //Time of last data pushed to socket 48 | int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster 49 | 50 | uint32_t serverBytesSent = 0; //Just a running total 51 | long lastReport_ms = 0; //Time of last report of bytes sent 52 | //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 53 | 54 | void setup() 55 | { 56 | Serial.begin(115200); 57 | delay(250); 58 | Serial.println(); 59 | Serial.println("SparkFun UM980 Example"); 60 | 61 | //We must start the serial port before using it in the library 62 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART_RX, pin_UART_TX); 63 | 64 | //myGNSS.enableDebugging(); // Print all debug to Serial 65 | 66 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 67 | { 68 | Serial.println("UM980 failed to respond. Check ports and baud rates."); 69 | while (1); 70 | } 71 | Serial.println("UM980 detected!"); 72 | 73 | myGNSS.disableOutput(); // Disables all messages on this port 74 | 75 | //We are not going to enable any NMEA so that the only thing pushed to the caster is RTCM 76 | 77 | //When the coordinates of the base station are known, users can set the position 78 | //using Geodetic or ECEF coordinates. 79 | //myGNSS.setModeBaseGeodetic(40.09029479, -105.18505761, 1560.089); //SparkFun HQ 80 | myGNSS.setModeBaseECEF(-1280206.568, -4716804.403, 4086665.484); //SparkFun HQ 81 | 82 | //Set the output rate of RTCM correction messages. 83 | //Unicore supports a *large* number of RTCM messages. We recommend the following for most applications: 84 | //RTCM1006, 1074, 1084, 1094, 1124, 1033 85 | myGNSS.setRTCMMessage("RTCM1006", 10); 86 | myGNSS.setRTCMMessage("RTCM1074", 1); 87 | myGNSS.setRTCMMessage("RTCM1084", 1); 88 | myGNSS.setRTCMMessage("RTCM1094", 1); 89 | myGNSS.setRTCMMessage("RTCM1124", 1); 90 | myGNSS.setRTCMMessage("RTCM1033", 10); 91 | 92 | myGNSS.saveConfiguration(); //Save the current configuration into non-volatile memory (NVM) 93 | 94 | Serial.println("GNSS Configuration complete"); 95 | 96 | Serial.print("Connecting to local WiFi"); 97 | WiFi.begin(ssid, password); 98 | while (WiFi.status() != WL_CONNECTED) 99 | { 100 | delay(500); 101 | Serial.print("."); 102 | } 103 | Serial.println(); 104 | 105 | Serial.print("WiFi connected with IP: "); 106 | Serial.println(WiFi.localIP()); 107 | 108 | //Clear any serial characters from the buffer 109 | while (Serial.available()) 110 | Serial.read(); 111 | } 112 | 113 | void loop() 114 | { 115 | if (Serial.available()) 116 | { 117 | beginServer(); 118 | while (Serial.available()) 119 | Serial.read(); //Empty buffer of any newline chars 120 | } 121 | 122 | Serial.println("Press any key to start NTRIP Server."); 123 | 124 | delay(1000); 125 | } 126 | 127 | //Connect to the NTRIP Caster and push RTCM to it 128 | void beginServer() 129 | { 130 | Serial.println("Begin transmitting to caster. Press any key to stop"); 131 | delay(10); //Wait for any serial to arrive 132 | while (Serial.available()) 133 | Serial.read(); //Flush 134 | 135 | while (Serial.available() == 0) 136 | { 137 | //Connect if we are not already 138 | if (ntripCaster.connected() == false) 139 | { 140 | Serial.printf("Opening socket to %s\n", casterHost); 141 | 142 | if (ntripCaster.connect(casterHost, casterPort) == true) //Attempt connection 143 | { 144 | Serial.printf("Connected to %s:%d\n", casterHost, casterPort); 145 | 146 | const int SERVER_BUFFER_SIZE = 512; 147 | char serverRequest[SERVER_BUFFER_SIZE]; 148 | 149 | snprintf(serverRequest, 150 | SERVER_BUFFER_SIZE, 151 | "SOURCE %s /%s\r\nSource-Agent: NTRIP SparkFun UM980 Server v1.0\r\n\r\n", 152 | mountPointPW, mountPoint); 153 | 154 | Serial.println(F("Sending server request:")); 155 | Serial.println(serverRequest); 156 | ntripCaster.write(serverRequest, strlen(serverRequest)); 157 | 158 | //Wait for response 159 | unsigned long timeout = millis(); 160 | while (ntripCaster.available() == 0) 161 | { 162 | if (millis() - timeout > 5000) 163 | { 164 | Serial.println("Caster timed out!"); 165 | ntripCaster.stop(); 166 | return; 167 | } 168 | delay(10); 169 | } 170 | 171 | //Check reply 172 | bool connectionSuccess = false; 173 | char response[512]; 174 | int responseSpot = 0; 175 | while (ntripCaster.available()) 176 | { 177 | response[responseSpot++] = ntripCaster.read(); 178 | if (strstr(response, "200") != nullptr) //Look for 'ICY 200 OK' 179 | connectionSuccess = true; 180 | if (responseSpot == 512 - 1) 181 | break; 182 | } 183 | response[responseSpot] = '\0'; 184 | 185 | if (connectionSuccess == false) 186 | { 187 | Serial.printf("Failed to connect to Caster: %s", response); 188 | return; 189 | } 190 | } //End attempt to connect 191 | else 192 | { 193 | Serial.println("Connection to host failed"); 194 | return; 195 | } 196 | } //End connected == false 197 | 198 | if (ntripCaster.connected() == true) 199 | { 200 | delay(10); 201 | while (Serial.available()) 202 | Serial.read(); //Flush any endlines or carriage returns 203 | 204 | lastReport_ms = millis(); 205 | lastSentRTCM_ms = millis(); 206 | 207 | //This is the main sending loop. We scan for new data but processRTCM() is where the data actually gets sent out. 208 | while (1) 209 | { 210 | if (Serial.available()) 211 | break; 212 | 213 | //Write incoming RTCM to the NTRIP Caster 214 | while (SerialGNSS.available()) 215 | { 216 | ntripCaster.write(SerialGNSS.read()); //Send this byte to socket 217 | serverBytesSent++; 218 | lastSentRTCM_ms = millis(); 219 | } 220 | 221 | //Close socket if we don't have new data for 10s 222 | //RTK2Go will ban your IP address if you abuse it. See http://www.rtk2go.com/how-to-get-your-ip-banned/ 223 | //So let's not leave the socket open/hanging without data 224 | if (millis() - lastSentRTCM_ms > maxTimeBeforeHangup_ms) 225 | { 226 | Serial.println("RTCM timeout. Disconnecting..."); 227 | ntripCaster.stop(); 228 | return; 229 | } 230 | 231 | delay(10); 232 | 233 | //Report some statistics every 250 234 | if (millis() - lastReport_ms > 250) 235 | { 236 | lastReport_ms += 250; 237 | Serial.printf("Total sent: %d\n", serverBytesSent); 238 | } 239 | } 240 | } 241 | 242 | delay(10); 243 | } 244 | 245 | Serial.println("User pressed a key"); 246 | Serial.println("Disconnecting..."); 247 | ntripCaster.stop(); 248 | 249 | delay(10); 250 | while (Serial.available()) 251 | Serial.read(); //Flush any endlines or carriage returns 252 | } 253 | -------------------------------------------------------------------------------- /examples/Example21_NTRIP_Server/secrets.h: -------------------------------------------------------------------------------- 1 | //Your WiFi credentials 2 | const char ssid[] = "Roving"; 3 | const char password[] = "sparkfun"; 4 | 5 | //RTK2Go works well and is free 6 | const char casterHost[] = "rtk2go.com"; 7 | const uint16_t casterPort = 2101; 8 | const char mountPoint[] = "bldr_dwntwn2"; //The mount point you want to push data to 9 | const char mountPointPW[] = "WR5wRo4H"; 10 | 11 | //Emlid Caster also works well and is free 12 | //const char casterHost[] = "caster.emlid.com"; 13 | //const uint16_t casterPort = 2101; 14 | //const char mountPoint[] = "MP1979d"; //The mount point you want to push data to 15 | //const char mountPointPW[] = "296ynq"; -------------------------------------------------------------------------------- /examples/Example2_DirectConnect/Example2_DirectConnect.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Echo all characters to the serial terminal. 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This sketch simply echoes chars coming from the UM980 and sends chars 9 | to the UM980. This allows a user to directly enter command strings into the UM980 10 | while still connected to the Arduino. Good for viewing raw output from a given command. 11 | 12 | For example, type CONFIG to see the module's current configuration response. 13 | 14 | These examples are targeted for an ESP32 platform but any platform that has multiple 15 | serial UARTs should be compatible. 16 | 17 | Feel like supporting open source hardware? 18 | Buy a board from SparkFun! 19 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 20 | 21 | Hardware Connections: 22 | Connect RX2 of the UM980 to pin 4 on the ESP32 23 | Connect TX2 of the UM980 to pin 13 on the ESP32 24 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 25 | Note: Almost any ESP32 pins can be used for serial. 26 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 27 | */ 28 | 29 | int pin_UART1_TX = 4; 30 | int pin_UART1_RX = 13; 31 | 32 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 33 | 34 | UM980 myGNSS; 35 | 36 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 37 | 38 | void setup() 39 | { 40 | Serial.begin(115200); 41 | delay(250); 42 | Serial.println(); 43 | Serial.println("SparkFun UM980 Example 2"); 44 | 45 | //We must start the serial port before using it in the library 46 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 47 | 48 | //myGNSS.enableDebugging(); // Print all debug to Serial 49 | 50 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 51 | { 52 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 53 | while (true); 54 | } 55 | Serial.println("UM980 detected!"); 56 | 57 | bool response = true; 58 | 59 | //Turn off all NMEA, RTCM, and any other message that may be reporting periodically 60 | response &= myGNSS.disableOutput(); 61 | 62 | Serial.println("All characters now being echoed to UM980"); 63 | Serial.println("Send CONFIG to see the current configuration"); 64 | Serial.println("Be sure both NL & CR is turned on!"); 65 | } 66 | 67 | void loop() 68 | { 69 | while(SerialGNSS.available()) 70 | Serial.write(SerialGNSS.read()); 71 | 72 | while(Serial.available()) 73 | SerialGNSS.write(Serial.read()); 74 | } 75 | -------------------------------------------------------------------------------- /examples/Example3_ECEFAndStats/Example3_ECEFAndStats.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Reading UM980 Statistics including ECEF 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to query a UM980 GNSS module for signal quality and fix type. 9 | These examples are targeted for an ESP32 platform but any platform that has multiple 10 | serial UARTs should be compatible. 11 | 12 | Feel like supporting open source hardware? 13 | Buy a board from SparkFun! 14 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 15 | 16 | Hardware Connections: 17 | Connect RX2 of the UM980 to pin 4 on the ESP32 18 | Connect TX2 of the UM980 to pin 13 on the ESP32 19 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 20 | Note: Almost any ESP32 pins can be used for serial. 21 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 22 | */ 23 | 24 | int pin_UART1_TX = 4; 25 | int pin_UART1_RX = 13; 26 | 27 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 28 | 29 | UM980 myGNSS; 30 | 31 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 32 | 33 | unsigned long lastCheck = 0; 34 | 35 | void setup() 36 | { 37 | Serial.begin(115200); 38 | delay(250); 39 | Serial.println(); 40 | Serial.println("SparkFun UM980 Example 3"); 41 | 42 | //We must start the serial port before using it in the library 43 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 44 | 45 | //myGNSS.enableDebugging(); // Print all debug to Serial 46 | 47 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 48 | { 49 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 50 | while (true); 51 | } 52 | Serial.println("UM980 detected!"); 53 | } 54 | 55 | void loop() 56 | { 57 | myGNSS.update(); //Regularly call to parse any new data 58 | 59 | if (millis() - lastCheck > 1000) 60 | { 61 | lastCheck = millis(); 62 | 63 | //This is the polling method and requires a slight delay (around 135ms) 64 | //while the device responds to the request 65 | 66 | Serial.print("Lat/Long/Alt: "); 67 | Serial.print(myGNSS.getLatitude(), 11); 68 | Serial.print("/"); 69 | Serial.print(myGNSS.getLongitude(), 11); 70 | Serial.print("/"); 71 | Serial.println(myGNSS.getAltitude(), 4); 72 | 73 | Serial.print("Deviation of Lat/Long/Alt (m): "); 74 | Serial.print(myGNSS.getLatitudeDeviation(), 4); 75 | Serial.print("/"); 76 | Serial.print(myGNSS.getLongitudeDeviation(), 4); 77 | Serial.print("/"); 78 | Serial.println(myGNSS.getAltitudeDeviation(), 4); 79 | 80 | Serial.print("ECEF X/Y/Z (m): "); 81 | Serial.print(myGNSS.getEcefX(), 4); 82 | Serial.print("/"); 83 | Serial.print(myGNSS.getEcefY(), 4); 84 | Serial.print("/"); 85 | Serial.println(myGNSS.getEcefZ(), 4); 86 | 87 | Serial.print("Deviation of ECEF X/Y/Z (m): "); 88 | Serial.print(myGNSS.getEcefXDeviation(), 4); 89 | Serial.print("/"); 90 | Serial.print(myGNSS.getEcefYDeviation(), 4); 91 | Serial.print("/"); 92 | Serial.println(myGNSS.getEcefZDeviation(), 4); 93 | 94 | Serial.print("Date (yyyy/mm/dd): "); 95 | Serial.print(myGNSS.getYear()); 96 | Serial.print("/"); 97 | if (myGNSS.getMonth() < 10) 98 | Serial.print("0"); 99 | Serial.print(myGNSS.getMonth()); 100 | Serial.print("/"); 101 | if (myGNSS.getDay() < 10) 102 | Serial.print("0"); 103 | Serial.print(myGNSS.getDay()); 104 | 105 | int timeStatus = myGNSS.getTimeStatus(); 106 | Serial.print(" Time status: "); 107 | Serial.print(timeStatus); 108 | Serial.print(" - "); 109 | if (timeStatus == 0) Serial.print("Valid"); 110 | else if (timeStatus == 3) Serial.print("Invalid!"); 111 | else Serial.print("Unknown"); 112 | Serial.println(); 113 | 114 | Serial.print("Satellites tracked: "); 115 | Serial.println(myGNSS.getSatellitesTracked()); 116 | Serial.print("Satellites used: "); 117 | Serial.println(myGNSS.getSatellitesUsed()); 118 | 119 | int positionType = myGNSS.getPositionType(); 120 | Serial.print("Position Type: "); 121 | Serial.print(positionType); 122 | Serial.print(" - "); 123 | if (positionType == 0) Serial.print("No solution"); 124 | else if (positionType == 8) Serial.print("Velocity computed using instantaneous Doppler"); 125 | else if (positionType == 16) Serial.print("Single point positioning"); 126 | else if (positionType == 17) Serial.print("Pseudorange differential solution"); 127 | else if (positionType == 18) Serial.print("SBAS positioning"); 128 | else if (positionType == 32) Serial.print("L1 float solution"); 129 | else if (positionType == 33) Serial.print("Ionosphere-free float solution"); 130 | else if (positionType == 34) Serial.print("Narrow-lane float solution"); 131 | else if (positionType == 48) Serial.print("L1 fixed solution"); 132 | else if (positionType == 49) Serial.print("Wide-lane fixed solution"); 133 | else if (positionType == 50) Serial.print("Narrow-lane fixed solution"); 134 | else if (positionType == 68) Serial.print("PPP solution converging"); 135 | else if (positionType == 69) Serial.print("Precise Point Positioning"); 136 | else Serial.print("Unknown"); 137 | Serial.println(); 138 | 139 | int solutionStatus = myGNSS.getSolutionStatus(); 140 | Serial.print("Solution Status: "); 141 | Serial.print(solutionStatus); 142 | Serial.print(" - "); 143 | if (solutionStatus == 0) Serial.print("Solution computed"); 144 | else if (solutionStatus == 1) Serial.print("Insufficient observation"); 145 | else if (solutionStatus == 2) Serial.print("No convergence, invalid solution"); 146 | else if (solutionStatus == 4) Serial.print("Covariance matrix trace exceeds maximum"); 147 | else Serial.print("Unknown"); 148 | Serial.println(); 149 | 150 | int rtkSolution = myGNSS.getRTKSolution(); 151 | Serial.print("RTK Solution: "); 152 | Serial.print(rtkSolution); 153 | Serial.print(" - "); 154 | if (rtkSolution == 0) Serial.print("Unchecked"); 155 | else if (rtkSolution == 1) Serial.print("Checked"); 156 | else Serial.print("Unknown"); 157 | Serial.println(); 158 | 159 | int pseudorangeCorrection = myGNSS.getPseudorangeCorrection(); 160 | Serial.print("Pseudorange Correction: "); 161 | Serial.print(pseudorangeCorrection); 162 | Serial.print(" - "); 163 | if (pseudorangeCorrection == 0) Serial.print("Unknown"); 164 | else if (pseudorangeCorrection == 0x001) Serial.print("Klobuchar broadcast ephemeris correction"); 165 | else if (pseudorangeCorrection == 0x010) Serial.print("SBAS ionospheric grid correction"); 166 | else if (pseudorangeCorrection == 0x011) Serial.print("Multi-frequency correction"); 167 | else if (pseudorangeCorrection == 0x100) Serial.print("Pseudorange differential correction"); 168 | else Serial.print("Unknown"); 169 | Serial.println(); 170 | 171 | Serial.println(); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /examples/Example4_EnableNMEA_5Hz/Example4_EnableNMEA_5Hz.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Enable NMEA messages on different ports on the UM980. 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to enable various NMEA sentences, at different rates, on different ports. 9 | These examples are targeted for an ESP32 platform but any platform that has multiple 10 | serial UARTs should be compatible. 11 | 12 | This example pipes all NMEA sentences to the UART1 (the USB C port) and UART2 (connected to the microcontroller). 13 | 14 | Feel like supporting open source hardware? 15 | Buy a board from SparkFun! 16 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 17 | 18 | Hardware Connections: 19 | Connect RX2 of the UM980 to pin 4 on the ESP32 20 | Connect TX2 of the UM980 to pin 13 on the ESP32 21 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 22 | Note: Almost any ESP32 pins can be used for serial. 23 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 24 | */ 25 | 26 | int pin_UART1_TX = 4; 27 | int pin_UART1_RX = 13; 28 | 29 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 30 | 31 | UM980 myGNSS; 32 | 33 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 34 | 35 | void setup() 36 | { 37 | Serial.begin(115200); 38 | delay(250); 39 | Serial.println(); 40 | Serial.println("SparkFun UM980 Example 4"); 41 | 42 | //We must start the serial port before using it in the library 43 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 44 | 45 | //myGNSS.enableDebugging(); // Print all debug to Serial 46 | 47 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 48 | { 49 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 50 | while (true); 51 | } 52 | Serial.println("UM980 detected!"); 53 | 54 | myGNSS.disableOutput(); // Disables all messages on this port 55 | 56 | //Set the output rate of NMEA messages. Users can set both the message type and update rate. 57 | //1, 0.5, 0.2, 0.1, 0.05 corresponds to 1Hz, 2Hz, 5Hz, 10Hz, 20Hz respectively. 58 | //1, 2, 5 corresponds to 1Hz, 0.5Hz, 0.2Hz respectively. 59 | //Configure the port we are currently communicating with on the UM980 (the ESP32 is connected to COM2) 60 | myGNSS.setNMEAMessage("GPGGA", 1); //Message type, 1 report per second. 61 | myGNSS.setNMEAMessage("GPGSA", 1); //Message type, 1 report per second. 62 | myGNSS.setNMEAMessage("GPGST", 1); //Message type, 1 report per second. 63 | myGNSS.setNMEAMessage("GPRMC", 1); //Message type, 1 report per second. 64 | myGNSS.setNMEAMessage("GPGSV", 1); //Message type, 1 report per second. 65 | 66 | //Configure a specific port 67 | float outputRate = 0.2; //0.2 = 5 reports per second. 68 | char comName[] = "COM1"; //COM1, COM2, and COM3 are valid 69 | myGNSS.setNMEAPortMessage("GPGGA", comName, outputRate); //Message type, COM port, output rate. 70 | myGNSS.setNMEAPortMessage("GPGSA", comName, outputRate); 71 | myGNSS.setNMEAPortMessage("GPGST", comName, outputRate); 72 | myGNSS.setNMEAPortMessage("GPRMC", comName, outputRate); 73 | myGNSS.setNMEAPortMessage("GPGSV", comName, 1); //Limit GSV to 1Hz 74 | 75 | myGNSS.saveConfiguration(); //Save the current configuration into non-volatile memory (NVM) 76 | 77 | Serial.println("Wait for UM980 to get fix and output NMEA..."); 78 | } 79 | 80 | void loop() 81 | { 82 | //Read in NMEA from the UM980 83 | while (SerialGNSS.available()) 84 | Serial.write(SerialGNSS.read()); 85 | } 86 | -------------------------------------------------------------------------------- /examples/Example5_EnableRTCM/Example5_EnableRTCM.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Enable an RTCM message on various ports, at various rates. 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to put the UM980 into a Base mode configuration using specified coordinates. 9 | These examples are targeted for an ESP32 platform but any platform that has multiple 10 | serial UARTs should be compatible. 11 | 12 | Feel like supporting open source hardware? 13 | Buy a board from SparkFun! 14 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 15 | 16 | Hardware Connections: 17 | Connect RX2 of the UM980 to pin 4 on the ESP32 18 | Connect TX2 of the UM980 to pin 13 on the ESP32 19 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 20 | Note: Almost any ESP32 pins can be used for serial. 21 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 22 | */ 23 | 24 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 25 | #include //http://librarymanager/All#SparkFun_Extensible_Message_Parser 26 | 27 | //---------------------------------------- 28 | // Constants 29 | //---------------------------------------- 30 | 31 | #define pin_UART_TX 4 32 | #define pin_UART_RX 13 33 | 34 | // Build the table listing all of the parsers 35 | SEMP_PARSE_ROUTINE const parserTable[] = 36 | { 37 | sempRtcmPreamble 38 | }; 39 | const int parserCount = sizeof(parserTable) / sizeof(parserTable[0]); 40 | 41 | const char * const parserNames[] = 42 | { 43 | "RTCM parser" 44 | }; 45 | const int parserNameCount = sizeof(parserNames) / sizeof(parserNames[0]); 46 | 47 | //---------------------------------------- 48 | // Locals 49 | //---------------------------------------- 50 | 51 | SEMP_PARSE_STATE *parse; 52 | UM980 myGNSS; 53 | 54 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 55 | 56 | void setup() 57 | { 58 | Serial.begin(115200); 59 | delay(250); 60 | Serial.println(); 61 | Serial.println("SparkFun UM980 Example 5"); 62 | 63 | // Initialize the parser 64 | parse = sempBeginParser(parserTable, parserCount, 65 | parserNames, parserNameCount, 66 | 0, 3000, processMessage, "RTCM_Test"); 67 | if (!parse) 68 | reportFatalError("Failed to initialize the parser"); 69 | sempEnableDebugOutput(parse); 70 | 71 | //We must start the serial port before using it in the library 72 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART_RX, pin_UART_TX); 73 | 74 | myGNSS.enableDebugging(); // Print all debug to Serial 75 | 76 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 77 | { 78 | Serial.println("UM980 failed to respond. Check ports and baud rates."); 79 | while (1); 80 | } 81 | Serial.println("UM980 detected!"); 82 | 83 | myGNSS.disableOutput(); // Disables all messages on this port 84 | 85 | //Configure the port on the UM980 we are currently commuicating with 86 | myGNSS.setRTCMMessage("RTCM1005", 1); //Message type, 1 report per second. 87 | 88 | //Configure a given port on the UM980 with a given message type 89 | myGNSS.setRTCMPortMessage("RTCM1074", "COM3", 1); //Message type, COM name, 1 report per second. 90 | 91 | myGNSS.setRTCMMessage("RTCM1124", 0); //Disable given message 92 | myGNSS.setRTCMPortMessage("RTCM1093", "COM1", 0); //Disable given message on a given port 93 | 94 | myGNSS.saveConfiguration(); //Save the current configuration into non-volatile memory (NVM) 95 | 96 | Serial.println("RTCM messages are dumped in HEX if the CRC is correct"); 97 | } 98 | 99 | void loop() 100 | { 101 | while (SerialGNSS.available()) 102 | // Update the parser state based on the incoming byte 103 | sempParseNextByte(parse, SerialGNSS.read()); 104 | } 105 | 106 | // Call back from within parser, for end of message 107 | // Process a complete message incoming from parser 108 | void processMessage(SEMP_PARSE_STATE *parse, uint16_t type) 109 | { 110 | SEMP_SCRATCH_PAD *scratchPad = (SEMP_SCRATCH_PAD *)parse->scratchPad; 111 | static bool displayOnce = true; 112 | 113 | // Display the raw message 114 | Serial.println(); 115 | Serial.printf("Valid RTCM message: 0x%04x (%d) bytes\r\n", parse->length, parse->length); 116 | ex5DumpBuffer(parse->buffer, parse->length); 117 | 118 | // Display the parser state 119 | if (displayOnce) 120 | { 121 | displayOnce = false; 122 | Serial.println(); 123 | sempPrintParserConfiguration(parse); 124 | } 125 | } 126 | 127 | // Display the contents of a buffer 128 | void ex5DumpBuffer(const uint8_t *buffer, uint16_t length) 129 | { 130 | int bytes; 131 | const uint8_t *end; 132 | int index; 133 | uint16_t offset; 134 | 135 | end = &buffer[length]; 136 | offset = 0; 137 | while (buffer < end) 138 | { 139 | // Determine the number of bytes to display on the line 140 | bytes = end - buffer; 141 | if (bytes > (16 - (offset & 0xf))) 142 | bytes = 16 - (offset & 0xf); 143 | 144 | // Display the offset 145 | Serial.printf("0x%08lx: ", offset); 146 | 147 | // Skip leading bytes 148 | for (index = 0; index < (offset & 0xf); index++) 149 | Serial.printf(" "); 150 | 151 | // Display the data bytes 152 | for (index = 0; index < bytes; index++) 153 | Serial.printf("%02x ", buffer[index]); 154 | 155 | // Separate the data bytes from the ASCII 156 | for (; index < (16 - (offset & 0xf)); index++) 157 | Serial.printf(" "); 158 | Serial.printf(" "); 159 | 160 | // Skip leading bytes 161 | for (index = 0; index < (offset & 0xf); index++) 162 | Serial.printf(" "); 163 | 164 | // Display the ASCII values 165 | for (index = 0; index < bytes; index++) 166 | Serial.printf("%c", ((buffer[index] < ' ') || (buffer[index] >= 0x7f)) ? '.' : buffer[index]); 167 | Serial.printf("\r\n"); 168 | 169 | // Set the next line of data 170 | buffer += bytes; 171 | offset += bytes; 172 | } 173 | } 174 | 175 | // Print the error message every 15 seconds 176 | void reportFatalError(const char *errorMsg) 177 | { 178 | while (1) 179 | { 180 | Serial.print("HALTED: "); 181 | Serial.print(errorMsg); 182 | Serial.println(); 183 | sleep(15); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /examples/Example6_AverageBase/Example6_AverageBase.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Setting UM980 as Base Station using averaged coordinates (60 seconds) 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to put the UM980 into a Base mode configuration using the average of positional 9 | fixes obtained over a 60 second period. We also turn on RTCM messages. 10 | These examples are targeted for an ESP32 platform but any platform that has multiple 11 | serial UARTs should be compatible. 12 | 13 | Feel like supporting open source hardware? 14 | Buy a board from SparkFun! 15 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 16 | 17 | Hardware Connections: 18 | Connect RX2 of the UM980 to pin 4 on the ESP32 19 | Connect TX2 of the UM980 to pin 13 on the ESP32 20 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 21 | Note: Almost any ESP32 pins can be used for serial. 22 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 23 | */ 24 | 25 | int pin_UART1_TX = 4; 26 | int pin_UART1_RX = 13; 27 | 28 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 29 | #include //http://librarymanager/All#SparkFun_Extensible_Message_Parser 30 | 31 | #define NMEA_PARSER_INDEX 0 32 | #define RTCM_PARSER_INDEX 1 33 | 34 | // Build the table listing all of the parsers 35 | SEMP_PARSE_ROUTINE const parserTable[] = 36 | { 37 | sempNmeaPreamble, 38 | sempRtcmPreamble, 39 | }; 40 | const int parserCount = sizeof(parserTable) / sizeof(parserTable[0]); 41 | 42 | const char * const parserNames[] = 43 | { 44 | "NMEA Parser", 45 | "RTCM Parser", 46 | }; 47 | const int parserNameCount = sizeof(parserNames) / sizeof(parserNames[0]); 48 | 49 | SEMP_PARSE_STATE *parse; // State of the parsers 50 | 51 | UM980 myGNSS; 52 | 53 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 54 | 55 | void setup() 56 | { 57 | Serial.begin(115200); 58 | delay(250); 59 | Serial.println(); 60 | Serial.println("SparkFun UM980 Example 6"); 61 | 62 | //We must start the serial port before using it in the library 63 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 64 | 65 | myGNSS.enableDebugging(); // Print all debug to Serial 66 | 67 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 68 | { 69 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 70 | while (true); 71 | } 72 | Serial.println("UM980 detected!"); 73 | 74 | myGNSS.disableOutput(); //Turn off all messages on this port during configuration 75 | 76 | //Set the output rate of NMEA messages. Users can set both the message type and update rate. 77 | //Configure the port we are currently communicating with on the UM980 (the ESP32 is connected to COM2) 78 | //1, 0.5, 0.2, 0.1, 0.05 corresponds to 1Hz, 2Hz, 5Hz, 10Hz, 20Hz respectively. 79 | //1, 2, 5 corresponds to 1Hz, 0.5Hz, 0.2Hz respectively. 80 | myGNSS.setNMEAMessage("GPGGA", 1); //Message type, 1 report per second. 81 | myGNSS.setNMEAMessage("GPGSA", 1); //Message type, 1 report per second. 82 | myGNSS.setNMEAMessage("GPGST", 1); //Message type, 1 report per second. 83 | myGNSS.setNMEAMessage("GPRMC", 1); //Message type, 1 report per second. 84 | myGNSS.setNMEAMessage("GPGSV", 1); //Message type, 1 report per second. 85 | 86 | //When the coordinates of base station are unknown, users can set the receiver to 87 | //automatically positioning for a period of time and get the average value as the 88 | //coordinates of the base station. This is similar to the u-blox 'Survey In' method. 89 | myGNSS.setModeBaseAverage(); //Average for 60 seconds (default) 90 | //myGNSS.setModeBaseAverage(120); //Average for 120 seconds. 91 | 92 | //Set the output rate of RTCM correction messages. 93 | //Unicore supports a *large* number of RTCM messages. We recommend the following for most applications: 94 | //RTCM1006, 1074, 1084, 1094, 1124, 1033 95 | myGNSS.setRTCMMessage("RTCM1006", 10); 96 | myGNSS.setRTCMMessage("RTCM1074", 1); 97 | myGNSS.setRTCMMessage("RTCM1084", 1); 98 | myGNSS.setRTCMMessage("RTCM1094", 1); 99 | myGNSS.setRTCMMessage("RTCM1124", 1); 100 | myGNSS.setRTCMMessage("RTCM1033", 10); 101 | 102 | //myGNSS.setRTCMMessage("RTCM1033", 0); //Example of how to disable a specific message 103 | 104 | //myGNSS.setRTCMPortMessage("RTCM1074", "COM1", 1); //Example of how to enable a message on a specific port 105 | 106 | myGNSS.saveConfiguration(); //Save the current configuration into non-volatile memory (NVM) 107 | 108 | // Initialize the parser 109 | parse = sempBeginParser(parserTable, parserCount, 110 | parserNames, parserNameCount, 111 | 0, 3000, processMessage, "Example 17 Parser"); 112 | if (!parse) 113 | while (1) 114 | { 115 | Serial.println("HALTED: Failed to initialize the parser!"); 116 | sleep(15); 117 | } 118 | 119 | Serial.println("Output will be a mix of NMEA and binary RTCM"); 120 | } 121 | 122 | void loop() 123 | { 124 | // Read the raw data one byte at a time and update the parser state 125 | // based on the incoming byte 126 | while (SerialGNSS.available()) 127 | { 128 | // Read the byte from the UM980 129 | uint8_t incoming = SerialGNSS.read(); 130 | 131 | // Parse this byte 132 | sempParseNextByte(parse, incoming); 133 | } 134 | } 135 | 136 | // Call back from within parser, for end of message 137 | // Process a complete message incoming from parser 138 | void processMessage(SEMP_PARSE_STATE *parse, uint16_t type) 139 | { 140 | SEMP_SCRATCH_PAD *scratchPad = (SEMP_SCRATCH_PAD *)parse->scratchPad; 141 | char *typeName; 142 | 143 | // Display the raw message 144 | // The type value is the index into the raw data array 145 | Serial.println(); 146 | Serial.printf("Valid %s message: 0x%04x (%d) bytes\r\n", 147 | parserNames[type], parse->length, parse->length); 148 | dumpBuffer(parse->buffer, parse->length); 149 | } 150 | 151 | // Display the contents of a buffer 152 | void dumpBuffer(const uint8_t *buffer, uint16_t length) 153 | { 154 | int bytes; 155 | const uint8_t *end; 156 | int index; 157 | char line[128]; 158 | uint16_t offset; 159 | 160 | end = &buffer[length]; 161 | offset = 0; 162 | while (buffer < end) 163 | { 164 | // Determine the number of bytes to display on the line 165 | bytes = end - buffer; 166 | if (bytes > (16 - (offset & 0xf))) 167 | bytes = 16 - (offset & 0xf); 168 | 169 | // Display the offset 170 | sprintf(line, "0x%08lx: ", offset); 171 | 172 | // Skip leading bytes 173 | for (index = 0; index < (offset & 0xf); index++) 174 | sprintf(&line[strlen(line)], " "); 175 | 176 | // Display the data bytes 177 | for (index = 0; index < bytes; index++) 178 | sprintf(&line[strlen(line)], "%02x ", buffer[index]); 179 | 180 | // Separate the data bytes from the ASCII 181 | for (; index < (16 - (offset & 0xf)); index++) 182 | sprintf(&line[strlen(line)], " "); 183 | sprintf(&line[strlen(line)], " "); 184 | 185 | // Skip leading bytes 186 | for (index = 0; index < (offset & 0xf); index++) 187 | sprintf(&line[strlen(line)], " "); 188 | 189 | // Display the ASCII values 190 | for (index = 0; index < bytes; index++) 191 | sprintf(&line[strlen(line)], "%c", ((buffer[index] < ' ') || (buffer[index] >= 0x7f)) ? '.' : buffer[index]); 192 | Serial.println(line); 193 | 194 | // Set the next line of data 195 | buffer += bytes; 196 | offset += bytes; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /examples/Example7_FixedBase/Example7_FixedBase.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Setting UM980 as Base Station using provided Geodetic or ECEF coordinates 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to put the UM980 into a Base mode configuration using specified coordinates. 9 | These examples are targeted for an ESP32 platform but any platform that has multiple 10 | serial UARTs should be compatible. 11 | 12 | Feel like supporting open source hardware? 13 | Buy a board from SparkFun! 14 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 15 | 16 | Hardware Connections: 17 | Connect RX2 of the UM980 to pin 4 on the ESP32 18 | Connect TX2 of the UM980 to pin 13 on the ESP32 19 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 20 | Note: Almost any ESP32 pins can be used for serial. 21 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 22 | */ 23 | 24 | int pin_UART1_TX = 4; 25 | int pin_UART1_RX = 13; 26 | 27 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 28 | #include //http://librarymanager/All#SparkFun_Extensible_Message_Parser 29 | 30 | #define NMEA_PARSER_INDEX 0 31 | #define RTCM_PARSER_INDEX 1 32 | 33 | // Build the table listing all of the parsers 34 | SEMP_PARSE_ROUTINE const parserTable[] = 35 | { 36 | sempNmeaPreamble, 37 | sempRtcmPreamble, 38 | }; 39 | const int parserCount = sizeof(parserTable) / sizeof(parserTable[0]); 40 | 41 | const char * const parserNames[] = 42 | { 43 | "NMEA Parser", 44 | "RTCM Parser", 45 | }; 46 | const int parserNameCount = sizeof(parserNames) / sizeof(parserNames[0]); 47 | 48 | SEMP_PARSE_STATE *parse; // State of the parsers 49 | 50 | UM980 myGNSS; 51 | 52 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 53 | 54 | void setup() 55 | { 56 | Serial.begin(115200); 57 | delay(250); 58 | Serial.println(); 59 | Serial.println("SparkFun UM980 Example 7"); 60 | 61 | //We must start the serial port before using it in the library 62 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 63 | 64 | myGNSS.enableDebugging(); // Print all debug to Serial 65 | 66 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 67 | { 68 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 69 | while (true); 70 | } 71 | Serial.println("UM980 detected!"); 72 | 73 | myGNSS.disableOutput(); //Turn off all messages on this port during configuration 74 | 75 | //Set the output rate of NMEA messages. Users can set both the message type and update rate. 76 | //Configure the port we are currently communicating with on the UM980 (the ESP32 is connected to COM2) 77 | //1, 0.5, 0.2, 0.1, 0.05 corresponds to 1Hz, 2Hz, 5Hz, 10Hz, 20Hz respectively. 78 | //1, 2, 5 corresponds to 1Hz, 0.5Hz, 0.2Hz respectively. 79 | myGNSS.setNMEAMessage("GPGGA", 1); //Message type, 1 report per second. 80 | myGNSS.setNMEAMessage("GPGSA", 1); //Message type, 1 report per second. 81 | myGNSS.setNMEAMessage("GPGST", 1); //Message type, 1 report per second. 82 | myGNSS.setNMEAMessage("GPRMC", 1); //Message type, 1 report per second. 83 | myGNSS.setNMEAMessage("GPGSV", 1); //Message type, 1 report per second. 84 | 85 | //When the coordinates of the base station are known, users can set the position 86 | //using Geodetic or ECEF coordinates. 87 | myGNSS.setModeBaseGeodetic(40.09029479, -105.18505761, 1560.089); //SparkFun HQ 88 | //myGNSS.setModeBaseECEF(-1280206.568, -4716804.403, 4086665.484); //SparkFun HQ 89 | 90 | //Set the output rate of RTCM correction messages. 91 | //Unicore supports a *large* number of RTCM messages. We recommend the following for most applications: 92 | //RTCM1006, 1074, 1084, 1094, 1124, 1033 93 | myGNSS.setRTCMMessage("RTCM1006", 10); 94 | myGNSS.setRTCMMessage("RTCM1074", 1); 95 | myGNSS.setRTCMMessage("RTCM1084", 1); 96 | myGNSS.setRTCMMessage("RTCM1094", 1); 97 | myGNSS.setRTCMMessage("RTCM1124", 1); 98 | myGNSS.setRTCMMessage("RTCM1033", 10); 99 | 100 | //myGNSS.setRTCMMessage("RTCM1033", 0); //Example of how to disable a specific message 101 | 102 | //myGNSS.setRTCMPortMessage("RTCM1074", "COM1", 1); //Example of how to enable a message on a specific port 103 | 104 | myGNSS.saveConfiguration(); //Save the current configuration into non-volatile memory (NVM) 105 | 106 | // Initialize the parser 107 | parse = sempBeginParser(parserTable, parserCount, 108 | parserNames, parserNameCount, 109 | 0, 3000, processMessage, "Example 17 Parser"); 110 | if (!parse) 111 | while (1) 112 | { 113 | Serial.println("HALTED: Failed to initialize the parser!"); 114 | sleep(15); 115 | } 116 | 117 | Serial.println("Output will be a mix of NMEA and binary RTCM"); 118 | } 119 | 120 | void loop() 121 | { 122 | // Read the raw data one byte at a time and update the parser state 123 | // based on the incoming byte 124 | while (SerialGNSS.available()) 125 | { 126 | // Read the byte from the UM980 127 | uint8_t incoming = SerialGNSS.read(); 128 | 129 | // Parse this byte 130 | sempParseNextByte(parse, incoming); 131 | } 132 | } 133 | 134 | // Call back from within parser, for end of message 135 | // Process a complete message incoming from parser 136 | void processMessage(SEMP_PARSE_STATE *parse, uint16_t type) 137 | { 138 | SEMP_SCRATCH_PAD *scratchPad = (SEMP_SCRATCH_PAD *)parse->scratchPad; 139 | char *typeName; 140 | 141 | // Display the raw message 142 | // The type value is the index into the raw data array 143 | Serial.println(); 144 | Serial.printf("Valid %s message: 0x%04x (%d) bytes\r\n", 145 | parserNames[type], parse->length, parse->length); 146 | dumpBuffer(parse->buffer, parse->length); 147 | } 148 | 149 | // Display the contents of a buffer 150 | void dumpBuffer(const uint8_t *buffer, uint16_t length) 151 | { 152 | int bytes; 153 | const uint8_t *end; 154 | int index; 155 | char line[128]; 156 | uint16_t offset; 157 | 158 | end = &buffer[length]; 159 | offset = 0; 160 | while (buffer < end) 161 | { 162 | // Determine the number of bytes to display on the line 163 | bytes = end - buffer; 164 | if (bytes > (16 - (offset & 0xf))) 165 | bytes = 16 - (offset & 0xf); 166 | 167 | // Display the offset 168 | sprintf(line, "0x%08lx: ", offset); 169 | 170 | // Skip leading bytes 171 | for (index = 0; index < (offset & 0xf); index++) 172 | sprintf(&line[strlen(line)], " "); 173 | 174 | // Display the data bytes 175 | for (index = 0; index < bytes; index++) 176 | sprintf(&line[strlen(line)], "%02x ", buffer[index]); 177 | 178 | // Separate the data bytes from the ASCII 179 | for (; index < (16 - (offset & 0xf)); index++) 180 | sprintf(&line[strlen(line)], " "); 181 | sprintf(&line[strlen(line)], " "); 182 | 183 | // Skip leading bytes 184 | for (index = 0; index < (offset & 0xf); index++) 185 | sprintf(&line[strlen(line)], " "); 186 | 187 | // Display the ASCII values 188 | for (index = 0; index < bytes; index++) 189 | sprintf(&line[strlen(line)], "%c", ((buffer[index] < ' ') || (buffer[index] >= 0x7f)) ? '.' : buffer[index]); 190 | Serial.println(line); 191 | 192 | // Set the next line of data 193 | buffer += bytes; 194 | offset += bytes; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /examples/Example8_SetConstellations/Example8_SetConstellations.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Enable or disable various constellations to be included in position calculation (GPS, GLO, BDS, GAL, QZSS) 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how to put the UM980 into a Base mode configuration using specified coordinates. 9 | These examples are targeted for an ESP32 platform but any platform that has multiple 10 | serial UARTs should be compatible. 11 | 12 | Feel like supporting open source hardware? 13 | Buy a board from SparkFun! 14 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 15 | 16 | Hardware Connections: 17 | Connect RX2 of the UM980 to pin 4 on the ESP32 18 | Connect TX2 of the UM980 to pin 13 on the ESP32 19 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 20 | Note: Almost any ESP32 pins can be used for serial. 21 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 22 | 23 | */ 24 | 25 | int pin_UART1_TX = 4; 26 | int pin_UART1_RX = 13; 27 | 28 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 29 | 30 | UM980 myGNSS; 31 | 32 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 33 | 34 | void setup() 35 | { 36 | Serial.begin(115200); 37 | delay(250); 38 | Serial.println(); 39 | Serial.println("SparkFun UM980 Example 8"); 40 | 41 | //We must start the serial port before using it in the library 42 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 43 | 44 | myGNSS.enableDebugging(); // Print all debug to Serial 45 | 46 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 47 | { 48 | Serial.println("UM980 failed to respond. Check ports and baud rates."); 49 | while (1); 50 | } 51 | Serial.println("UM980 detected!"); 52 | 53 | //We can enable/disable constellations and check if the command was successful 54 | if (myGNSS.enableConstellation("GPS") == false) 55 | Serial.println("GPS Enable Failed"); 56 | else 57 | Serial.println("GPS Enable Successful"); 58 | 59 | //We can batch commands together and check the overall success 60 | bool response = true; 61 | if (!myGNSS.enableConstellation("BDS")) 62 | { 63 | response = false; 64 | Serial.println("Failed to enable BDS constellation"); 65 | } 66 | if (!myGNSS.enableConstellation("GAL")) 67 | { 68 | response = false; 69 | Serial.println("Failed to enable GAL constellation"); 70 | } 71 | if (!myGNSS.disableConstellation("GLO")) 72 | { 73 | response = false; 74 | Serial.println("Failed to disable GLO constellation"); 75 | } 76 | if (!myGNSS.enableConstellation("QZSS")) 77 | { 78 | response = false; 79 | Serial.println("Failed to enable QZSS constellation"); 80 | } 81 | 82 | //Save the current configuration into non-volatile memory (NVM) 83 | if (!myGNSS.saveConfiguration()) 84 | { 85 | response = false; 86 | Serial.println("Failed to save the configuration"); 87 | } 88 | 89 | if (response == true) 90 | Serial.println("Configuration complete!"); 91 | else 92 | Serial.println("Configuration failed!"); 93 | } 94 | 95 | void loop() 96 | { 97 | //Read in NMEA from the UM980 98 | // while (SerialGNSS.available()) 99 | // Serial.write(SerialGNSS.read()); 100 | } 101 | -------------------------------------------------------------------------------- /examples/Example9_SignalElevation/Example9_SignalElevation.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Set the UM980 Elevation Angle and minimum CN0 value 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2023 6 | License: MIT. Please see LICENSE.md for more information. 7 | 8 | This example shows how set the Elevation Angle and minimum CN0 value required from a 9 | satellite to be included in the position calculation. 10 | These examples are targeted for an ESP32 platform but any platform that has multiple 11 | serial UARTs should be compatible. 12 | 13 | Feel like supporting open source hardware? 14 | Buy a board from SparkFun! 15 | SparkFun Triband GNSS RTK Breakout - UM980 (GPS-23286) https://www.sparkfun.com/products/23286 16 | 17 | Hardware Connections: 18 | Connect RX2 of the UM980 to pin 4 on the ESP32 19 | Connect TX2 of the UM980 to pin 13 on the ESP32 20 | To make this easier, a 4-pin locking JST cable can be purchased here: https://www.sparkfun.com/products/17240 21 | Note: Almost any ESP32 pins can be used for serial. 22 | Connect a dual or triband GNSS antenna: https://www.sparkfun.com/products/21801 23 | */ 24 | 25 | int pin_UART1_TX = 4; 26 | int pin_UART1_RX = 13; 27 | 28 | #include //http://librarymanager/All#SparkFun_Unicore_GNSS 29 | 30 | UM980 myGNSS; 31 | 32 | HardwareSerial SerialGNSS(1); //Use UART1 on the ESP32 33 | 34 | void setup() 35 | { 36 | Serial.begin(115200); 37 | delay(250); 38 | Serial.println(); 39 | Serial.println("SparkFun UM980 Example 9"); 40 | 41 | //We must start the serial port before using it in the library 42 | SerialGNSS.begin(115200, SERIAL_8N1, pin_UART1_RX, pin_UART1_TX); 43 | 44 | //myGNSS.enableDebugging(); // Print all debug to Serial 45 | 46 | if (myGNSS.begin(SerialGNSS) == false) //Give the serial port over to the library 47 | { 48 | Serial.println("UM980 failed to respond. Check ports and baud rates. Freezing..."); 49 | while (true); 50 | } 51 | Serial.println("UM980 detected!"); 52 | 53 | myGNSS.setElevationAngle(20); //Set the elevation mask angle to 20 degrees. The default is 5 degrees. 54 | myGNSS.setMinCNO(10); //Set the minimum CN0 value to 10 dBHz 55 | 56 | myGNSS.saveConfiguration(); //Save the current configuration into non-volatile memory (NVM) 57 | 58 | Serial.println("Configuration complete"); 59 | } 60 | 61 | void loop() 62 | { 63 | //Read in NMEA from the UM980 64 | while (SerialGNSS.available()) 65 | Serial.write(SerialGNSS.read()); 66 | } 67 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | UM980 KEYWORD1 10 | Um980Result KEYWORD1 11 | 12 | ####################################### 13 | # Methods and Functions (KEYWORD2) 14 | ####################################### 15 | 16 | begin KEYWORD2 17 | isConnected KEYWORD2 18 | isBlocking KEYWORD2 19 | enableDebugging KEYWORD2 20 | disableDebugging KEYWORD2 21 | 22 | setMode KEYWORD2 23 | setModeBase KEYWORD2 24 | setModeBaseGeodetic KEYWORD2 25 | setModeBaseECEF KEYWORD2 26 | setModeBaseAverage KEYWORD2 27 | setModeRover KEYWORD2 28 | setModeRoverSurvey KEYWORD2 29 | setModeRoverUAV KEYWORD2 30 | setModeRoverAutomotive KEYWORD2 31 | setModeRoverMow KEYWORD2 32 | setPortBaudrate KEYWORD2 33 | setBaudrate KEYWORD2 34 | 35 | enablePPS KEYWORD2 36 | disablePPS KEYWORD2 37 | configurePPS KEYWORD2 38 | 39 | enableConstellation KEYWORD2 40 | disableConstellation KEYWORD2 41 | 42 | setElevationAngle KEYWORD2 43 | setMinCNO KEYWORD2 44 | 45 | enableFrequency KEYWORD2 46 | disableFrequency KEYWORD2 47 | enableSystem KEYWORD2 48 | disableSystem KEYWORD2 49 | setNMEAPortMessage KEYWORD2 50 | setNMEAMessage KEYWORD2 51 | setRTCMPortMessage KEYWORD2 52 | setRTCMMessage KEYWORD2 53 | disableOutput KEYWORD2 54 | disableOutputPort KEYWORD2 55 | 56 | factoryReset KEYWORD2 57 | reset KEYWORD2 58 | saveConfiguration KEYWORD2 59 | 60 | serialAvailable KEYWORD2 61 | serialRead KEYWORD2 62 | serialPrintln KEYWORD2 63 | 64 | clearBuffer KEYWORD2 65 | 66 | sendCommand KEYWORD2 67 | sendQuery KEYWORD2 68 | sendString KEYWORD2 69 | 70 | scanForCharacter KEYWORD2 71 | getResponseAscii KEYWORD2 72 | getResponseBinary KEYWORD2 73 | 74 | checkChecksum KEYWORD2 75 | checkCRC KEYWORD2 76 | 77 | getLatitude KEYWORD2 78 | getLongitude KEYWORD2 79 | getAltitude KEYWORD2 80 | getLatitudeDeviation KEYWORD2 81 | getLongitudeDeviation KEYWORD2 82 | getAltitudeDeviation KEYWORD2 83 | getEcefX KEYWORD2 84 | getEcefY KEYWORD2 85 | getEcefZ KEYWORD2 86 | getEcefXDeviation KEYWORD2 87 | getEcefYDeviation KEYWORD2 88 | getEcefZDeviation KEYWORD2 89 | getSIV KEYWORD2 90 | getSatellitesUsed KEYWORD2 91 | getSatellitesTracked KEYWORD2 92 | getSolutionStatus KEYWORD2 93 | getPositionType KEYWORD2 94 | getRTKSolution KEYWORD2 95 | getPseudorangeCorrection KEYWORD2 96 | getYear KEYWORD2 97 | getMonth KEYWORD2 98 | getDay KEYWORD2 99 | getHour KEYWORD2 100 | getMinute KEYWORD2 101 | getSecond KEYWORD2 102 | getMillisecond KEYWORD2 103 | getTimeStatus KEYWORD2 104 | getDateStatus KEYWORD2 105 | getTimeOffset KEYWORD2 106 | getTimeOffsetDeviation KEYWORD2 107 | getFixAgeMilliseconds KEYWORD2 108 | getModuleType KEYWORD2 109 | getID KEYWORD2 110 | getVersion KEYWORD2 111 | getCompileTime KEYWORD2 112 | getVersionFull KEYWORD2 113 | 114 | enableBinaryBeforeFix KEYWORD2 115 | disableBinaryBeforeFix KEYWORD2 116 | 117 | ####################################### 118 | # Constants (LITERAL1) 119 | ####################################### 120 | 121 | UM980_RESULT_OK 122 | UM980_RESULT_TIMEOUT_START_BYTE 123 | UM980_RESULT_TIMEOUT_DATA_BYTE 124 | UM980_RESULT_TIMEOUT_END_BYTE 125 | UM980_RESULT_WRONG_COMMAND 126 | UM980_RESULT_WRONG_MESSAGE_ID 127 | UM980_RESULT_COMMAND_ERROR 128 | UM980_RESULT_BAD_START_BYTE 129 | UM980_RESULT_BAD_CHECKSUM 130 | UM980_RESULT_BAD_CRC 131 | UM980_RESULT_MISSING_CRC 132 | UM980_RESULT_TIMEOUT 133 | UM980_RESULT_RESPONSE_OVERFLOW 134 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SparkFun UM980 Triband RTK GNSS Arduino Library 2 | version=1.0.5 3 | author=SparkFun Electronics 4 | maintainer=SparkFun Electronics 5 | sentence=Library for Serial Communication and Configuration of the UM980 6 | paragraph=This is a library to control Unicore GNSS receivers, with a focus on the UM980 Triband receiver. Other receivers in the same family should work: UM982, UM960, UM960L, etc. The UM980 is a 1408-Channel GNSS Receiver based on the Nebulas IV™ that is able to simultaneously track GPS L1/L2/L5, GLONASS L1/L2/L3, Galileo E1/E5a/E5b/E6, Beidou B1I/B2I/B3I/B1C/B2a/B2b, QZSS L1/L2/L5, and SBAS. This library is ideal for interfacing to the SparkFun Triband GNSS RTK Breakout - UM980.
7 | category=Sensors 8 | url=https://github.com/sparkfun/SparkFun_Unicore_GNSS_Arduino_Library 9 | architectures=* 10 | depends=SparkFun Extensible Message Parser 11 | -------------------------------------------------------------------------------- /src/SparkFun_Unicore_GNSS_Arduino_Library.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This is a library to control Unicore GNSS receivers, with 3 | a focus on the UM980 Triband receiver. Other receivers in the 4 | same family should work: UM982, UM960, UM960L, etc. 5 | 6 | https://github.com/sparkfun/SparkFun_Unicore_GNSS_Arduino_Library 7 | Best used with the UM980 Breakout: https://www.sparkfun.com/products/23286 8 | 9 | Development environment specifics: 10 | Arduino IDE 1.8.x 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | */ 17 | 18 | #include "SparkFun_Unicore_GNSS_Arduino_Library.h" 19 | #include "Arduino.h" 20 | 21 | //---------------------------------------- 22 | // Constants 23 | //---------------------------------------- 24 | 25 | // parserTable index values 26 | #define UM980_NMEA_PARSER_INDEX 0 27 | #define UM980_UNICORE_HASH_PARSER_INDEX 1 28 | #define UM980_RTCM_PARSER_INDEX 2 29 | #define UM980_UNICORE_BINARY_PARSER_INDEX 3 30 | 31 | // Build the table listing all of the parsers 32 | SEMP_PARSE_ROUTINE const parserTable[] = { 33 | sempNmeaPreamble, 34 | sempUnicoreHashPreamble, 35 | sempRtcmPreamble, 36 | sempUnicoreBinaryPreamble, 37 | }; 38 | const int parserCount = sizeof(parserTable) / sizeof(parserTable[0]); 39 | 40 | const char *const parserNames[] = { 41 | "UN980 NMEA Parser", 42 | "UM980 Unicore Hash (#) Parser", 43 | "UM980 RTCM Parser", 44 | "UM980 Unicore Binary Parser", 45 | }; 46 | const int parserNameCount = sizeof(parserNames) / sizeof(parserNames[0]); 47 | 48 | // Account for the largest message 49 | #define BUFFER_LENGTH 3000 50 | 51 | //---------------------------------------- 52 | // Globals 53 | //---------------------------------------- 54 | 55 | UM980 *ptrUM980 = nullptr; // Global pointer for external parser access into library class 56 | 57 | //---------------------------------------- 58 | // Parser support routines 59 | //---------------------------------------- 60 | 61 | // Enable the display of bad checksum messages from the parser 62 | void UM980::enablePrintBadChecksums() 63 | { 64 | _printBadChecksum = true; 65 | } 66 | 67 | // Disable the display of bad checksum messages from the parser 68 | void UM980::disablePrintBadChecksums() 69 | { 70 | _printBadChecksum = false; 71 | } 72 | 73 | // Alternate checksum for NMEA parser needed during setup 74 | bool badNmeaChecksum(SEMP_PARSE_STATE *parse) 75 | { 76 | int alternateChecksum; 77 | bool badChecksum; 78 | int checksum; 79 | 80 | // Not a NMEA parser, no correction is possible 81 | if (parse->type >= UM980_RTCM_PARSER_INDEX) 82 | return false; 83 | 84 | // Older UM980 firmware during setup is improperly adding the '$' 85 | // into the checksum calculation. Convert the received checksum 86 | // characters into binary. 87 | checksum = sempAsciiToNibble(parse->buffer[parse->length - 1]); 88 | checksum |= sempAsciiToNibble(parse->buffer[parse->length - 2]) << 4; 89 | 90 | // Determine if the checksum also includes the '$' or '#' 91 | alternateChecksum = parse->crc ^ (parse->type ? '#' : '$'); 92 | badChecksum = (alternateChecksum != checksum); 93 | 94 | // Display bad checksums 95 | if ((!badChecksum) && ptrUM980->_printBadChecksum) 96 | { 97 | ptrUM980->debugPrintf("Unicore Lib: Message improperly includes %c in checksum", parse->buffer[0]); 98 | ptrUM980->dumpBuffer(parse->buffer, parse->length); 99 | } 100 | return badChecksum; 101 | } 102 | 103 | // Translate the state value into an ASCII state name 104 | const char *um980GetStateName(SEMP_PARSE_STATE *parse) 105 | { 106 | const char *name; 107 | 108 | do 109 | { 110 | name = sempNmeaGetStateName(parse); 111 | if (name) 112 | break; 113 | name = sempRtcmGetStateName(parse); 114 | if (name) 115 | break; 116 | name = sempUnicoreBinaryGetStateName(parse); 117 | if (name) 118 | break; 119 | name = sempUnicoreHashGetStateName(parse); 120 | if (name) 121 | break; 122 | name = sempGetStateName(parse); 123 | } while (0); 124 | return name; 125 | } 126 | 127 | // Disable debug output from the parser 128 | void UM980::disableParserDebug() 129 | { 130 | sempDisableDebugOutput(_sempParse); 131 | } 132 | 133 | // Enable debug output from the parser 134 | void UM980::enableParserDebug(Print *print) 135 | { 136 | sempEnableDebugOutput(_sempParse, print); 137 | } 138 | 139 | // Disable debug output from the parser 140 | void UM980::disableParserErrors() 141 | { 142 | sempDisableDebugOutput(_sempParse); 143 | } 144 | 145 | // Enable debug output from the parser 146 | void UM980::enableParserErrors(Print *print) 147 | { 148 | sempEnableErrorOutput(_sempParse, print); 149 | } 150 | 151 | // Print the UM980 parser configuration 152 | void UM980::printParserConfiguration(Print *print) 153 | { 154 | sempPrintParserConfiguration(_sempParse, print); 155 | } 156 | 157 | //---------------------------------------- 158 | // UM980 support routines 159 | //---------------------------------------- 160 | 161 | bool UM980::begin(HardwareSerial &serialPort, Print *parserDebug, Print *parserError) 162 | { 163 | ptrUM980 = this; 164 | _hwSerialPort = &serialPort; 165 | 166 | // Initialize the parser 167 | _sempParse = 168 | sempBeginParser(parserTable, parserCount, parserNames, parserNameCount, 0, BUFFER_LENGTH, um980ProcessMessage, 169 | "SFE_Unicore_GNSS_Library", parserError, parserDebug, badNmeaChecksum); 170 | if (!_sempParse) 171 | { 172 | debugPrintf("Unicore Lib: Failed to initialize the parser!"); 173 | return false; 174 | } 175 | 176 | // We assume the user has started the serial port with proper pins and baud rate prior to calling begin() 177 | if (isConnected() == false) 178 | { 179 | sempStopParser(&_sempParse); 180 | return false; 181 | } 182 | return (true); 183 | } 184 | 185 | // Query the device with 'MODE', expect OK response 186 | // Device may be booting and outputting other messages (ie, $devicename,COM3*65) 187 | // Try a few times 188 | bool UM980::isConnected() 189 | { 190 | for (int x = 0; x < 3; x++) 191 | { 192 | disableOutput(); // Tell unit to stop transmitting 193 | 194 | // Wait until serial stops coming in 195 | uint16_t maxTime = 500; 196 | unsigned long startTime = millis(); 197 | while (1) 198 | { 199 | delay(50); 200 | 201 | if (serialAvailable() == 0) 202 | break; 203 | while (serialAvailable()) 204 | serialRead(); 205 | 206 | if (millis() - startTime > maxTime) 207 | return (false); 208 | } 209 | 210 | if (sendQuery("MODE") == UM980_RESULT_OK) 211 | return (true); 212 | debugPrintf("UM980 failed to connect. Trying again."); 213 | delay(500); 214 | } 215 | return (false); 216 | } 217 | 218 | // If another task outside of this library is accessing the same Serial hardware, it can 219 | // check to see if this library currently needs exclusive read/write access for a short period. 220 | // If isBlocking is true, external consumers should not read/write to the Serial hardware 221 | bool UM980::isBlocking() 222 | { 223 | return (unicoreLibrarySemaphoreBlock); 224 | } 225 | 226 | // Calling this function with nothing sets the debug port to Serial 227 | // You can also call it with other streams like Serial1, SerialUSB, etc. 228 | void UM980::enableDebugging(Print &debugPort) 229 | { 230 | _debugPort = &debugPort; 231 | } 232 | void UM980::disableDebugging() 233 | { 234 | _debugPort = nullptr; 235 | } 236 | 237 | // Check for new data until there is no more 238 | bool UM980::update() 239 | { 240 | bool newData = false; 241 | 242 | unicoreLibrarySemaphoreBlock = true; // Allow external tasks to control serial hardware 243 | 244 | while (serialAvailable()) 245 | newData = updateOnce(); 246 | 247 | unicoreLibrarySemaphoreBlock = false; // Allow external tasks to control serial hardware 248 | 249 | return (newData); 250 | } 251 | 252 | // Enable the display of parser transitions 253 | void UM980::enablePrintParserTransitions() 254 | { 255 | _printParserTransitions = true; 256 | } 257 | 258 | // Checks for new data once 259 | // Used during sendString and sendQuery 260 | // um980ProcessMessage() is called once the parser completes on a line 261 | bool UM980::updateOnce() 262 | { 263 | const char *endName; 264 | const char *startName = nullptr; 265 | SEMP_PARSE_ROUTINE startState; 266 | 267 | if (serialAvailable()) 268 | { 269 | uint8_t incoming = serialRead(); 270 | 271 | // Get the current state and state name 272 | if (_printParserTransitions) 273 | { 274 | startState = _sempParse->state; 275 | startName = um980GetStateName(_sempParse); 276 | } 277 | 278 | // Update the parser state based on the incoming byte 279 | sempParseNextByte(_sempParse, incoming); 280 | 281 | // Get the current state name 282 | if (_printParserTransitions) 283 | { 284 | endName = um980GetStateName(_sempParse); 285 | 286 | // Display the parser state transition 287 | debugPrintf("Unicore Lib: 0x%02x (%c), crc: 0x%08x, state: %s --> %s", incoming, 288 | ((incoming >= ' ') && (incoming < 0x7f)) ? incoming : '.', _sempParse->crc, startName, endName); 289 | } 290 | return (true); 291 | } 292 | 293 | (void)startState; // Fix pesky warning-as-error 294 | 295 | return (false); 296 | } 297 | 298 | // Display the contents of a buffer 299 | void UM980::dumpBuffer(const uint8_t *buffer, uint16_t length) 300 | { 301 | int bytes; 302 | const uint8_t *end; 303 | int index; 304 | char line[128]; 305 | uint16_t offset; 306 | 307 | end = &buffer[length]; 308 | offset = 0; 309 | while (buffer < end) 310 | { 311 | // Determine the number of bytes to display on the line 312 | bytes = end - buffer; 313 | if (bytes > (16 - (offset & 0xf))) 314 | bytes = 16 - (offset & 0xf); 315 | 316 | // Display the offset 317 | sprintf(line, "0x%08lx: ", (long unsigned int)offset); 318 | 319 | // Skip leading bytes 320 | for (index = 0; index < (offset & 0xf); index++) 321 | sprintf(&line[strlen(line)], " "); 322 | 323 | // Display the data bytes 324 | for (index = 0; index < bytes; index++) 325 | sprintf(&line[strlen(line)], "%02x ", buffer[index]); 326 | 327 | // Separate the data bytes from the ASCII 328 | for (; index < (16 - (offset & 0xf)); index++) 329 | sprintf(&line[strlen(line)], " "); 330 | sprintf(&line[strlen(line)], " "); 331 | 332 | // Skip leading bytes 333 | for (index = 0; index < (offset & 0xf); index++) 334 | sprintf(&line[strlen(line)], " "); 335 | 336 | // Display the ASCII values 337 | for (index = 0; index < bytes; index++) 338 | sprintf(&line[strlen(line)], "%c", 339 | ((buffer[index] < ' ') || (buffer[index] >= 0x7f)) ? '.' : buffer[index]); 340 | debugPrintf("%s", line); 341 | 342 | // Set the next line of data 343 | buffer += bytes; 344 | offset += bytes; 345 | } 346 | } 347 | 348 | // Enable the display of received messages 349 | void UM980::enablePrintRxMessages() 350 | { 351 | _printRxMessages = true; 352 | } 353 | 354 | // Disable the display of received messages 355 | void UM980::disablePrintRxMessages() 356 | { 357 | _printRxMessages = false; 358 | } 359 | 360 | // Enable the hex dump of received messages 361 | void UM980::enableRxMessageDump() 362 | { 363 | _dumpRxMessages = true; 364 | } 365 | 366 | // Disable the hex dump of received messages 367 | void UM980::disableRxMessageDump() 368 | { 369 | _dumpRxMessages = false; 370 | } 371 | 372 | // Call back from within parser, for end of message 373 | // Process a complete message incoming from parser 374 | void um980ProcessMessage(SEMP_PARSE_STATE *parse, uint16_t type) 375 | { 376 | SEMP_SCRATCH_PAD *scratchPad = (SEMP_SCRATCH_PAD *)parse->scratchPad; 377 | 378 | if (ptrUM980->_printRxMessages) 379 | { 380 | // Display the raw message 381 | ptrUM980->debugPrintf(""); 382 | switch (type) 383 | { 384 | case UM980_NMEA_PARSER_INDEX: 385 | ptrUM980->debugPrintf("Unicore Lib: Valid NMEA Sentence: %s, 0x%04x (%d) bytes", 386 | sempNmeaGetSentenceName(parse), parse->length, parse->length); 387 | break; 388 | 389 | case UM980_UNICORE_HASH_PARSER_INDEX: 390 | ptrUM980->debugPrintf("Unicore Lib: Valid Unicore Hash (#) Sentence: %s, 0x%04x (%d) bytes", 391 | sempUnicoreHashGetSentenceName(parse), parse->length, parse->length); 392 | break; 393 | 394 | case UM980_RTCM_PARSER_INDEX: 395 | ptrUM980->debugPrintf("Unicore Lib: Valid RTCM message: 0x%04x (%d) bytes", parse->length, parse->length); 396 | break; 397 | 398 | case UM980_UNICORE_BINARY_PARSER_INDEX: 399 | ptrUM980->debugPrintf("Unicore Lib: Valid Unicore message: 0x%04x (%d) bytes", parse->length, 400 | parse->length); 401 | break; 402 | } 403 | } 404 | 405 | // Dump the contents of the parsed messages 406 | if (ptrUM980->_dumpRxMessages) 407 | ptrUM980->dumpBuffer(parse->buffer, parse->length); 408 | 409 | // Process the message 410 | switch (type) 411 | { 412 | case UM980_UNICORE_BINARY_PARSER_INDEX: 413 | ptrUM980->unicoreHandler(parse->buffer, parse->length); 414 | break; 415 | 416 | case UM980_RTCM_PARSER_INDEX: 417 | break; 418 | 419 | case UM980_UNICORE_HASH_PARSER_INDEX: 420 | // Does this response contain the command we are looking for? 421 | if (strcasecmp((char *)scratchPad->unicoreHash.sentenceName, ptrUM980->commandName) == 0) // Found 422 | { 423 | ptrUM980->debugPrintf("Unicore Lib: Query response: %s", parse->buffer); 424 | ptrUM980->commandResponse = UM980_RESULT_RESPONSE_COMMAND_OK; 425 | } 426 | break; 427 | 428 | case UM980_NMEA_PARSER_INDEX: 429 | 430 | // Is this a NMEA response or command response? 431 | 432 | if (strcasecmp((char *)scratchPad->nmea.sentenceName, "command") != 0 && 433 | strcasecmp((char *)scratchPad->nmea.sentenceName, "MASK") != 0 && 434 | strcasecmp((char *)scratchPad->nmea.sentenceName, "CONFIG") != 0) 435 | { 436 | // command, MASK, CONFIG not found 437 | 438 | if (strcasecmp((char *)scratchPad->nmea.sentenceName, "GNGGA") == 0) 439 | { 440 | ptrUM980->debugPrintf("um980ProcessMessage GNGGA"); 441 | } 442 | 443 | // Unknown response, ignore this message 444 | ptrUM980->debugPrintf("Unicore Lib: Message ignored: %s", parse->buffer); 445 | } 446 | else 447 | { 448 | // Does this response contain the command we are looking for? 449 | // It may be anywhere in the response: 450 | // $command,MODE,response: OK*5D 451 | char *responsePointer = strcasestr((char *)parse->buffer, ptrUM980->commandName); 452 | if (responsePointer != nullptr) // Found 453 | { 454 | // Display the command response 455 | ptrUM980->debugPrintf("Unicore Lib: Known command response: %s", parse->buffer); 456 | 457 | // Check to see if we got a command response 458 | responsePointer = strcasestr((char *)parse->buffer, "OK"); 459 | if (responsePointer != nullptr) // Found 460 | { 461 | ptrUM980->commandResponse = UM980_RESULT_RESPONSE_COMMAND_OK; 462 | return; 463 | } 464 | 465 | responsePointer = strcasestr((char *)parse->buffer, "PARSING"); 466 | if (responsePointer != nullptr) // Found 467 | { 468 | ptrUM980->debugPrintf("Unicore Lib: Error response: %s", parse->buffer); 469 | ptrUM980->commandResponse = UM980_RESULT_RESPONSE_COMMAND_ERROR; 470 | return; 471 | } 472 | 473 | responsePointer = strcasestr((char *)parse->buffer, "CONFIG"); 474 | if (responsePointer != nullptr) // Found 475 | { 476 | ptrUM980->debugPrintf("CONFIG response: %s", parse->buffer); 477 | ptrUM980->configHandler(parse->buffer, parse->length); 478 | ptrUM980->commandResponse = UM980_RESULT_RESPONSE_COMMAND_CONFIG; 479 | return; 480 | } 481 | } 482 | else 483 | { 484 | // Display the command response 485 | ptrUM980->debugPrintf("Unicore Lib: Unknown command response: %s", parse->buffer); 486 | ptrUM980->debugPrintf("Unicore Lib: Looking for command: %s", ptrUM980->commandName); 487 | } 488 | } 489 | break; 490 | } 491 | } 492 | 493 | // Mode commands 494 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 495 | 496 | // Directly set a mode: setMode("ROVER"); 497 | bool UM980::setMode(const char *modeType) 498 | { 499 | char command[50]; 500 | snprintf(command, sizeof(command), "MODE %s", modeType); 501 | 502 | return (sendCommand(command)); 503 | } 504 | 505 | // Directly set a base mode: setModeBase("40.09029479 -105.18505761 1560.089") 506 | bool UM980::setModeBase(const char *baseType) 507 | { 508 | char command[50]; 509 | snprintf(command, sizeof(command), "BASE %s", baseType); 510 | 511 | return (setMode(command)); 512 | } 513 | 514 | // Start base mode with given coordinates 515 | bool UM980::setModeBaseGeodetic(double latitude, double longitude, double altitude) 516 | { 517 | char command[50]; 518 | snprintf(command, sizeof(command), "%0.11f %0.11f %0.6f", latitude, longitude, altitude); 519 | 520 | return (setModeBase(command)); 521 | } 522 | 523 | // Start base mode with given coordinates 524 | bool UM980::setModeBaseECEF(double coordinateX, double coordinateY, double coordinateZ) 525 | { 526 | char command[50]; 527 | snprintf(command, sizeof(command), "%0.4f %0.4f %0.4f", coordinateX, coordinateY, coordinateZ); 528 | 529 | return (setModeBase(command)); 530 | } 531 | 532 | // Start base mode using self-optimization (similar to u-blox's Survey-In method) 533 | bool UM980::setModeBaseAverage() 534 | { 535 | return (setModeBaseAverage(60)); 536 | } 537 | 538 | bool UM980::setModeBaseAverage(uint16_t averageTime) 539 | { 540 | char command[50]; 541 | snprintf(command, sizeof(command), "TIME %d", averageTime); 542 | 543 | return (setModeBase(command)); 544 | } 545 | 546 | // Start rover mode: setModeRover("SURVEY") 547 | bool UM980::setModeRover(const char *roverType) 548 | { 549 | char command[50]; 550 | snprintf(command, sizeof(command), "ROVER %s", roverType); 551 | 552 | return (setMode(command)); 553 | } 554 | bool UM980::setModeRoverSurvey() 555 | { 556 | return (setModeRover("SURVEY")); 557 | } 558 | bool UM980::setModeRoverUAV() 559 | { 560 | return (setModeRover("UAV")); 561 | } 562 | bool UM980::setModeRoverAutomotive() 563 | { 564 | return (setModeRover("AUTOMOTIVE")); 565 | } 566 | bool UM980::setModeRoverMow() 567 | { 568 | return (setModeRover("SURVEY MOW")); // This fails for unknown reasons. Might be build7923 required, might not 569 | // be supported on UM980. 570 | } 571 | 572 | // Config commands 573 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 574 | 575 | // Configure a given COM port to a given baud 576 | // Supported baud rates: 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 577 | bool UM980::setPortBaudrate(const char *comName, unsigned long newBaud) 578 | { 579 | char command[50]; 580 | snprintf(command, sizeof(command), "CONFIG %s %ld", comName, newBaud); 581 | 582 | return (sendCommand(command)); 583 | } 584 | 585 | // Sets the baud rate of the port we are communicating on 586 | // Supported baud rates: 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 587 | bool UM980::setBaudrate(unsigned long newBaud) 588 | { 589 | char command[50]; 590 | snprintf(command, sizeof(command), "CONFIG %ld", newBaud); 591 | 592 | return (sendCommand(command)); 593 | } 594 | 595 | // Enable Pulse Per Second signal with various settings 596 | bool UM980::enablePPS(uint32_t widthMicroseconds, uint16_t periodMilliseconds, bool positivePolarity, int16_t rfDelay, 597 | int16_t userDelay) 598 | { 599 | char polarity[] = "POSITIVE"; 600 | if (positivePolarity == false) 601 | strncpy(polarity, "NEGATIVE", sizeof(polarity)); 602 | 603 | char command[50]; 604 | snprintf(command, sizeof(command), "ENABLE GPS %s %ld %d %d %d", polarity, widthMicroseconds, periodMilliseconds, 605 | rfDelay, userDelay); 606 | 607 | return (configurePPS(command)); 608 | } 609 | 610 | // Disable the PPS signal 611 | bool UM980::disablePPS() 612 | { 613 | return (configurePPS("DISABLE")); 614 | } 615 | 616 | bool UM980::configurePPS(const char *configString) 617 | { 618 | char command[50]; 619 | snprintf(command, sizeof(command), "CONFIG PPS %s", configString); 620 | 621 | return (sendCommand(command)); 622 | } 623 | 624 | // Mask commands 625 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 626 | 627 | // Available constellations: GPS, BDS, GLO, GAL, QZSS, IRNSS 628 | 629 | // Enable a given constallation 630 | // Returns true if successful 631 | bool UM980::enableConstellation(const char *constellationName) 632 | { 633 | char command[50]; 634 | snprintf(command, sizeof(command), "%s", constellationName); 635 | 636 | return (enableSystem(command)); 637 | } 638 | bool UM980::disableConstellation(const char *constellationName) 639 | { 640 | char command[50]; 641 | snprintf(command, sizeof(command), "%s", constellationName); 642 | 643 | return (disableSystem(command)); 644 | } 645 | 646 | // Ignore satellites below a given elevation for a given constellation 647 | bool UM980::setElevationAngle(int16_t elevationDegrees, const char *constellationName) 648 | { 649 | char command[50]; 650 | snprintf(command, sizeof(command), "%d %s", elevationDegrees, constellationName); 651 | 652 | return (disableSystem(command)); // Use MASK to set elevation angle 653 | } 654 | 655 | // Ignore satellites below a given elevation 656 | bool UM980::setElevationAngle(int16_t elevationDegrees) 657 | { 658 | char command[50]; 659 | snprintf(command, sizeof(command), "%d", elevationDegrees); 660 | 661 | return (disableSystem(command)); // Use MASK to set elevation angle 662 | } 663 | 664 | // Ignore satellites below certain CN0 value 665 | // C/N0, limits the observation data output of OBSV messages 666 | bool UM980::setMinCNO(uint8_t dBHz) 667 | { 668 | char command[50]; 669 | snprintf(command, sizeof(command), "CN0 %d", dBHz); 670 | 671 | return (disableSystem(command)); // Use MASK to set CN0 value 672 | } 673 | 674 | // Enable a given frequency name 675 | // See table 5-4 for list of frequency names 676 | bool UM980::enableFrequency(const char *frequencyName) 677 | { 678 | char command[50]; 679 | snprintf(command, sizeof(command), "%s", frequencyName); 680 | 681 | return (enableSystem(command)); 682 | } 683 | 684 | // Disable a given frequency name 685 | bool UM980::disableFrequency(const char *frequencyName) 686 | { 687 | char command[50]; 688 | snprintf(command, sizeof(command), "%s", frequencyName); 689 | 690 | return (disableSystem(command)); 691 | } 692 | 693 | // Called mask (disable) and unmask (enable), this is how to ignore certain constellations, or signal/frequencies, 694 | // or satellite elevations Returns true if successful 695 | bool UM980::enableSystem(const char *systemName) 696 | { 697 | char command[50]; 698 | snprintf(command, sizeof(command), "UNMASK %s", systemName); 699 | 700 | return (sendCommand(command)); 701 | } 702 | 703 | bool UM980::disableSystem(const char *systemName) 704 | { 705 | char command[50]; 706 | snprintf(command, sizeof(command), "MASK %s", systemName); 707 | 708 | return (sendCommand(command)); 709 | } 710 | 711 | // Data Output commands 712 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 713 | 714 | // Set the output rate of a given message on a given COM port/Use 715 | // 1, 0.5, 0.2, 0.1 corresponds to 1Hz, 2Hz, 5Hz, 10Hz respectively. 716 | // Ex: GPGGA 0.5 <- 2 times per second 717 | // Returns true if successful 718 | bool UM980::setNMEAPortMessage(const char *sentenceType, const char *comName, float outputRate) 719 | { 720 | char command[50]; 721 | if (outputRate == 0) 722 | snprintf(command, sizeof(command), "UNLOG %s %s", comName, sentenceType); 723 | else 724 | snprintf(command, sizeof(command), "%s %s %0.2f", sentenceType, comName, outputRate); 725 | 726 | return (sendCommand(command)); 727 | } 728 | 729 | // Set the output rate of a given message on the port we are communicating on 730 | // 1, 0.5, 0.2, 0.1 corresponds to 1Hz, 2Hz, 5Hz, 10Hz respectively. 731 | // Ex: GPGGA 0.5 <- 2 times per second 732 | // Returns true if successful 733 | bool UM980::setNMEAMessage(const char *sentenceType, float outputRate) 734 | { 735 | char command[50]; 736 | if (outputRate == 0) 737 | snprintf(command, sizeof(command), "UNLOG %s", sentenceType); 738 | else 739 | snprintf(command, sizeof(command), "%s %0.2f", sentenceType, outputRate); 740 | 741 | return (sendCommand(command)); 742 | } 743 | 744 | // Set the output rate of a given RTCM message on a given COM port/Use 745 | // 1, 0.5, 0.2, 0.1 corresponds to 1Hz, 2Hz, 5Hz, 10Hz respectively. 746 | // Ex: RTCM1005 0.5 <- 2 times per second 747 | // Returns true if successful 748 | bool UM980::setRTCMPortMessage(const char *sentenceType, const char *comName, float outputRate) 749 | { 750 | char command[50]; 751 | if (outputRate == 0) 752 | snprintf(command, sizeof(command), "UNLOG %s %s", comName, sentenceType); 753 | else 754 | snprintf(command, sizeof(command), "%s %s %0.2f", sentenceType, comName, outputRate); 755 | 756 | return (sendCommand(command)); 757 | } 758 | 759 | // Set the output rate of a given RTCM message on the port we are communicating on 760 | // Ex: RTCM1005 10 <- Once every ten seconds 761 | // Returns true if successful 762 | bool UM980::setRTCMMessage(const char *sentenceType, float outputRate) 763 | { 764 | char command[50]; 765 | if (outputRate == 0) 766 | snprintf(command, sizeof(command), "UNLOG %s", sentenceType); 767 | else 768 | snprintf(command, sizeof(command), "%s %0.2f", sentenceType, outputRate); 769 | 770 | return (sendCommand(command)); 771 | } 772 | 773 | // Other commands 774 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 775 | 776 | // Disables all messages on this port 777 | // Warning, each message has to be individually re-enabled 778 | bool UM980::disableOutput() 779 | { 780 | for (int x = 0; x < 5; x++) 781 | { 782 | if (sendCommand("UNLOG") == true) 783 | { 784 | stopAutoReports(); // Remove pointers so we will re-init next check 785 | return (true); 786 | } 787 | 788 | delay(10 * x); 789 | } 790 | 791 | return (false); 792 | } 793 | 794 | // Disable all messages on a given port 795 | bool UM980::disableOutputPort(const char *comName) 796 | { 797 | // We don't know if this is the COM port we are communicating on, so err on the side of caution. 798 | stopAutoReports(); // Remove pointers so we will re-init next check 799 | 800 | char command[50]; 801 | snprintf(command, sizeof(command), "UNLOG %s", comName); 802 | 803 | return (sendCommand(command)); 804 | } 805 | 806 | // We've issued an unlog, so the binary messages will no longer be coming in automatically 807 | // Turn off pointers so the next time a getLatitude() is issued, the associated messsage is reinit'd 808 | void UM980::stopAutoReports() 809 | { 810 | if (packetBESTNAV != nullptr) 811 | { 812 | delete packetBESTNAV; 813 | packetBESTNAV = nullptr; 814 | } 815 | if (packetBESTNAVXYZ != nullptr) 816 | { 817 | delete packetBESTNAVXYZ; 818 | packetBESTNAVXYZ = nullptr; 819 | } 820 | if (packetRECTIME != nullptr) 821 | { 822 | delete packetRECTIME; 823 | packetRECTIME = nullptr; 824 | } 825 | } 826 | 827 | // Clear saved configurations, satellite ephemerides, position information, and reset baud rate to 115200bps. 828 | bool UM980::factoryReset() 829 | { 830 | return (sendCommand("FRESET")); 831 | } 832 | 833 | // Resetting the receiver will clear the satellite ephemerides, position information, satellite 834 | // almanacs, ionosphere parameters and UTC parameters saved in the receiver. 835 | bool UM980::reset() 836 | { 837 | return (sendCommand("RESET")); 838 | } 839 | 840 | // Saves the current configuration into non-volatile memory (NVM), 841 | // including LOG messages (except those triggered by ONCE), port 842 | // configuration, etc. 843 | bool UM980::saveConfiguration(uint16_t maxWait) 844 | { 845 | return (sendCommand("SAVECONFIG", maxWait)); 846 | } 847 | 848 | // Abstraction of the serial interface 849 | // Useful if we ever need to support SoftwareSerial 850 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 851 | 852 | // Enable printfs to various endpoints 853 | // https://stackoverflow.com/questions/42131753/wrapper-for-printf 854 | void UM980::debugPrintf(const char *format, ...) 855 | { 856 | if (_debugPort == nullptr) 857 | return; 858 | 859 | va_list args; 860 | va_start(args, format); 861 | 862 | va_list args2; 863 | va_copy(args2, args); 864 | char buf[vsnprintf(nullptr, 0, format, args) + sizeof("\r\n")]; 865 | 866 | vsnprintf(buf, sizeof buf, format, args2); 867 | 868 | // Add CR+LF 869 | buf[sizeof(buf) - 3] = '\r'; 870 | buf[sizeof(buf) - 2] = '\n'; 871 | buf[sizeof(buf) - 1] = '\0'; 872 | 873 | _debugPort->write(buf, strlen(buf)); 874 | 875 | va_end(args); 876 | va_end(args2); 877 | } 878 | 879 | // Discards any characters sitting in RX buffer 880 | void UM980::clearBuffer() 881 | { 882 | while (serialAvailable()) 883 | serialRead(); 884 | } 885 | 886 | uint16_t UM980::serialAvailable() 887 | { 888 | if (_hwSerialPort != nullptr) 889 | { 890 | return (_hwSerialPort->available()); 891 | } 892 | return (0); 893 | } 894 | 895 | uint8_t UM980::serialRead() 896 | { 897 | if (_hwSerialPort != nullptr) 898 | { 899 | return (_hwSerialPort->read()); 900 | } 901 | return (0); 902 | } 903 | 904 | void UM980::serialPrintln(const char *command) 905 | { 906 | if (_hwSerialPort != nullptr) 907 | { 908 | _hwSerialPort->println(command); 909 | } 910 | } 911 | 912 | // Query and send functionality 913 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 914 | 915 | // Send a command string (ie 'MODE ROVER') to the UM980 916 | // Returns true if device reponded with OK to command 917 | bool UM980::sendCommand(const char *command, uint16_t maxWaitMs) 918 | { 919 | return (sendString(command, maxWaitMs) == UM980_RESULT_OK); 920 | } 921 | 922 | // Send a query string (ie 'MODE') to the UM980 923 | // Looks for a query response ('#') 924 | // Some commands like MASK or CONFIG have responses that begin with $ 925 | //'#' begins the responses to queries, ie 'MODE', ends with the result (ie MODE ROVER) 926 | // #MODE,97,GPS,FINE,2283,499142000,0,0,18,22;MODE BASE -1280206.5680 -4716804.4030 4086665.4840,*60 927 | 928 | //'$' begins the responses to commands, ie 'MODE ROVER', ends with OK 929 | // Contains reponse for caller 930 | Um980Result UM980::sendQuery(const char *command, uint16_t maxWaitMs) 931 | { 932 | Um980Result result; 933 | 934 | clearBuffer(); 935 | 936 | // Send command and check for OK response 937 | result = sendString(command, maxWaitMs); 938 | if (result != UM980_RESULT_OK) 939 | return (result); 940 | 941 | strncpy(commandName, command, sizeof(commandName)); 942 | commandResponse = UM980_RESULT_RESPONSE_COMMAND_WAITING; // Reset 943 | 944 | unicoreLibrarySemaphoreBlock = true; // Prevent external tasks from harvesting serial data 945 | 946 | // Feed the parser until we see a response to the command 947 | int wait = 0; 948 | while (1) 949 | { 950 | if (wait++ == maxWaitMs) 951 | { 952 | debugPrintf("Unicore Lib: Response timeout"); 953 | unicoreLibrarySemaphoreBlock = false; // Allow external tasks to control serial hardware 954 | return (UM980_RESULT_TIMEOUT_RESPONSE); 955 | } 956 | 957 | updateOnce(); // Will call um980ProcessMessage() 958 | 959 | if (commandResponse == UM980_RESULT_RESPONSE_COMMAND_OK) 960 | { 961 | debugPrintf("Unicore Lib: Response received"); 962 | break; 963 | } 964 | 965 | if (commandResponse == UM980_RESULT_RESPONSE_COMMAND_ERROR) 966 | { 967 | debugPrintf("Unicore Lib: Query failure"); 968 | unicoreLibrarySemaphoreBlock = false; // Allow external tasks to control serial hardware 969 | return (UM980_RESULT_RESPONSE_COMMAND_ERROR); 970 | } 971 | 972 | delay(1); 973 | } 974 | 975 | unicoreLibrarySemaphoreBlock = false; // Allow external tasks to control serial hardware 976 | 977 | return (UM980_RESULT_OK); 978 | } 979 | 980 | // Send a string to the UM980 981 | // Looks for a command response ('#' or '$') 982 | //'#' begins the responses to queries, ie 'MODE', ends with the result (ie MODE ROVER) 983 | //'$' begins the responses to commands, ie 'MODE ROVER', ends with OK 984 | //$command,badResponse,response: PARSING FAILD NO MATCHING FUNC BADRESPONSE*40 985 | // Returns UM980 result 986 | Um980Result UM980::sendString(const char *command, uint16_t maxWaitMs) 987 | { 988 | clearBuffer(); 989 | 990 | debugPrintf("Unicore Lib: Sending command %s", command); 991 | strncpy(commandName, command, sizeof(commandName)); // Copy to class so that parsers can see it 992 | commandResponse = UM980_RESULT_RESPONSE_COMMAND_WAITING; // Reset 993 | 994 | unicoreLibrarySemaphoreBlock = true; // Prevent external tasks from harvesting serial data 995 | 996 | serialPrintln(command); 997 | 998 | // Feed the parser until we see a response to the command 999 | int wait = 0; 1000 | while (1) 1001 | { 1002 | if (wait++ == maxWaitMs) 1003 | { 1004 | debugPrintf("Unicore Lib: Command timeout"); 1005 | unicoreLibrarySemaphoreBlock = false; // Allow external tasks to control serial hardware 1006 | return (UM980_RESULT_TIMEOUT_RESPONSE); 1007 | } 1008 | 1009 | updateOnce(); // Will call um980ProcessMessage() 1010 | 1011 | if (commandResponse == UM980_RESULT_RESPONSE_COMMAND_OK) 1012 | { 1013 | debugPrintf("Unicore Lib: Command success"); 1014 | break; 1015 | } 1016 | 1017 | if (commandResponse == UM980_RESULT_RESPONSE_COMMAND_ERROR) 1018 | { 1019 | debugPrintf("Unicore Lib: Command error"); 1020 | unicoreLibrarySemaphoreBlock = false; // Allow external tasks to control serial hardware 1021 | return (UM980_RESULT_RESPONSE_COMMAND_ERROR); 1022 | } 1023 | 1024 | delay(1); 1025 | } 1026 | 1027 | unicoreLibrarySemaphoreBlock = false; // Allow external tasks to control serial hardware 1028 | 1029 | return (UM980_RESULT_OK); 1030 | } 1031 | 1032 | // Main Unicore handler and RAM inits 1033 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 1034 | 1035 | #define CHECK_POINTER_BOOL(packetPointer, initPointer) \ 1036 | { \ 1037 | if (packetPointer == nullptr) \ 1038 | initPointer(); \ 1039 | if (packetPointer == nullptr) \ 1040 | return false; \ 1041 | } 1042 | 1043 | #define CHECK_POINTER_VOID(packetPointer, initPointer) \ 1044 | { \ 1045 | if (packetPointer == nullptr) \ 1046 | initPointer(); \ 1047 | if (packetPointer == nullptr) \ 1048 | return; \ 1049 | } 1050 | 1051 | #define CHECK_POINTER_CHAR(packetPointer, initPointer) \ 1052 | { \ 1053 | if (packetPointer == nullptr) \ 1054 | initPointer(); \ 1055 | if (packetPointer == nullptr) \ 1056 | return ((char *)"Error"); \ 1057 | } 1058 | 1059 | // Cracks a given binary message into the applicable container 1060 | void UM980::unicoreHandler(uint8_t *response, uint16_t length) 1061 | { 1062 | uint16_t messageID = ((uint16_t)response[offsetHeaderMessageId + 1] << 8) | response[offsetHeaderMessageId]; 1063 | 1064 | if (messageID == messageIdBestnav) 1065 | { 1066 | debugPrintf("BestNav Handler"); 1067 | CHECK_POINTER_VOID(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1068 | 1069 | lastUpdateGeodetic = millis(); // Update stale marker 1070 | 1071 | uint8_t *data = &response[um980HeaderLength]; // Point at the start of the data fields 1072 | 1073 | // Move data into given containers 1074 | 1075 | // 0 = Solution computed, 1 = Insufficient observation, 3 = No convergence, 4 = Covariance trace 1076 | memcpy(&packetBESTNAV->data.solutionStatus, &data[offsetBestnavPsolStatus], sizeof(uint8_t)); 1077 | 1078 | // 0 = None, 1 = FixedPos, 8 = DopplerVelocity, 16 = Single, ... 1079 | memcpy(&packetBESTNAV->data.positionType, &data[offsetBestnavPosType], sizeof(uint8_t)); 1080 | memcpy(&packetBESTNAV->data.velocityType, &data[offsetBestnavVelType], sizeof(uint8_t)); 1081 | 1082 | memcpy(&packetBESTNAV->data.latitude, &data[offsetBestnavLat], sizeof(double)); 1083 | memcpy(&packetBESTNAV->data.longitude, &data[offsetBestnavLon], sizeof(double)); 1084 | memcpy(&packetBESTNAV->data.altitude, &data[offsetBestnavHgt], sizeof(double)); 1085 | memcpy(&packetBESTNAV->data.horizontalSpeed, &data[offsetBestnavHorSpd], sizeof(double)); 1086 | memcpy(&packetBESTNAV->data.verticalSpeed, &data[offsetBestnavVertSpd], sizeof(double)); 1087 | memcpy(&packetBESTNAV->data.trackGround, &data[offsetBestnavTrkGnd], sizeof(double)); 1088 | 1089 | memcpy(&packetBESTNAV->data.latitudeDeviation, &data[offsetBestnavLatDeviation], sizeof(float)); 1090 | memcpy(&packetBESTNAV->data.longitudeDeviation, &data[offsetBestnavLonDeviation], sizeof(float)); 1091 | memcpy(&packetBESTNAV->data.heightDeviation, &data[offsetBestnavHgtDeviation], sizeof(float)); 1092 | 1093 | memcpy(&packetBESTNAV->data.horizontalSpeedDeviation, &data[offsetBestnavHorspdStd], sizeof(float)); 1094 | memcpy(&packetBESTNAV->data.verticalSpeedDeviation, &data[offsetBestnavVerspdStd], sizeof(float)); 1095 | 1096 | memcpy(&packetBESTNAV->data.satellitesTracked, &data[offsetBestnavSatsTracked], sizeof(uint8_t)); 1097 | memcpy(&packetBESTNAV->data.satellitesUsed, &data[offsetBestnavSatsUsed], sizeof(uint8_t)); 1098 | 1099 | uint8_t extSolStat; 1100 | memcpy(&extSolStat, &data[offsetBestnavExtSolStat], sizeof(uint8_t)); 1101 | packetBESTNAV->data.rtkSolution = extSolStat & 0x01; // 0 = unchecked, 1 = checked 1102 | packetBESTNAV->data.pseudorangeCorrection = (extSolStat >> 1) & 0b111; // Limit to three bits 1103 | } 1104 | else if (messageID == messageIdRectime) 1105 | { 1106 | debugPrintf("RecTime Handler"); 1107 | CHECK_POINTER_VOID(packetRECTIME, initRectime); // Check that RAM has been allocated 1108 | 1109 | lastUpdateDateTime = millis(); 1110 | 1111 | uint8_t *data = &response[um980HeaderLength]; // Point at the start of the data fields 1112 | 1113 | // Move data into given containers 1114 | memcpy(&packetRECTIME->data.timeStatus, &data[offsetRectimeClockStatus], sizeof(uint8_t)); 1115 | memcpy(&packetRECTIME->data.timeOffset, &data[offsetRectimeOffset], sizeof(double)); 1116 | memcpy(&packetRECTIME->data.timeDeviation, &data[offsetRectimeOffsetStd], sizeof(double)); 1117 | memcpy(&packetRECTIME->data.year, &data[offsetRectimeUtcYear], sizeof(uint16_t)); 1118 | memcpy(&packetRECTIME->data.month, &data[offsetRectimeUtcMonth], sizeof(uint8_t)); 1119 | memcpy(&packetRECTIME->data.day, &data[offsetRectimeUtcDay], sizeof(uint8_t)); 1120 | memcpy(&packetRECTIME->data.hour, &data[offsetRectimeUtcHour], sizeof(uint8_t)); 1121 | memcpy(&packetRECTIME->data.minute, &data[offsetRectimeUtcMinute], sizeof(uint8_t)); 1122 | 1123 | memcpy(&packetRECTIME->data.millisecond, &data[offsetRectimeUtcMillisecond], sizeof(uint32_t)); 1124 | packetRECTIME->data.second = round(packetRECTIME->data.millisecond / 1000.0); 1125 | packetRECTIME->data.millisecond -= (packetRECTIME->data.second * 1000); // Remove seconds from milliseconds 1126 | 1127 | memcpy(&packetRECTIME->data.dateStatus, &data[offsetRectimeUtcStatus], sizeof(uint8_t)); 1128 | } 1129 | else if (messageID == messageIdBestnavXyz) 1130 | { 1131 | debugPrintf("BestNavXyz Handler"); 1132 | CHECK_POINTER_VOID(packetBESTNAVXYZ, initBestnavXyz); // Check that RAM has been allocated 1133 | 1134 | lastUpdateEcef = millis(); // Update stale marker 1135 | 1136 | uint8_t *data = &response[um980HeaderLength]; // Point at the start of the data fields 1137 | 1138 | // Move data into given containers 1139 | memcpy(&packetBESTNAVXYZ->data.ecefX, &data[offsetBestnavXyzPX], sizeof(double)); 1140 | memcpy(&packetBESTNAVXYZ->data.ecefY, &data[offsetBestnavXyzPY], sizeof(double)); 1141 | memcpy(&packetBESTNAVXYZ->data.ecefZ, &data[offsetBestnavXyzPZ], sizeof(double)); 1142 | 1143 | memcpy(&packetBESTNAVXYZ->data.ecefXDeviation, &data[offsetBestnavXyzPXDeviation], sizeof(float)); 1144 | memcpy(&packetBESTNAVXYZ->data.ecefYDeviation, &data[offsetBestnavXyzPYDeviation], sizeof(float)); 1145 | memcpy(&packetBESTNAVXYZ->data.ecefZDeviation, &data[offsetBestnavXyzPZDeviation], sizeof(float)); 1146 | } 1147 | else if (messageID == messageIdVersion) 1148 | { 1149 | debugPrintf("Version Handler"); 1150 | CHECK_POINTER_VOID(packetVERSION, initVersion); // Check that RAM has been allocated 1151 | 1152 | lastUpdateVersion = millis(); // Update stale marker 1153 | 1154 | uint8_t *data = &response[um980HeaderLength]; // Point at the start of the data fields 1155 | 1156 | // Move data into given containers 1157 | memcpy(&packetVERSION->data.modelType, &data[offsetVersionModuleType], sizeof(packetVERSION->data.modelType)); 1158 | memcpy(&packetVERSION->data.swVersion, &data[offsetVersionFirmwareVersion], 1159 | sizeof(packetVERSION->data.swVersion)); 1160 | memcpy(&packetVERSION->data.efuseID, &data[offsetVersionEfuseID], sizeof(packetVERSION->data.efuseID)); 1161 | memcpy(&packetVERSION->data.compileTime, &data[offsetVersionCompTime], sizeof(packetVERSION->data.compileTime)); 1162 | } 1163 | else 1164 | { 1165 | // Is this a NMEA sentence? 1166 | if (response[0] == '$') 1167 | { 1168 | response[length] = '\0'; // Force terminator because strncasestr does not exist 1169 | 1170 | // The UM980 does not respond to binary requests when there is no GNSS reception. 1171 | // Block BestNavB, etc commands if there is no fix. 1172 | // Look for GNGGA NMEA then extract GNSS position status (spot 6). 1173 | // $GNGGA,181535.00,,,,,0,00,9999.0,,,,,,*43 1174 | char *responsePointer = strcasestr((char *)response, "GNGGA"); 1175 | if (responsePointer != nullptr) // Found 1176 | { 1177 | char gngga[100]; 1178 | strncpy(gngga, (const char *)response, length - 1); // Make copy before strtok 1179 | 1180 | debugPrintf("Unicore Lib: GNGGA message: %s\r\n", gngga); 1181 | 1182 | char *pt; 1183 | pt = strtok(gngga, ","); 1184 | int counter = 0; 1185 | while (pt != NULL) 1186 | { 1187 | int spotValue = atoi(pt); 1188 | if (counter++ == 6) 1189 | nmeaPositionStatus = spotValue; 1190 | pt = strtok(NULL, ","); 1191 | } 1192 | } 1193 | else 1194 | { 1195 | // Unhandled NMEA message 1196 | // debugPrintf("Unicore Lib: Unhandled NMEA sentence (%d bytes): %s\r\n", length, (char *)response); 1197 | } 1198 | } 1199 | else 1200 | { 1201 | debugPrintf("Unicore Lib: Unknown message id: %d\r\n", messageID); 1202 | } 1203 | } 1204 | } 1205 | 1206 | // Allocate RAM for packetVERSION and initialize it 1207 | bool UM980::initVersion() 1208 | { 1209 | packetVERSION = new UNICORE_VERSION_t; // Allocate RAM for the main struct 1210 | if (packetVERSION == nullptr) 1211 | { 1212 | debugPrintf("Pointer alloc fail"); 1213 | return (false); 1214 | } 1215 | // packetVERSION->callbackPointerPtr = nullptr; 1216 | // packetVERSION->callbackData = nullptr; 1217 | 1218 | // Send command for single query 1219 | if (sendCommand("VERSIONB") == false) 1220 | { 1221 | delete packetVERSION; 1222 | packetVERSION = nullptr; // Remove pointer so we will re-init next check 1223 | return (false); 1224 | } 1225 | 1226 | debugPrintf("VERSION started"); 1227 | 1228 | // Wait until response is received 1229 | lastUpdateVersion = 0; 1230 | uint16_t maxWait = 1000; // Wait for one response to come in 1231 | unsigned long startTime = millis(); 1232 | while (1) 1233 | { 1234 | update(); // Call parser 1235 | if (lastUpdateVersion > 0) 1236 | break; 1237 | if (millis() - startTime > maxWait) 1238 | { 1239 | debugPrintf("GNSS: Failed to get response from VERSION start"); 1240 | delete packetVERSION; 1241 | packetVERSION = nullptr; 1242 | return (false); 1243 | } 1244 | } 1245 | 1246 | return (true); 1247 | } 1248 | 1249 | // Allocate RAM for packetBESTNAV and initialize it 1250 | bool UM980::initBestnav(uint8_t rate) 1251 | { 1252 | if ((startBinaryBeforeFix == false) && (isNmeaFixed() == false)) 1253 | { 1254 | debugPrintf("Unicore Lib: BestNav no fix"); 1255 | return (false); 1256 | } 1257 | 1258 | packetBESTNAV = new UNICORE_BESTNAV_t; // Allocate RAM for the main struct 1259 | if (packetBESTNAV == nullptr) 1260 | { 1261 | debugPrintf("Pointer alloc fail"); 1262 | return (false); 1263 | } 1264 | // packetBESTNAV->callbackPointerPtr = nullptr; 1265 | // packetBESTNAV->callbackData = nullptr; 1266 | 1267 | // Start outputting BESTNAV in Binary on this COM port 1268 | char command[50]; 1269 | snprintf(command, sizeof(command), "BESTNAVB %d", rate); 1270 | if (sendCommand(command) == false) 1271 | { 1272 | delete packetBESTNAV; 1273 | packetBESTNAV = nullptr; // Remove pointer so we will re-init next check 1274 | return (false); 1275 | } 1276 | 1277 | debugPrintf("BestNav started"); 1278 | 1279 | // Wait until first report is available 1280 | lastUpdateGeodetic = 0; 1281 | uint16_t maxWait = (1000 / rate) + 100; // Wait for one response to come in 1282 | unsigned long startTime = millis(); 1283 | while (1) 1284 | { 1285 | update(); // Call parser 1286 | if (lastUpdateGeodetic > 0) 1287 | break; 1288 | if (millis() - startTime > maxWait) 1289 | { 1290 | debugPrintf("GNSS: Failed to get response from BestNav start"); 1291 | delete packetBESTNAV; 1292 | packetBESTNAV = nullptr; 1293 | return (false); 1294 | } 1295 | } 1296 | 1297 | return (true); 1298 | } 1299 | 1300 | // Allocate RAM for packetBESTNAVXYZ and initialize it 1301 | bool UM980::initBestnavXyz(uint8_t rate) 1302 | { 1303 | if ((startBinaryBeforeFix == false) && (isNmeaFixed() == false)) 1304 | { 1305 | debugPrintf("Unicore Lib: BestNavXyz no fix"); 1306 | return (false); 1307 | } 1308 | 1309 | packetBESTNAVXYZ = new UNICORE_BESTNAVXYZ_t; // Allocate RAM for the main struct 1310 | if (packetBESTNAVXYZ == nullptr) 1311 | { 1312 | debugPrintf("Pointer alloc fail"); 1313 | return (false); 1314 | } 1315 | // packetBESTNAVXYZ->callbackPointerPtr = nullptr; 1316 | // packetBESTNAVXYZ->callbackData = nullptr; 1317 | 1318 | // Start outputting BESTNAVXYZ in Binary on this COM port 1319 | char command[50]; 1320 | snprintf(command, sizeof(command), "BESTNAVXYZB %d", rate); 1321 | if (sendCommand(command) == false) 1322 | { 1323 | delete packetBESTNAVXYZ; 1324 | packetBESTNAVXYZ = nullptr; // Remove pointer so we will re-init next check 1325 | return (false); 1326 | } 1327 | 1328 | debugPrintf("BestNavXYZB started"); 1329 | 1330 | // Wait until first report is available 1331 | lastUpdateEcef = 0; 1332 | uint16_t maxWait = (1000 / rate) + 100; // Wait for one response to come in 1333 | unsigned long startTime = millis(); 1334 | while (1) 1335 | { 1336 | update(); // Call parser 1337 | if (lastUpdateEcef > 0) 1338 | break; 1339 | if (millis() - startTime > maxWait) 1340 | { 1341 | debugPrintf("GNSS: Failed to get response from BestNavXyz start"); 1342 | delete packetBESTNAVXYZ; 1343 | packetBESTNAVXYZ = nullptr; 1344 | return (false); 1345 | } 1346 | } 1347 | 1348 | return (true); 1349 | } 1350 | 1351 | // Allocate RAM for packetRECTIME and initialize it 1352 | bool UM980::initRectime(uint8_t rate) 1353 | { 1354 | if ((startBinaryBeforeFix == false) && (isNmeaFixed() == false)) 1355 | { 1356 | debugPrintf("Unicore Lib: RecTime no fix"); 1357 | return (false); 1358 | } 1359 | 1360 | packetRECTIME = new UNICORE_RECTIME_t; // Allocate RAM for the main struct 1361 | if (packetRECTIME == nullptr) 1362 | { 1363 | debugPrintf("Pointer alloc fail"); 1364 | return (false); 1365 | } 1366 | // packetRECTIME->callbackPointerPtr = nullptr; 1367 | // packetRECTIME->callbackData = nullptr; 1368 | 1369 | debugPrintf("RecTime started"); 1370 | 1371 | // Start outputting RECTIME in Binary on this COM port 1372 | char command[50]; 1373 | snprintf(command, sizeof(command), "RECTIMEB %d", rate); 1374 | if (sendCommand(command) == false) 1375 | { 1376 | delete packetRECTIME; 1377 | packetRECTIME = nullptr; // Remove pointer so we will re-init next check 1378 | return (false); 1379 | } 1380 | 1381 | debugPrintf("RecTimeB started"); 1382 | 1383 | // Wait until first report is available 1384 | lastUpdateDateTime = 0; 1385 | uint16_t maxWait = (1000 / rate) + 100; // Wait for one response to come in 1386 | unsigned long startTime = millis(); 1387 | while (1) 1388 | { 1389 | update(); // Call parser 1390 | if (lastUpdateDateTime > 0) 1391 | break; 1392 | if (millis() - startTime > maxWait) 1393 | { 1394 | debugPrintf("GNSS: Failed to get response from RecTime start"); 1395 | delete packetRECTIME; 1396 | packetRECTIME = nullptr; 1397 | return (false); 1398 | } 1399 | } 1400 | 1401 | return (true); 1402 | } 1403 | 1404 | // All the general gets and sets 1405 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 1406 | 1407 | double UM980::getLatitude(uint16_t maxWait) 1408 | { 1409 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1410 | return (packetBESTNAV->data.latitude); 1411 | } 1412 | 1413 | double UM980::getLongitude() 1414 | { 1415 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1416 | return (packetBESTNAV->data.longitude); 1417 | } 1418 | double UM980::getAltitude() 1419 | { 1420 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1421 | return (packetBESTNAV->data.altitude); 1422 | } 1423 | double UM980::getHorizontalSpeed() 1424 | { 1425 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1426 | return (packetBESTNAV->data.horizontalSpeed); 1427 | } 1428 | double UM980::getVerticalSpeed() 1429 | { 1430 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1431 | return (packetBESTNAV->data.verticalSpeed); 1432 | } 1433 | double UM980::getTrackGround() 1434 | { 1435 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1436 | return (packetBESTNAV->data.trackGround); 1437 | } 1438 | 1439 | float UM980::getLatitudeDeviation() 1440 | { 1441 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1442 | return (packetBESTNAV->data.latitudeDeviation); 1443 | } 1444 | float UM980::getLongitudeDeviation() 1445 | { 1446 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1447 | return (packetBESTNAV->data.longitudeDeviation); 1448 | } 1449 | float UM980::getAltitudeDeviation() 1450 | { 1451 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1452 | return (packetBESTNAV->data.heightDeviation); 1453 | } 1454 | float UM980::getHorizontalSpeedDeviation() 1455 | { 1456 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1457 | return (packetBESTNAV->data.horizontalSpeedDeviation); 1458 | } 1459 | float UM980::getVerticalSpeedDeviation() 1460 | { 1461 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1462 | return (packetBESTNAV->data.verticalSpeedDeviation); 1463 | } 1464 | 1465 | uint8_t UM980::getSIV() 1466 | { 1467 | return (getSatellitesTracked()); 1468 | } 1469 | uint8_t UM980::getSatellitesTracked() 1470 | { 1471 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1472 | return (packetBESTNAV->data.satellitesTracked); 1473 | } 1474 | uint8_t UM980::getSatellitesUsed() 1475 | { 1476 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1477 | return (packetBESTNAV->data.satellitesUsed); 1478 | } 1479 | 1480 | // 0 = Solution computed, 1 = Insufficient observation, 3 = No convergence, 4 = Covariance trace 1481 | uint8_t UM980::getSolutionStatus() 1482 | { 1483 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1484 | return (packetBESTNAV->data.solutionStatus); 1485 | } 1486 | 1487 | // 0 = no fix, 1 = dead reckoning only, 2 = 2D-fix, 3 = 3D-fix, 4 = GNSS + dead reckoning combined, 5 = time only 1488 | // fix 1489 | uint8_t UM980::getPositionType() 1490 | { 1491 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1492 | return (packetBESTNAV->data.positionType); 1493 | } 1494 | uint8_t UM980::getVelocityType() 1495 | { 1496 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1497 | return (packetBESTNAV->data.velocityType); 1498 | } 1499 | 1500 | uint8_t UM980::getRTKSolution() 1501 | { 1502 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1503 | return (packetBESTNAV->data.rtkSolution); 1504 | } 1505 | uint8_t UM980::getPseudorangeCorrection() 1506 | { 1507 | CHECK_POINTER_BOOL(packetBESTNAV, initBestnav); // Check that RAM has been allocated 1508 | return (packetBESTNAV->data.pseudorangeCorrection); 1509 | } 1510 | 1511 | // Return the number of millis since last update 1512 | uint32_t UM980::getFixAgeMilliseconds() 1513 | { 1514 | return (millis() - lastUpdateGeodetic); 1515 | } 1516 | 1517 | double UM980::getEcefX() 1518 | { 1519 | CHECK_POINTER_BOOL(packetBESTNAVXYZ, initBestnavXyz); // Check that RAM has been allocated 1520 | return (packetBESTNAVXYZ->data.ecefX); 1521 | } 1522 | double UM980::getEcefY() 1523 | { 1524 | CHECK_POINTER_BOOL(packetBESTNAVXYZ, initBestnavXyz); // Check that RAM has been allocated 1525 | return (packetBESTNAVXYZ->data.ecefY); 1526 | } 1527 | double UM980::getEcefZ() 1528 | { 1529 | CHECK_POINTER_BOOL(packetBESTNAVXYZ, initBestnavXyz); // Check that RAM has been allocated 1530 | return (packetBESTNAVXYZ->data.ecefZ); 1531 | } 1532 | float UM980::getEcefXDeviation() 1533 | { 1534 | CHECK_POINTER_BOOL(packetBESTNAVXYZ, initBestnavXyz); // Check that RAM has been allocated 1535 | return (packetBESTNAVXYZ->data.ecefXDeviation); 1536 | } 1537 | float UM980::getEcefYDeviation() 1538 | { 1539 | CHECK_POINTER_BOOL(packetBESTNAVXYZ, initBestnavXyz); // Check that RAM has been allocated 1540 | return (packetBESTNAVXYZ->data.ecefYDeviation); 1541 | } 1542 | float UM980::getEcefZDeviation() 1543 | { 1544 | CHECK_POINTER_BOOL(packetBESTNAVXYZ, initBestnavXyz); // Check that RAM has been allocated 1545 | return (packetBESTNAVXYZ->data.ecefZDeviation); 1546 | } 1547 | 1548 | uint16_t UM980::getYear() 1549 | { 1550 | CHECK_POINTER_BOOL(packetRECTIME, initRectime); // Check that RAM has been allocated 1551 | return (packetRECTIME->data.year); 1552 | } 1553 | uint8_t UM980::getMonth() 1554 | { 1555 | CHECK_POINTER_BOOL(packetRECTIME, initRectime); // Check that RAM has been allocated 1556 | return (packetRECTIME->data.month); 1557 | } 1558 | uint8_t UM980::getDay() 1559 | { 1560 | CHECK_POINTER_BOOL(packetRECTIME, initRectime); // Check that RAM has been allocated 1561 | return (packetRECTIME->data.day); 1562 | } 1563 | uint8_t UM980::getHour() 1564 | { 1565 | CHECK_POINTER_BOOL(packetRECTIME, initRectime); // Check that RAM has been allocated 1566 | return (packetRECTIME->data.hour); 1567 | } 1568 | uint8_t UM980::getMinute() 1569 | { 1570 | CHECK_POINTER_BOOL(packetRECTIME, initRectime); // Check that RAM has been allocated 1571 | return (packetRECTIME->data.minute); 1572 | } 1573 | uint8_t UM980::getSecond() 1574 | { 1575 | CHECK_POINTER_BOOL(packetRECTIME, initRectime); // Check that RAM has been allocated 1576 | return (packetRECTIME->data.second); 1577 | } 1578 | uint16_t UM980::getMillisecond() 1579 | { 1580 | CHECK_POINTER_BOOL(packetRECTIME, initRectime); // Check that RAM has been allocated 1581 | return (packetRECTIME->data.millisecond); 1582 | } 1583 | 1584 | uint8_t UM980::getTimeStatus() 1585 | { 1586 | CHECK_POINTER_BOOL(packetRECTIME, initRectime); // Check that RAM has been allocated 1587 | return (packetRECTIME->data.timeStatus); 1588 | } 1589 | uint8_t UM980::getDateStatus() 1590 | { 1591 | CHECK_POINTER_BOOL(packetRECTIME, initRectime); // Check that RAM has been allocated 1592 | return (packetRECTIME->data.dateStatus); 1593 | } 1594 | 1595 | // Receiver clock offset relative to GPS time, s. 1596 | // Positive indicates that the receiver clock is ahead of GPS time. To calculate the GPS time, use the formula 1597 | // below: GPS time = receiver time - clock offset 1598 | double UM980::getTimeOffset() 1599 | { 1600 | CHECK_POINTER_BOOL(packetRECTIME, initRectime); // Check that RAM has been allocated 1601 | return (packetRECTIME->data.timeOffset); 1602 | } 1603 | 1604 | // Standard deviation of the receiver clock offset, s. 1605 | double UM980::getTimeOffsetDeviation() 1606 | { 1607 | CHECK_POINTER_BOOL(packetRECTIME, initRectime); // Check that RAM has been allocated 1608 | return (packetRECTIME->data.timeDeviation); 1609 | } 1610 | 1611 | uint8_t UM980::getModelType() 1612 | { 1613 | CHECK_POINTER_BOOL(packetVERSION, initVersion); // Check that RAM has been allocated 1614 | return (packetVERSION->data.modelType); 1615 | } 1616 | char *UM980::getVersion() 1617 | { 1618 | CHECK_POINTER_CHAR(packetVERSION, initVersion); // Check that RAM has been allocated 1619 | return (packetVERSION->data.swVersion); 1620 | } 1621 | char *UM980::getID() 1622 | { 1623 | CHECK_POINTER_CHAR(packetVERSION, initVersion); // Check that RAM has been allocated 1624 | return (packetVERSION->data.efuseID); 1625 | } 1626 | char *UM980::getCompileTime() 1627 | { 1628 | CHECK_POINTER_CHAR(packetVERSION, initVersion); // Check that RAM has been allocated 1629 | return (packetVERSION->data.compileTime); 1630 | } 1631 | 1632 | // Returns pointer to terminated response. 1633 | //$command,VERSION,response: OK*04 1634 | // #VERSION,92,GPS,FINE,2289,167126600,0,0,18,155;UM980,R4.10Build7923,HRPT00-S10C-P,2310415000001-MD22B1224961040,ff3bd496fd7ca68b,2022/09/28*45d62771 1635 | char *UM980::getVersionFull(uint16_t maxWaitMs) 1636 | { 1637 | // Issue the version command 1638 | Um980Result result = sendQuery("VERSION"); 1639 | 1640 | // Process the response 1641 | if (result == UM980_RESULT_OK) 1642 | // Response sitting in buffer. Return pointer to buffer. 1643 | return ((char *)_sempParse->buffer); 1644 | else if (result == UM980_RESULT_TIMEOUT_RESPONSE) 1645 | return ((char *)"Timeout"); 1646 | else if (result == UM980_RESULT_RESPONSE_COMMAND_ERROR) 1647 | return ((char *)"Error1"); 1648 | return ((char *)"Error2"); 1649 | } 1650 | 1651 | // Cracks a given CONFIG response into settings 1652 | void UM980::configHandler(uint8_t *response, uint16_t length) 1653 | { 1654 | // We've received a response such as $CONFIG,COM3,CONFIG COM3 115200*23 1655 | // See if it is the one we want 1656 | char *responsePointer = strcasestr((char *)response, configStringToFind); 1657 | if (responsePointer != nullptr) // Found 1658 | { 1659 | configStringFound = true; 1660 | } 1661 | else 1662 | { 1663 | // This config response was not what we were looking for 1664 | } 1665 | } 1666 | 1667 | // Given a string such as "CONFIG COM3 115200", query the device's config settings 1668 | // If the given string is in the CONFIG response, return true 1669 | // Send a CONFIG command and see if a specific string exists in the responses 1670 | // $command,config,response: OK*54 1671 | // $CONFIG,ANTENNA,CONFIG ANTENNA POWERON*7A 1672 | // $CONFIG,NMEAVERSION,CONFIG NMEAVERSION V410*47 1673 | // $CONFIG,RTK,CONFIG RTK TIMEOUT 120*6C 1674 | // $CONFIG,RTK,CONFIG RTK RELIABILITY 3 1*76 1675 | // $CONFIG,PPP,CONFIG PPP TIMEOUT 120*6C 1676 | // $CONFIG,DGPS,CONFIG DGPS TIMEOUT 300*6C 1677 | // $CONFIG,RTCMB1CB2A,CONFIG RTCMB1CB2A ENABLE*25 1678 | // $CONFIG,ANTENNADELTAHEN,CONFIG ANTENNADELTAHEN 0.0000 0.0000 0.0000*3A 1679 | // $CONFIG,PPS,CONFIG PPS ENABLE GPS POSITIVE 500000 1000 0 0*6E 1680 | // $CONFIG,SIGNALGROUP,CONFIG SIGNALGROUP 2*16 1681 | // $CONFIG,ANTIJAM,CONFIG ANTIJAM AUTO*2B 1682 | // $CONFIG,AGNSS,CONFIG AGNSS DISABLE*70 1683 | // $CONFIG,BASEOBSFILTER,CONFIG BASEOBSFILTER DISABLE*70 1684 | // $CONFIG,COM1,CONFIG COM1 115200*23 1685 | // $CONFIG,COM2,CONFIG COM2 115200*23 1686 | // $CONFIG,COM3,CONFIG COM3 115200*23 1687 | bool UM980::isConfigurationPresent(const char *stringToFind, uint16_t maxWaitMs) 1688 | { 1689 | Um980Result result; 1690 | 1691 | clearBuffer(); 1692 | 1693 | // Send command and check for OK response 1694 | result = sendString("CONFIG", maxWaitMs); 1695 | if (result != UM980_RESULT_OK) 1696 | // return (result); 1697 | return (false); 1698 | 1699 | // Setup configStringToFind so configHandler() knows what to look for 1700 | strncpy(configStringToFind, stringToFind, sizeof(configStringToFind)); 1701 | 1702 | configStringFound = false; // configHandler() sets true if we find the intended string 1703 | 1704 | commandResponse = UM980_RESULT_RESPONSE_COMMAND_WAITING; // Reset 1705 | 1706 | unicoreLibrarySemaphoreBlock = true; // Prevent external tasks from harvesting serial data 1707 | 1708 | // Feed the parser until we see a response to the command 1709 | int wait = 0; 1710 | while (1) 1711 | { 1712 | if (wait++ == maxWaitMs) 1713 | { 1714 | debugPrintf("Unicore Lib: Response timeout"); 1715 | unicoreLibrarySemaphoreBlock = false; // Allow external tasks to control serial hardware 1716 | // return (UM980_RESULT_TIMEOUT_RESPONSE); 1717 | return (false); 1718 | } 1719 | 1720 | updateOnce(); // Will call um980ProcessMessage() and configHandler() 1721 | 1722 | if (configStringFound == true) 1723 | { 1724 | // return (UM980_RESULT_CONFIG_PRESENT); 1725 | return (true); 1726 | } 1727 | 1728 | if (commandResponse == UM980_RESULT_RESPONSE_COMMAND_ERROR) 1729 | { 1730 | debugPrintf("Unicore Lib: Query failure"); 1731 | unicoreLibrarySemaphoreBlock = false; // Allow external tasks to control serial hardware 1732 | // return (UM980_RESULT_RESPONSE_COMMAND_ERROR); 1733 | return (false); 1734 | } 1735 | 1736 | delay(1); 1737 | } 1738 | 1739 | unicoreLibrarySemaphoreBlock = false; // Allow external tasks to control serial hardware 1740 | 1741 | // return (UM980_RESULT_OK); 1742 | return (false); 1743 | } 1744 | 1745 | // Returns true when GNGGA NMEA reports position status >= 1 1746 | bool UM980::isNmeaFixed() 1747 | { 1748 | if (nmeaPositionStatus >= 1) 1749 | return (true); 1750 | return (false); 1751 | } 1752 | 1753 | // By default, library will attempt to start RECTIME and BESTNAV regardless of GNSS fix 1754 | // This may lead to command timeouts as the UM980 does not appear to respond to BESTNAVB commands if 3D fix is not 1755 | // achieved. Set startBinartBeforeFix = false via disableBinaryBeforeFix() to block binary commands before a fix is 1756 | // achieved 1757 | void UM980::enableBinaryBeforeFix() 1758 | { 1759 | startBinaryBeforeFix = true; 1760 | } 1761 | void UM980::disableBinaryBeforeFix() 1762 | { 1763 | startBinaryBeforeFix = false; 1764 | } 1765 | -------------------------------------------------------------------------------- /src/SparkFun_Unicore_GNSS_Arduino_Library.h: -------------------------------------------------------------------------------- 1 | /* 2 | This is a library to control Unicore GNSS receivers, with 3 | a focus on the UM980 Triband receiver. Other receivers in the 4 | same family should work: UM982, UM960, UM960L, etc. 5 | 6 | https://github.com/sparkfun/SparkFun_Unicore_GNSS_Arduino_Library 7 | Best used with the UM980 Breakout: https://www.sparkfun.com/products/23286 8 | 9 | Development environment specifics: 10 | Arduino IDE 1.8.x 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | */ 18 | 19 | #ifndef _SPARKFUN_UNICORE_GNSS_ARDUINO_LIBRARY_H 20 | #define _SPARKFUN_UNICORE_GNSS_ARDUINO_LIBRARY_H 21 | 22 | #include "Arduino.h" 23 | 24 | #if __has_include("SoftwareSerial.h") 25 | #include 26 | #endif 27 | 28 | #include "unicore_structs.h" 29 | #include //http://librarymanager/All#SparkFun_Extensible_Message_Parser 30 | 31 | typedef enum 32 | { 33 | UM980_RESULT_OK = 0, 34 | UM980_RESULT_TIMEOUT_START_BYTE, 35 | UM980_RESULT_TIMEOUT_DATA_BYTE, 36 | UM980_RESULT_TIMEOUT_END_BYTE, 37 | UM980_RESULT_TIMEOUT_RESPONSE, 38 | UM980_RESULT_WRONG_COMMAND, 39 | UM980_RESULT_WRONG_MESSAGE_ID, 40 | UM980_RESULT_BAD_START_BYTE, 41 | UM980_RESULT_BAD_CHECKSUM, 42 | UM980_RESULT_BAD_CRC, 43 | UM980_RESULT_MISSING_CRC, 44 | UM980_RESULT_TIMEOUT, 45 | UM980_RESULT_RESPONSE_OVERFLOW, 46 | UM980_RESULT_RESPONSE_COMMAND_OK, 47 | UM980_RESULT_RESPONSE_COMMAND_ERROR, 48 | UM980_RESULT_RESPONSE_COMMAND_WAITING, 49 | UM980_RESULT_RESPONSE_COMMAND_CONFIG, 50 | UM980_RESULT_CONFIG_PRESENT, 51 | } Um980Result; 52 | 53 | #define um980BinarySyncA ((uint8_t)0xAA) 54 | #define um980BinarySyncB ((uint8_t)0x44) 55 | #define um980BinarySyncC ((uint8_t)0xB5) 56 | #define um980ASCIISyncEnd ((uint8_t)'\n') 57 | 58 | #define um980HeaderLength ((uint16_t)24) 59 | #define offsetHeaderSyncA ((uint16_t)0) 60 | #define offsetHeaderSyncB ((uint16_t)1) 61 | #define offsetHeaderSyncC ((uint16_t)2) 62 | #define offsetHeaderCpuIdle ((uint16_t)3) 63 | #define offsetHeaderMessageId ((uint16_t)4) 64 | #define offsetHeaderMessageLength ((uint16_t)6) 65 | #define offsetHeaderReferenceTime ((uint16_t)8) 66 | #define offsetHeaderTimeStatus ((uint16_t)9) 67 | #define offsetHeaderWeekNumber ((uint16_t)10) 68 | #define offsetHeaderSecondsOfWeek ((uint16_t)12) 69 | #define offsetHeaderReleaseVersion ((uint16_t)20) 70 | #define offsetHeaderLeapSecond ((uint16_t)21) 71 | #define offsetHeaderOutputDelay ((uint16_t)22) 72 | 73 | // VERSIONB 74 | #define messageIdVersion ((uint16_t)37) 75 | #define offsetVersionModuleType ((uint16_t)0) 76 | #define offsetVersionFirmwareVersion ((uint16_t)4) 77 | #define offsetVersionAuth ((uint16_t)37) 78 | #define offsetVersionPsn ((uint16_t)166) 79 | #define offsetVersionEfuseID ((uint16_t)232) 80 | #define offsetVersionCompTime ((uint16_t)265) 81 | 82 | // BESTNAVB contains HPA, sats tracked/used, lat/long, RTK status, fix status 83 | #define messageIdBestnav ((uint16_t)2118) 84 | #define offsetBestnavPsolStatus ((uint16_t)0) 85 | #define offsetBestnavPosType ((uint16_t)4) 86 | #define offsetBestnavLat ((uint16_t)8) 87 | #define offsetBestnavLon ((uint16_t)16) 88 | #define offsetBestnavHgt ((uint16_t)24) 89 | #define offsetBestnavLatDeviation ((uint16_t)40) 90 | #define offsetBestnavLonDeviation ((uint16_t)44) 91 | #define offsetBestnavHgtDeviation ((uint16_t)48) 92 | #define offsetBestnavSatsTracked ((uint16_t)64) 93 | #define offsetBestnavSatsUsed ((uint16_t)65) 94 | #define offsetBestnavExtSolStat ((uint16_t)69) 95 | #define offsetBestnavVelType ((uint16_t)76) 96 | #define offsetBestnavHorSpd ((uint16_t)88) 97 | #define offsetBestnavTrkGnd ((uint16_t)96) 98 | #define offsetBestnavVertSpd ((uint16_t)104) 99 | #define offsetBestnavVerspdStd ((uint16_t)112) 100 | #define offsetBestnavHorspdStd ((uint16_t)116) 101 | 102 | // BESTNAVXYZB 103 | #define messageIdBestnavXyz ((uint16_t)240) 104 | #define offsetBestnavXyzPsolStatus ((uint16_t)0) 105 | #define offsetBestnavXyzPosType ((uint16_t)4) 106 | #define offsetBestnavXyzPX ((uint16_t)8) 107 | #define offsetBestnavXyzPY ((uint16_t)16) 108 | #define offsetBestnavXyzPZ ((uint16_t)24) 109 | #define offsetBestnavXyzPXDeviation ((uint16_t)32) 110 | #define offsetBestnavXyzPYDeviation ((uint16_t)36) 111 | #define offsetBestnavXyzPZDeviation ((uint16_t)40) 112 | #define offsetBestnavXyzSatsTracked ((uint16_t)104) 113 | #define offsetBestnavXyzSatsUsed ((uint16_t)105) 114 | #define offsetBestnavXyzExtSolStat ((uint16_t)109) 115 | 116 | // RECTIMEB for time/date 117 | #define messageIdRectime ((uint16_t)102) 118 | #define offsetRectimeClockStatus ((uint16_t)0) 119 | #define offsetRectimeOffset ((uint16_t)4) 120 | #define offsetRectimeOffsetStd ((uint16_t)12) 121 | #define offsetRectimeUtcYear ((uint16_t)28) 122 | #define offsetRectimeUtcMonth ((uint16_t)32) 123 | #define offsetRectimeUtcDay ((uint16_t)33) 124 | #define offsetRectimeUtcHour ((uint16_t)34) 125 | #define offsetRectimeUtcMinute ((uint16_t)35) 126 | #define offsetRectimeUtcMillisecond ((uint16_t)36) 127 | #define offsetRectimeUtcStatus ((uint16_t)40) 128 | 129 | // HWSTATUS has temperature info, and voltage info 130 | 131 | void um980ProcessMessage(SEMP_PARSE_STATE *parse, uint16_t type); 132 | 133 | class UM980 134 | { 135 | private: 136 | const uint16_t dataFreshLimit_ms = 2000; 137 | unsigned long lastUpdateGeodetic = 0; 138 | unsigned long lastUpdateEcef = 0; 139 | unsigned long lastUpdateDateTime = 0; 140 | unsigned long lastUpdateVersion = 0; 141 | 142 | bool isNmeaFixed(); // Returns true when GNGGA NMEA reports position status >= 1 143 | 144 | void stopAutoReports(); // Delete all pointers to force reinit next time a helper function is called 145 | 146 | Um980Result getGeodetic(uint16_t maxWaitMs = 1500); 147 | Um980Result updateEcef(uint16_t maxWaitMs = 1500); 148 | Um980Result updateDateTime(uint16_t maxWaitMs = 1500); 149 | 150 | Print *_debugPort = nullptr; // The stream to send debug messages to if enabled. Usually Serial. 151 | 152 | SEMP_PARSE_STATE *_sempParse; // State of the SparkFun Extensible Message Parser 153 | 154 | bool unicoreLibrarySemaphoreBlock = false; // Gets set to true when the Unicore library needs to interact directly 155 | // with the serial hardware 156 | char configStringToFind[100] = {'\0'}; 157 | bool configStringFound = false; // configHandler() sets true if we find the intended string 158 | 159 | protected: 160 | HardwareSerial *_hwSerialPort = nullptr; 161 | 162 | public: 163 | bool _printBadChecksum = false; // Display bad checksum message from the parser 164 | bool _printParserTransitions = false; // Display the parser transitions 165 | bool _printRxMessages = false; // Display the received message summary 166 | bool _dumpRxMessages = false; // Display the received message hex dump 167 | 168 | uint8_t nmeaPositionStatus = 0; // Position psition status obtained from GNGGA NMEA 169 | 170 | // By default, library will attempt to start RECTIME and BESTNAV regardless of GNSS fix. 171 | // This may lead to command timeouts as the UM980 does not appear to respond to BESTNAVB commands if 3D fix is not 172 | // achieved. Set startBinartBeforeFix = false via disableBinaryBeforeFix() to block binary commands before a fix is 173 | // achieved 174 | bool startBinaryBeforeFix = true; 175 | 176 | bool begin(HardwareSerial &serialPort, Print *parserDebug = nullptr, Print *parserError = &Serial); 177 | bool isConnected(); 178 | bool isBlocking(); 179 | bool update(); 180 | bool updateOnce(); 181 | 182 | void debugPrintf(const char *format, ...); 183 | void enableDebugging(Print &debugPort = Serial); 184 | void disableDebugging(); 185 | 186 | void enableParserDebug(Print *print = &Serial); 187 | void disableParserDebug(); 188 | void enableParserErrors(Print *print = &Serial); 189 | void disableParserErrors(); 190 | 191 | void enableBinaryBeforeFix(); 192 | void disableBinaryBeforeFix(); 193 | 194 | void enablePrintBadChecksums(); 195 | void disablePrintBadChecksums(); 196 | void enablePrintParserTransitions(); 197 | void disablePrintParserTransitions(); 198 | void enablePrintRxMessages(); 199 | void disablePrintRxMessages(); 200 | void enableRxMessageDump(); 201 | void disableRxMessageDump(); 202 | void printParserConfiguration(Print *print = &Serial); 203 | 204 | void dumpBuffer(const uint8_t *buffer, uint16_t length); 205 | 206 | char commandName[50] = ""; // Passes the command type into parser - CONFIG PPS ENABLE GPS POSITIVE 200000 1000 0 0 207 | uint8_t commandResponse = UM980_RESULT_OK; // Gets EOM result from parser 208 | 209 | // Mode 210 | bool setMode(const char *modeType); 211 | bool setModeBase(const char *baseType); 212 | bool setModeBaseGeodetic(double latitude, double longitude, double altitude); 213 | bool setModeBaseECEF(double coordinateX, double coordinateY, double coordinateZ); 214 | bool setModeBaseAverage(); 215 | bool setModeBaseAverage(uint16_t averageTime); 216 | bool setModeRover(const char *roverType); 217 | bool setModeRoverSurvey(); 218 | bool setModeRoverUAV(); 219 | bool setModeRoverAutomotive(); 220 | bool setModeRoverMow(); 221 | 222 | // Config 223 | bool setPortBaudrate(const char *comName, unsigned long newBaud); 224 | bool setBaudrate(unsigned long newBaud); 225 | bool enablePPS(uint32_t widthMicroseconds, uint16_t periodMilliseconds, bool positivePolarity = true, 226 | int16_t rfDelay = 0, int16_t userDelay = 0); 227 | bool disablePPS(); 228 | bool configurePPS(const char *configString); 229 | 230 | // Mask 231 | bool enableConstellation(const char *constellationName); 232 | bool disableConstellation(const char *constellationName); 233 | bool setElevationAngle(int16_t elevationDegrees, const char *constellationName); 234 | bool setElevationAngle(int16_t elevationDegrees); 235 | bool setMinCNO(uint8_t dBHz); 236 | bool enableFrequency(const char *frequencyName); 237 | bool disableFrequency(const char *frequencyName); 238 | bool enableSystem(const char *systemName); 239 | bool disableSystem(const char *systemName); 240 | 241 | // Data output 242 | bool setNMEAPortMessage(const char *sentenceType, const char *comName, float outputRate); 243 | bool setNMEAMessage(const char *sentenceType, float outputRate); 244 | bool setRTCMPortMessage(const char *sentenceType, const char *comName, float outputRate); 245 | bool setRTCMMessage(const char *sentenceType, float outputRate); 246 | 247 | // Other 248 | bool disableOutput(); 249 | bool disableOutputPort(const char *comName); 250 | bool factoryReset(); 251 | bool reset(); 252 | bool saveConfiguration(uint16_t maxWaitMs = 1500); 253 | 254 | uint16_t serialAvailable(); 255 | uint8_t serialRead(); 256 | void serialPrintln(const char *command); 257 | void clearBuffer(); 258 | 259 | bool sendCommand(const char *command, uint16_t maxWaitMs = 1500); 260 | Um980Result sendQuery(const char *command, uint16_t maxWaitMs = 1500); 261 | Um980Result sendString(const char *command, uint16_t maxWaitMs = 1500); 262 | 263 | // Main helper functions 264 | double getLatitude(uint16_t maxWaitMs = 1500); 265 | double getLongitude(); 266 | double getAltitude(); 267 | double getHorizontalSpeed(); 268 | double getVerticalSpeed(); 269 | double getTrackGround(); 270 | 271 | float getLatitudeDeviation(); 272 | float getLongitudeDeviation(); 273 | float getAltitudeDeviation(); 274 | float getHorizontalSpeedDeviation(); 275 | float getVerticalSpeedDeviation(); 276 | 277 | double getEcefX(); 278 | double getEcefY(); 279 | double getEcefZ(); 280 | float getEcefXDeviation(); 281 | float getEcefYDeviation(); 282 | float getEcefZDeviation(); 283 | 284 | uint8_t getSIV(); 285 | uint8_t getSatellitesTracked(); 286 | uint8_t getSatellitesUsed(); 287 | uint8_t getSolutionStatus(); 288 | uint8_t getPositionType(); 289 | uint8_t getVelocityType(); 290 | 291 | uint8_t getRTKSolution(); 292 | uint8_t getPseudorangeCorrection(); 293 | 294 | uint16_t getYear(); 295 | uint8_t getMonth(); 296 | uint8_t getDay(); 297 | uint8_t getHour(); 298 | uint8_t getMinute(); 299 | uint8_t getSecond(); 300 | uint16_t getMillisecond(); 301 | uint8_t getTimeStatus(); 302 | uint8_t getDateStatus(); 303 | double getTimeOffset(); 304 | double getTimeOffsetDeviation(); 305 | 306 | uint32_t getFixAgeMilliseconds(); // Based on Geodetic report 307 | 308 | uint8_t getModelType(); 309 | char *getVersion(); 310 | char *getID(); 311 | char *getCompileTime(); 312 | 313 | char *getVersionFull(uint16_t maxWaitMs = 1500); 314 | 315 | // Limit maxWaitMs for CONFIG interactions. 800ms good. 500ms too short. 316 | // because we rely on response timeout - there is no known end to the CONFIG response 317 | bool isConfigurationPresent(const char *configStringToFind, uint16_t maxWaitMs = 800); 318 | 319 | void unicoreHandler(uint8_t *data, uint16_t length); 320 | void configHandler(uint8_t *response, uint16_t length); 321 | 322 | bool initBestnav(uint8_t rate = 1); 323 | UNICORE_BESTNAV_t *packetBESTNAV = nullptr; 324 | 325 | bool initBestnavXyz(uint8_t rate = 1); 326 | UNICORE_BESTNAVXYZ_t *packetBESTNAVXYZ = nullptr; 327 | 328 | bool initRectime(uint8_t rate = 1); 329 | UNICORE_RECTIME_t *packetRECTIME = nullptr; 330 | 331 | bool initVersion(); 332 | UNICORE_VERSION_t *packetVERSION = nullptr; 333 | }; 334 | 335 | #endif //_SPARKFUN_UNICORE_GNSS_ARDUINO_LIBRARY_H 336 | -------------------------------------------------------------------------------- /src/unicore_structs.h: -------------------------------------------------------------------------------- 1 | #ifndef _SPARKFUN_UNICORE_STRUCTS_H 2 | #define _SPARKFUN_UNICORE_STRUCTS_H 3 | 4 | typedef struct 5 | { 6 | 7 | double latitude; 8 | double longitude; 9 | double altitude; 10 | double horizontalSpeed; 11 | double verticalSpeed; 12 | double trackGround; 13 | 14 | float latitudeDeviation; 15 | float longitudeDeviation; 16 | float heightDeviation; 17 | float horizontalSpeedDeviation; 18 | float verticalSpeedDeviation; 19 | 20 | uint8_t positionType; // 0 = None, 1 = FixedPos, 8 = DopplerVelocity, 16 = Single, ... 21 | uint8_t velocityType; // 0 = None, 1 = FixedPos, 8 = DopplerVelocity, 16 = Single, ... 22 | 23 | uint8_t 24 | solutionStatus; // 0 = Solution computed, 1 = Insufficient observation, 3 = No convergence, 4 = Covariance trace 25 | 26 | uint8_t satellitesTracked; 27 | uint8_t satellitesUsed; 28 | 29 | uint8_t rtkSolution = 0; 30 | uint8_t pseudorangeCorrection = 0; 31 | } UNICORE_BESTNAV_data_t; 32 | 33 | typedef struct 34 | { 35 | // ubxAutomaticFlags automaticFlags; 36 | UNICORE_BESTNAV_data_t data; 37 | void (*callbackPointerPtr)(UNICORE_BESTNAV_data_t *); 38 | UNICORE_BESTNAV_data_t *callbackData; 39 | } UNICORE_BESTNAV_t; 40 | 41 | typedef struct 42 | { 43 | double ecefX = 0; 44 | double ecefY = 0; 45 | double ecefZ = 0; 46 | float ecefXDeviation = 0; 47 | float ecefYDeviation = 0; 48 | float ecefZDeviation = 0; 49 | } UNICORE_BESTNAVXYZ_data_t; 50 | 51 | typedef struct 52 | { 53 | // ubxAutomaticFlags automaticFlags; 54 | UNICORE_BESTNAVXYZ_data_t data; 55 | void (*callbackPointerPtr)(UNICORE_BESTNAVXYZ_data_t *); 56 | UNICORE_BESTNAVXYZ_data_t *callbackData; 57 | } UNICORE_BESTNAVXYZ_t; 58 | 59 | typedef struct 60 | { 61 | uint16_t year = 0; 62 | uint8_t month = 0; 63 | uint8_t day = 0; 64 | uint8_t hour = 0; 65 | uint8_t minute = 0; 66 | uint8_t second = 0; 67 | uint16_t millisecond = 0; 68 | uint8_t timeStatus = 3; // 0 = valid, 3 = invalid 69 | uint8_t dateStatus = 0; // 0 = Invalid, 1 = valid, 2 = leap second warning 70 | double timeOffset = 0; 71 | double timeDeviation = 0; 72 | } UNICORE_RECTIME_data_t; 73 | 74 | typedef struct 75 | { 76 | // ubxAutomaticFlags automaticFlags; 77 | UNICORE_RECTIME_data_t data; 78 | void (*callbackPointerPtr)(UNICORE_RECTIME_data_t *); 79 | UNICORE_RECTIME_data_t *callbackData; 80 | } UNICORE_RECTIME_t; 81 | 82 | // #VERSION,98,GPS,UNKNOWN,1,711000,0,0,18,144;UM980,R4.10Build7923,HRPT00-S10C-P,2310415000001-MD22B1224961040,ff3bd496fd7ca68b,2022/09/28*55f61e51 83 | typedef struct 84 | { 85 | uint8_t modelType = 0; 86 | char swVersion[33 + 1] = {0}; // Add terminator 87 | char efuseID[33 + 1] = {0}; // Add terminator 88 | char compileTime[43 + 1] = {0}; // Add terminator 89 | } UNICORE_VERSION_data_t; 90 | 91 | typedef struct 92 | { 93 | // ubxAutomaticFlags automaticFlags; 94 | UNICORE_VERSION_data_t data; 95 | void (*callbackPointerPtr)(UNICORE_VERSION_data_t *); 96 | UNICORE_VERSION_data_t *callbackData; 97 | } UNICORE_VERSION_t; 98 | 99 | #endif // _SPARKFUN_UNICORE_STRUCTS_H 100 | --------------------------------------------------------------------------------