Hello,
I am working on setting up a ZED-F9P/ NEO-D9S combo board with an Artemis Nano redboard connected with the qwiik connect system. The antenna I am using is an SPK-6E L1/L2/L5 band and I have a PointPerfect account set up for L-Band SPARTN messages. For my application, I am particularly interested in high vertical position accuracy.
I am able to get a floating carrier solution with an accuracy that is high enough for my application but the carrier solution never reaches a fixed carrier solution. The spartn messages are being corrected decrypted and picked up and I have let it sit still in this configuration for 20 minutes without ever reaching a fixed solution. Is there a setting that I am missing? Should I use a different antenna? Do you have a suggestion for the most robust antenna we can use for high vertical accuracy? The Arduino code I am using is pasted below here with the exception of the spartan keys. Any help is much appreciated thank you!
#include <SparkFun_u-blox_GNSS_v3.h> //http://librarymanager/All#SparkFun_u-blox_GNSS_v3
SFE_UBLOX_GNSS myLBand; // NEO-D9S
SFE_UBLOX_GNSS myGNSS; // ZED-F9P
#include âspartn_keys.hâ
void pushRXMPMP(UBX_RXM_PMP_message_data_t *pmpData)
{
//Extract the raw message payload length
uint16_t payloadLen = ((uint16_t)pmpData->lengthMSB << 8) | (uint16_t)pmpData->lengthLSB;
Serial.print(F("New RXM-PMP data received. Message payload length is "));
Serial.print(payloadLen);
#ifndef noPush
Serial.println(F(" Bytes. Pushing it to the GNSSâŚ"));
//Push the PMP data to the GNSS
//The payload length could be variable, so we need to push the header and payload, then checksum
myGNSS.pushRawData(&pmpData->sync1, (size_t)payloadLen + 6); // Push the sync chars, class, ID, length and payload
myGNSS.pushRawData(&pmpData->checksumA, (size_t)2); // Push the checksum bytes
#else
Serial.println(F(" Bytes."));
#endif
}
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Callback: printPVTdata will be called when new NAV PVT data arrives
// See u-blox_structs.h for the full definition of UBX_NAV_PVT_data_t
// _____ You can use any name you like for the callback. Use the same name when you call setAutoPVTcallbackPtr
// / _____ This must be UBX_NAV_PVT_data_t
// | / _____ You can use any name you like for the struct
// | | /
// | | |
void printPVTdata(UBX_NAV_PVT_data_t *ubxDataStruct)
{
double latitude = ubxDataStruct->lat; // Print the latitude
Serial.print(F("Lat: "));
Serial.print(latitude / 10000000.0, 7);
double longitude = ubxDataStruct->lon; // Print the longitude
Serial.print(F(" Long: "));
Serial.print(longitude / 10000000.0, 7);
double altitude = ubxDataStruct->hMSL; // Print the height above mean sea level
Serial.print(F(" Height: "));
Serial.print(altitude / 1000.0, 3);
uint8_t fixType = ubxDataStruct->fixType; // Print the fix type
Serial.print(F(" Fix: â));
Serial.print(fixType);
if (fixType == 0)
Serial.print(F(â (None)â));
else if (fixType == 1)
Serial.print(F(â (Dead Reckoning)â));
else if (fixType == 2)
Serial.print(F(â (2D)â));
else if (fixType == 3)
Serial.print(F(â (3D)â));
else if (fixType == 4)
Serial.print(F(â (GNSS + Dead Reckoning)â));
else if (fixType == 5)
Serial.print(F(â (Time Only)â));
else
Serial.print(F(â (UNKNOWN)"));
uint8_t carrSoln = ubxDataStruct->flags.bits.carrSoln; // Print the carrier solution
Serial.print(F(" Carrier Solution: â));
Serial.print(carrSoln);
if (carrSoln == 0)
Serial.print(F(â (None)â));
else if (carrSoln == 1)
Serial.print(F(â (Floating)â));
else if (carrSoln == 2)
Serial.print(F(â (Fixed)â));
else
Serial.print(F(â (UNKNOWN)"));
uint32_t hAcc = ubxDataStruct->hAcc; // Print the horizontal accuracy estimate
Serial.print(F(" Horizontal Accuracy Estimate: â));
Serial.print(hAcc);
Serial.print(F(â (mm)"));
uint32_t vAcc = ubxDataStruct->vAcc; // Print the horizontal accuracy estimate
Serial.print(F(" Vertical Accuracy Estimate: â));
Serial.print(vAcc);
Serial.print(F(â (mm)"));
Serial.println();
}
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Callback: printRXMCOR will be called when new RXM COR data arrives
// See u-blox_structs.h for the full definition of UBX_RXM_COR_data_t
// _____ You can use any name you like for the callback. Use the same name when you call setRXMCORcallbackPtr
// / _____ This must be UBX_RXM_COR_data_t
// | / _____ You can use any name you like for the struct
// | | /
// | | |
void printRXMCOR(UBX_RXM_COR_data_t *ubxDataStruct)
{
Serial.print(F("UBX-RXM-COR: ebno: "));
Serial.print((double)ubxDataStruct->ebno / 8, 3); //Convert ebno to dB
Serial.print(F(" protocol: "));
if (ubxDataStruct->statusInfo.bits.protocol == 1)
Serial.print(F(âRTCM3â));
else if (ubxDataStruct->statusInfo.bits.protocol == 2)
Serial.print(F(âSPARTNâ));
else if (ubxDataStruct->statusInfo.bits.protocol == 29)
Serial.print(F(âPMP (SPARTN)â));
else if (ubxDataStruct->statusInfo.bits.protocol == 30)
Serial.print(F(âQZSSL6â));
else
Serial.print(F(âUnknownâ));
Serial.print(F(" errStatus: "));
if (ubxDataStruct->statusInfo.bits.errStatus == 1)
Serial.print(F(âError-freeâ));
else if (ubxDataStruct->statusInfo.bits.errStatus == 2)
Serial.print(F(âErroneousâ));
else
Serial.print(F(âUnknownâ));
Serial.print(F(" msgUsed: "));
if (ubxDataStruct->statusInfo.bits.msgUsed == 1)
Serial.print(F(âNot usedâ));
else if (ubxDataStruct->statusInfo.bits.msgUsed == 2)
Serial.print(F(âUsedâ));
else
Serial.print(F(âUnknownâ));
Serial.print(F(" msgEncrypted: "));
if (ubxDataStruct->statusInfo.bits.msgEncrypted == 1)
Serial.print(F(âNot encryptedâ));
else if (ubxDataStruct->statusInfo.bits.msgEncrypted == 2)
Serial.print(F(âEncryptedâ));
else
Serial.print(F(âUnknownâ));
Serial.print(F(" msgDecrypted: "));
if (ubxDataStruct->statusInfo.bits.msgDecrypted == 1)
Serial.print(F(âNot decryptedâ));
else if (ubxDataStruct->statusInfo.bits.msgDecrypted == 2)
Serial.print(F(âSuccessfully decryptedâ));
else
Serial.print(F(âUnknownâ));
Serial.println();
}
#define OK(ok) (ok ? F(" â OK") : F(" â ERROR!")) // Convert uint8_t into OK/ERROR
void setup() {
Wire.begin(); //Start I2C
// -------------- Configure NEO-D9S --------------
while (myLBand.begin(Wire, 0x43) == false) //Connect to the u-blox NEO-D9S using Wire port. The D9S default I2C address is 0x43 (not 0x42)
{
Serial.println(F(âu-blox NEO-D9S not detected at default I2C address. Please check wiring.â));
delay(2000);
}
Serial.println(F(âu-blox NEO-D9S connectedâ));
// myLBand.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial
// L-Band center frequency
//const uint32_t myLBandFreq = 1556290000; // Uncomment this line to use the US SPARTN 1.8 service
const uint32_t myLBandFreq = 1545260000; // Uncomment this line to use the EU SPARTN 1.8 service
// NEO-D9S Configuration Settings
Serial.println(F(âu-blox NEO-D9S connectedâ));
myLBand.newCfgValset(); // Create a new Configuration Interface message - this defaults to VAL_LAYER_RAM_BBR (change in RAM and BBR)
myLBand.addCfgValset(UBLOX_CFG_PMP_CENTER_FREQUENCY, myLBandFreq); // Default 1539812500 Hz
myLBand.addCfgValset(UBLOX_CFG_PMP_SEARCH_WINDOW, 2200); // Default 2200 Hz
myLBand.addCfgValset(UBLOX_CFG_PMP_USE_SERVICE_ID, 1); // Default 1
myLBand.addCfgValset(UBLOX_CFG_PMP_SERVICE_ID, 21845); // Default 50821
myLBand.addCfgValset(UBLOX_CFG_PMP_DATA_RATE, 2400); // Default 2400 bps
myLBand.addCfgValset(UBLOX_CFG_PMP_USE_DESCRAMBLER, 1); // Default 1
myLBand.addCfgValset(UBLOX_CFG_PMP_DESCRAMBLER_INIT, 26969); // Default 23560
myLBand.addCfgValset(UBLOX_CFG_PMP_USE_PRESCRAMBLING, 0); // Default 0
myLBand.addCfgValset(UBLOX_CFG_PMP_UNIQUE_WORD, 16238547128276412563ull);
// myLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_I2C, 1); // Ensure UBX-RXM-PMP is enabled on the I2C port
// myLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART1, 1); // Output UBX-RXM-PMP on UART1
myLBand.addCfgValset(UBLOX_CFG_UART2OUTPROT_UBX, 1); // Enable UBX output on UART2
myLBand.addCfgValset(UBLOX_CFG_MSGOUT_UBX_RXM_PMP_UART2, 1); // Output UBX-RXM-PMP on UART2
// myLBand.addCfgValset(UBLOX_CFG_UART1_BAUDRATE, 38400); // match baudrate with ZED default
myLBand.addCfgValset(UBLOX_CFG_UART2_BAUDRATE, 38400); // match baudrate with ZED default
uint8_t ok = myLBand.sendCfgValset(); // Apply the settings
Serial.print(F("L-Band: configuration "));
if (ok)
Serial.println(F(âOKâ));
else
Serial.println(F(âNOT OK!â));
myLBand.softwareResetGNSSOnly(); // Restart the NEO-D9S to apply the configuration settings
myLBand.setRXMPMPmessageCallbackPtr(&pushRXMPMP); // Call pushRXMPMP when new PMP data arrives. Push it to the GNSS
// -------------- Configure ZED-F9P --------------
while (myGNSS.begin() == false) //Connect to the u-blox module using Wire port
{
Serial.println(F(âu-blox GNSS module not detected at default I2C address. Please check wiring.â));
delay(2000);
}
Serial.println(F(âu-blox GNSS module connectedâ));
//Check the ZED firmware version - SPARTN is only supported on ZED-F9P from HPG 1.30 and ZED-F9R from HPS 1.21 onwards
if (myGNSS.getModuleInfo())
{
Serial.print(F(âFWVER: â));
Serial.print(myGNSS.getFirmwareVersionHigh()); // Returns uint8_t
Serial.print(F(â.â));
Serial.println(myGNSS.getFirmwareVersionLow()); // Returns uint8_t
Serial.print(F("Firmware: "));
Serial.println(myGNSS.getFirmwareType()); // Returns HPG, SPG etc. as (const char *)
if (strcmp(myGNSS.getFirmwareType(), "HPG") == 0)
if ((myGNSS.getFirmwareVersionHigh() == 1) && (myGNSS.getFirmwareVersionLow() < 30))
Serial.println("Your module is running old firmware which may not support SPARTN. Please upgrade.");
if (strcmp(myGNSS.getFirmwareType(), "HPS") == 0)
if ((myGNSS.getFirmwareVersionHigh() == 1) && (myGNSS.getFirmwareVersionLow() < 21))
Serial.println("Your module is running old firmware which may not support SPARTN. Please upgrade.");
}
else
Serial.println(F(âError: could not read module info!â));
// myGNSS.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial
// Configure UART2 Port on ZED-F9P to accept PMP Correction Data
ok = myGNSS.setI2COutput(COM_TYPE_UBX); //Turn off NMEA noise
// if (ok) ok = myGNSS.setI2CInput(COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_SPARTN); //Be sure SPARTN input is enabled
if (ok) ok = myGNSS.setUART2Input(COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_SPARTN); //Be sure SPARTN input is enabled
// if (ok) ok = myGNSS.setDGNSSConfiguration(SFE_UBLOX_DGNSS_MODE_FIXED); // Set the differential mode - ambiguities are fixed whenever possible
// if (ok) ok = myGNSS.setNavigationFrequency(1); //Set output in Hz.
// if (ok) ok = myGNSS.setVal8(UBLOX_CFG_MSGOUT_UBX_RXM_COR_I2C, 1); // Enable UBX-RXM-COR messages on I2C
// if (ok) ok = myGNSS.setUART2Input(COM_PORT_UART2, COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_SPARTN); //Be sure SPARTN input is enabled
if (ok) ok = myGNSS.setDGNSSConfiguration(SFE_UBLOX_DGNSS_MODE_FIXED); // Set the differential mode - ambiguities are fixed whenever possible
if (ok) ok = myGNSS.setVal8(UBLOX_CFG_SPARTN_USE_SOURCE, 1); // use LBAND PMP message
// if (ok) ok = myGNSS.setVal8(UBLOX_CFG_NAVSPG_CONSTR_DGNSSTO, 90); // set interval for how long to wait between correction messages
if (ok) ok = myGNSS.setVal8(UBX_NAV_PVT, 1);
//Configure the SPARTN IP Dynamic Keys
//âWhen the receiver boots, the host should send âcurrentâ and ânextâ keys in one message.â - Use setDynamicSPARTNKeys for this.
//âEvery time the âcurrentâ key is expired, ânextâ takes its place.â
//âTherefore the host should then retrieve the new ânextâ key and send only that.â - Use setDynamicSPARTNKey for this.
// The key can be provided in binary (uint8_t) format or in ASCII Hex (char) format, but in both cases keyLengthBytes must represent the binary key length in bytes.
if (ok) ok = myGNSS.setDynamicSPARTNKeys(currentKeyLengthBytes, currentKeyGPSWeek, currentKeyGPSToW, currentDynamicKey,
nextKeyLengthBytes, nextKeyGPSWeek, nextKeyGPSToW, nextDynamicKey);
myGNSS.setAutoPVTcallbackPtr(&printPVTdata); // Enable automatic NAV PVT messages with callback to printPVTdata so we can watch the carrier solution go to fixed
myGNSS.setRXMCORcallbackPtr(&printRXMCOR); // Print the contents of UBX-RXM-COR messages so we can check if the PMP data is being decrypted successfully
}
void loop() {
myGNSS.checkUblox(); // Check for the arrival of new GNSS data and process it.
myGNSS.checkCallbacks(); // Check if any GNSS callbacks are waiting to be processed.
myLBand.checkUblox(); // Check for the arrival of new PMP data and process it.
myLBand.checkCallbacks(); // Check if any LBand callbacks are waiting to be processed.
}