Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data is not stored in BBR after myGNSS.powerOff() (software backup mode is not working) #42

Closed
crnicholson opened this issue Nov 30, 2023 · 13 comments

Comments

@crnicholson
Copy link

crnicholson commented Nov 30, 2023

Hello, all!
I am experiencing a weird issue after putting my u-blox MAX M10S GPS module into software backup mode using the function powerOff(). It is not storing time or location data from the previous period of being on. It is almost like it is reseting every time I put it into backup mode. This screenshot from the integration manual says that it stores the relevant data:
image
And this screenshot says that it retains BBR:
image
But, this screenshot seems contradicting, saying that the RAM is erased:
image
Reading futher in the inerface manual, I noticed this. Is the Sparkfun library sensing a reset message every time it "wakes up" from software backup mode?
image
I am using a homemade SAMD21G18A microcontroller to interface with the GPS module. The GPS module has power supplied to the V_IO and VCC pins, with nothing supplied to the V_BCKP pin, as specified. The u-blox integration manual can be found using this link (section 3.6.3/3.6.3.2).

If you're wondering what code (modified example 22) I'm using to get these results, here it is:

/*
  Powering off a ublox GNSS module
  By: bjorn
  unsurv.org
  Date: July 20th, 2020
  License: MIT. See license file for more information.

  This example shows you how to turn off the ublox module to lower the power consumption.
  There are two functions: one just specifies a duration in milliseconds the other also specifies a pin on the GNSS device to wake it up with.
  By driving a voltage from LOW to HIGH or HIGH to LOW on the chosen module pin you wake the device back up.
  Note: Doing so on the INT0 pin when using the regular powerOff(durationInMs) function will wake the device anyway. (tested on SAM-M8Q)
  Note: While powered off, you should not query the device for data or it might wake up. This can be used to wake the device but is not recommended.
        Works best when also putting your microcontroller to sleep.

  Feel like supporting open source hardware?
  Buy a board from SparkFun!
  SparkFun myGNSS-RTK2 - ZED-F9P (myGNSS-15136)    https://www.sparkfun.com/products/15136
  SparkFun myGNSS-RTK-SMA - ZED-F9P (myGNSS-16481) https://www.sparkfun.com/products/16481
  SparkFun ZED-F9K Breakout (myGNSS-18719)      https://www.sparkfun.com/products/18719
  SparkFun ZED-F9R Breakout (myGNSS-16344)      https://www.sparkfun.com/products/16344

  Hardware Connections:
  Plug a Qwiic cable into the GNSS and a BlackBoard.
  To force the device to wake up you need to connect to a pin (for example INT0) seperately on the module.
  If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425)
  Open the serial monitor at 115200 baud to see the output
*/

#define Serial SerialUSB
#include <SparkFun_u-blox_GNSS_v3.h> //http://librarymanager/All#SparkFun_u-blox_GNSS_v3
SFE_UBLOX_GNSS myGNSS;

// define a digital pin capable of driving HIGH and LOW
#define WAKEUP_PIN 11

// Possible GNSS interrupt pins for powerOffWithInterrupt are:
// VAL_RXM_PMREQ_WAKEUPSOURCE_UARTRX  = uartrx
// VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0 = extint0 (default)
// VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT1 = extint1
// VAL_RXM_PMREQ_WAKEUPSOURCE_SPICS   = spics
// These values can be or'd (|) together to enable interrupts on multiple pins

void wakeUp() {
  Serial.print("-- waking up module via pin " + String(WAKEUP_PIN));
  Serial.println(" on your microcontroller --");

  digitalWrite(WAKEUP_PIN, LOW);
  delay(1000);
  digitalWrite(WAKEUP_PIN, HIGH);
  delay(1000);
  digitalWrite(WAKEUP_PIN, LOW);
}

void setup() {
  pinMode(WAKEUP_PIN, OUTPUT);
  digitalWrite(WAKEUP_PIN, LOW);

  Serial.begin(115200);
  while (!Serial); //Wait for user to open terminal
  Serial.println("SparkFun u-blox Example");

  Wire.begin();

  myGNSS.enableDebugging(); // Enable debug messages

  if (myGNSS.begin() == false) //Connect to the u-blox module using Wire port
  {
    Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing."));
    while (1);
  }
}

void loop() {
  Serial.print("Date/Time: ");
  Serial.print(myGNSS.getYear());
  Serial.print("-");
  Serial.print(myGNSS.getMonth());
  Serial.print("-");
  Serial.print(myGNSS.getDay());
  Serial.print(" ");
  Serial.print(myGNSS.getHour());
  Serial.print(":");
  Serial.print(myGNSS.getMinute());
  Serial.print(":");
  Serial.println(myGNSS.getSecond());
   
  // Powering off for 20s, you should see the power consumption drop.
  Serial.println("-- Powering off module for 20s --");

  myGNSS.powerOff(20000);
  //myGNSS.powerOffWithInterrupt(20000, VAL_RXM_PMREQ_WAKEUPSOURCE_EXTINT0);

  delay(10000);

  // After 10 seconds wake the device via the specified pin on your microcontroller and module.
  wakeUp();
}

Any help with this would be appreciated!

@PaulZC
Copy link
Collaborator

PaulZC commented Nov 30, 2023

Hi Charlie (@charlienicholsonr ),

We'll help you if we can. But, to me, this sounds like an issue with the way you are powering the GNSS and/or using the PMREQ functions.

Please re-read section 4.1 in the integration manual. Are you sure leaving V_BCKP disconnected is the right thing to do, in your case?

RESET_N is the GNSS hardware reset pin. How do you have RESET_N and EXTINT connected? Are you keeping RESET_N high? Are you attempting to use EXTINT to wake the GNSS?

The library has three reset methods (hardReset, softwareResetGNSSOnly, softwareEnableGNSS) which all use the common method cfgRst. Internally, cfgRst isn't called anywhere else, so I don't think the library is responsible for your reset.

In summary, I don't think the library is to blame. I think this is more likely to be a misunderstanding about the sleep state and the wake source.

Best,
Paul

@crnicholson
Copy link
Author

I appreciate your quick response! It very much could be my ignorance of the software backup mode, and not the library's fault.

According to section 3.6.3.2 of the integration manual, V_BCKP should be left open unless other wise needed:
image
I have tried connecting V_BCKP to VCC but to no avail, in case you were wondering.

I have EXTINT connected to GND and I am not trying to wake up the module with it. I have left RESET_N unconnected, but I will run a quick test in a moment with it pulled high.

@crnicholson
Copy link
Author

Okay, did a test and pulling RESET_N high doesn't solve my problem. Btw, this is the serial output:

17:05:15.365 -> Sending: CLS:CFG ID:0x8B Len: 0x8 Payload: 0 0 0 0 1 0 71 10
17:05:15.365 -> sendCommand: Waiting for ACK response
17:05:15.431 -> checkUbloxI2C: 27 bytes available
17:05:15.431 -> Incoming: Size: 9 Received: CLS:CFG ID:0x8B Len: 0x9 Payload: 1 0 0 0 1 0 71 10 1
17:05:15.431 -> packetCfg now valid
17:05:15.431 -> packetCfg classAndIDmatch
17:05:15.431 -> Incoming: Size: 2 Received: CLS:ACK ID:0x1 Len: 0x2 Payload: 6 8B
17:05:15.431 -> packetCfg now valid
17:05:15.431 -> packetAck now valid
17:05:15.431 -> packetCfg classAndIDmatch
17:05:15.431 -> packetAck classAndIDmatch
17:05:15.431 -> waitForACKResponse: valid data and valid ACK received after 105 msec
17:05:15.431 -> getVal: sendCommand returned: Data Received
17:05:15.431 -> Date/Time: 
17:05:15.431 -> Sending: CLS:NAV ID:PVT Len: 0x0 Payload:
17:05:15.431 -> sendCommand: Waiting for No ACK response
17:05:15.530 -> checkUbloxI2C: 469 bytes available
17:05:15.563 -> Incoming: Size: 92 Received: CLS:NAV ID:PVT Len: 0x5C Payload: E0 2E 0 0 E5 7 3 7 0 0 C F0 FF FF FF FF 0 0 0 0 0 0 24 0 0 0 0 0 0 0 0 0 0 0 0 0 98 BD FF FF FF FF FF FF 0 76 84 DF 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20 4E 0 0 80 A8 12 1 F 27 0 0 5C 40 56 2F 0 0 0 0 0 0 0 0
17:05:15.563 -> packetCfg now valid
17:05:15.563 -> packetCfg classAndIDmatch
17:05:15.595 -> waitForNoACKResponse: valid data with CLS/ID match after 164 msec
17:05:15.595 -> 2021-3-7 0:0:12
17:05:15.595 -> -- Powering off module for 20s --
17:05:15.595 -> Powering off for 20000 ms
17:05:15.595 -> 
17:05:15.595 -> Sending: CLS:0x2 ID:0x41 Len: 0x8 Payload: 20 4E 0 0 2 0 0 0
17:05:15.595 -> sendCommand: Waiting for No ACK response
17:05:15.726 -> checkUbloxI2C: 34 bytes available
17:05:15.924 -> checkUbloxI2C: 35 bytes available
17:05:16.714 -> waitForNoACKResponse: TIMEOUT after 1100 msec. No packet received.
17:05:26.697 -> -- waking up module via pin 11 on your microcontroller --
17:05:28.707 -> Date/Time: 
17:05:28.707 -> Sending: CLS:NAV ID:PVT Len: 0x0 Payload:
17:05:28.707 -> sendCommand: Waiting for No ACK response
17:05:28.805 -> checkUbloxI2C: 469 bytes available
17:05:28.838 -> Incoming: Size: 92 Received: CLS:NAV ID:PVT Len: 0x5C Payload: E8 3 0 0 E5 7 3 7 0 0 1 F0 FF FF FF FF 0 0 0 0 0 0 24 0 0 0 0 0 0 0 0 0 0 0 0 0 98 BD FF FF FF FF FF FF 0 76 84 DF 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20 4E 0 0 80 A8 12 1 F 27 0 0 5C 40 56 2F 0 0 0 0 0 0 0 0
17:05:28.838 -> packetCfg now valid

@PaulZC
Copy link
Collaborator

PaulZC commented Dec 1, 2023

Hi Charlie (@charlienicholsonr ),

OK. I think I understand what is happening... I have a MAX-M10S board here. I've cut the track from the battery to V_BCKP to replicate your board.

If I run the following example with firstAwakeForSecs set to 60 seconds, all is well. The clock date and time is correct when the GNSS wakes from sleep. But if I set firstAwakeForSecs to 15 seconds, then the clock goes back to 2020-5-3 00:00:00. I think it has something to do with the time validity; if the GNSS does not think the clock is accurate, it starts over when waking from sleep. You need to ensure the GNSS is on long enough for it to sync the clock before you put it to sleep...

I'm going to close this because I think this solves your issue.

Good luck with your project!

Best wishes,
Paul

const int firstAwakeForSecs = 60;
const int awakeForSecs = 15;
const int sleepForSecs = 15;

#define mySerial Serial

#include <SparkFun_u-blox_GNSS_v3.h> //http://librarymanager/All#SparkFun_u-blox_GNSS_v3
SFE_UBLOX_GNSS myGNSS;

void setup() {

  delay(1000);

  mySerial.begin(115200);
  while (!mySerial); //Wait for user to open terminal
  mySerial.println("SparkFun u-blox Example");

  Wire.begin();

  //myGNSS.enableDebugging(); // Enable debug messages

  while (myGNSS.begin() == false) //Connect to the u-blox module using Wire port
  {
    mySerial.println(F("u-blox GNSS not detected. Please check wiring. Retrying..."));
  }

  // Clear any saved configuration
  myGNSS.factoryDefault();
  
  // Wait for 5 seconds
  mySerial.println("-- Waiting for 5s --");
  for (int i = 0; i < 5; i++) {
    delay(1000);
    mySerial.print(".");
  }
  mySerial.println();

  // Hard reset - force a cold start
  myGNSS.hardReset();
}

void loop() {

  static bool firstAwake = true;

  // Print date and time while awake
  // First time, stay awake for firstAwakeForSecs
  // Thereafter, stay awake for awakeForSecs
  for (int i = 0; i < (firstAwake ? firstAwakeForSecs : awakeForSecs); ) {
    if (myGNSS.getPVT()) {
      mySerial.print("Date/Time: ");
      mySerial.print(myGNSS.getYear());
      mySerial.print("-");
      mySerial.print(myGNSS.getMonth());
      mySerial.print("-");
      mySerial.print(myGNSS.getDay());
      mySerial.print(" ");
      mySerial.print(myGNSS.getHour());
      mySerial.print(":");
      mySerial.print(myGNSS.getMinute());
      mySerial.print(":");
      mySerial.println(myGNSS.getSecond());
      i++;
    }
  }

  firstAwake = false;

  // Tell GNSS to power off for sleepForSecs
  mySerial.print("-- Powering off module for ");
  mySerial.print(sleepForSecs);
  mySerial.println("s --");

  // powerOff uses the 8-byte version of RXM-PMREQ - supported by older (M8) modules
  //myGNSS.powerOff(sleepForSecs * 1000);

  // powerOffWithInterrupt uses the 16-byte version of RXM-PMREQ - supported by the M10 etc.
  // powerOffWithInterrupt allows us to set the force flag. The M10 integration manual states:
  // "The "force" flag must be set in UBX-RXM-PMREQ to enter software standby mode."
  myGNSS.powerOffWithInterrupt(sleepForSecs * 1000, 0, true); // No (additional) wakeup sources. force = true

}

@PaulZC PaulZC closed this as completed Dec 1, 2023
@crnicholson
Copy link
Author

This works! Thanks so much! I was troubleshooting this for weeks and I'm so glad you could help!

A quick tip for anyone else having this problem: I couldn't get the const int firstAwakeForSecs = 60; to work, I had to change it to const int firstAwakeForSecs = 120;

@PaulZC
Copy link
Collaborator

PaulZC commented Dec 1, 2023

Hi Charlie,

You can also check the time validity with getTimeValid, getDateValid, getConfirmedTime, getConfirmedDate and getTimeFullyResolved. You could wait for those to go true before powering off, instead of waiting for a fixed period.

All the best,
Paul

@crnicholson
Copy link
Author

Hi Paul,

I don't know whether to open this issue again, but I am encountering some issues after integrating the GNSS code with the rest of my code (which, by the way, is a LoRa tracker). I am having the same problems as before, but this time I am checking if the time data is valid with getConfirmedTime before continuing on to the rest of the loop.

The annoying thing is, I got my code working, and then I stupidly decided to change something, and it stopped working. It probably is just a silly error on my part, especially seeing how much I modified your (working) code.

Here's my sketch:

#include <SPI.h> // Needed for LoRa.
#include <LoRa.h> // LoRa.
#include <Wire.h> // Needed for I2C to GPS module.
#include <SparkFun_u-blox_GNSS_v3.h> // http://librarymanager/All#SparkFun_u-blox_GNSS_v3.

// Settings:
#define comment "XXXXXX"
#define baudRate 115200 // SerialUSB baud rate.
#define syncWord 0xFB // Only other devices with this sync word can receive your broadcast.
#define ssPin 38 // Arduino pin that connects to LoRa module. AKA csPin. 
#define resetPin 5 // Arduino pin that connects to LoRa module.
#define DIO0Pin 2 // Arduino pin that connects to LoRa module.
#define frequency 433E6 // Frequency of your LoRa module.
#define spreadingFactor 12 // Choose this based on this https://forum.arduino.cc/t/what-are-the-best-settings-for-a-lora-radio/449528.
#define bandwidth 62.5E3  // Choose this based on this https://forum.arduino.cc/t/what-are-the-best-settings-for-a-lora-radio/449528.
#define firstAwakeForSecs 120 // You need to have enough time for the GPS to sync before the first sleep, or else the RAM data is unvalidated and is not used by the GPS
#define sleepForSecs 5000 // How many milliseconds do you want to sleep for?
#define devMode // Comment this out when you're ready to do the actual flight to save even more power.
// #define testCoord // Uncomment this to use test coordinates instead of actual coordinates.

struct __attribute__((packed)) dataStruct {
  float lat;
  float lon;
  long alt;
  long sats;
  long speed;
  long course;
  long txCount = 0;
  char text[50] = comment;
} transmittingData;

#ifdef testCoord
  float lat = 42.316651;
  float lon = -71.366030;
  int alt = 96;
  int sats = 9;
  int speed = 60;
  int course = 360;
#endif

SFE_UBLOX_GNSS gps;

void setup() {
  #ifdef devMode
    SerialUSB.begin(baudRate); 
    while (!SerialUSB); // Wait until SerialUSB is all good.
    SerialUSB.println("LoRa Tracker v2.1");
  #endif

  LoRa.setPins(ssPin, resetPin, DIO0Pin); // SS, reset, and DIO0. Has to be before LoRa.begin().

  if (!LoRa.begin(frequency)) {
    #ifdef devMode
      SerialUSB.println("Starting LoRa failed!");
    #endif
    while(1);
  }

  LoRa.setSyncWord(syncWord); 
  LoRa.setSpreadingFactor(spreadingFactor);
  LoRa.setSignalBandwidth(bandwidth);
  LoRa.crc();

  Wire.begin();

  if (gps.begin() == false) { // Connect to the u-blox module using Wire port.
    #ifdef devMode
      SerialUSB.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing."));
    #endif 
    while(1);
  }

  gps.factoryDefault(); // Clear any saved configuration
  
  // Wait for 5 seconds
  #ifdef devMode
    SerialUSB.println("Waiting for 5 seconds before loop.");
  #endif
  for (int i = 0; i < 5; i++) {
    delay(1000);
    #ifdef devMode
      SerialUSB.print(".");
    #endif
  }

  gps.hardReset(); // Hard reset - force a cold start
}

void loop() {
  static bool firstAwake = true;

  if (firstAwake) {
    while(!gps.getConfirmedTime()) {
    // for (int i = 0; i < firstAwakeForSecs;) {
      if (gps.getPVT()) { // Poll it to get some data in the BBR (battery backed RAM) 
        SerialUSB.print("Date/Time: ");
        SerialUSB.print(gps.getYear());
        SerialUSB.print("-");
        SerialUSB.print(gps.getMonth());
        SerialUSB.print("-");
        SerialUSB.print(gps.getDay());
        SerialUSB.print(" ");
        SerialUSB.print(gps.getHour());
        SerialUSB.print(":");
        SerialUSB.print(gps.getMinute());
        SerialUSB.print(":");
        SerialUSB.println(gps.getSecond());
      }
    }
  }

  firstAwake = false;

  #ifdef testCoord
    transmittingData.lat = lat;
    transmittingData.lon = lon;
    transmittingData.alt = alt;
    transmittingData.sats = sats;
    transmittingData.speed = speed;
    transmittingData.course = course;
    transmittingData.txCount++;
  #endif

  #ifndef testCoord
    if (gps.getPVT()) {
      transmittingData.lat = gps.getLatitude();
      transmittingData.lon = gps.getLongitude();
      transmittingData.alt = gps.getAltitude();
      transmittingData.sats = gps.getSIV();
      transmittingData.speed = 0;
      transmittingData.course = 0;
      transmittingData.txCount++;
      transmittingData.lat = transmittingData.lat / 10000000; // Convert to float.
      transmittingData.lon = transmittingData.lon / 10000000; // Convert to float.
      transmittingData.alt = transmittingData.alt / 1000; // Convert to meters.
    }
  #endif

  #ifdef devMode
      SerialUSB.print("Lat: ");
      SerialUSB.print(transmittingData.lat, 6);
      SerialUSB.print(" Lon: ");
      SerialUSB.print(transmittingData.lon, 6);
      SerialUSB.print(" Date/Time: ");
      SerialUSB.print(gps.getYear());
      SerialUSB.print("-");
      SerialUSB.print(gps.getMonth());
      SerialUSB.print("-");
      SerialUSB.print(gps.getDay());
      SerialUSB.print(" ");
      SerialUSB.print(gps.getHour());
      SerialUSB.print(":");
      SerialUSB.print(gps.getMinute());
      SerialUSB.print(":");
      SerialUSB.println(gps.getSecond());
  #endif

  #ifdef devMode
    SerialUSB.print("Entering GPS low power mode for ");
    SerialUSB.print(sleepForSecs);
    SerialUSB.println(" milliseconds.");
  #endif

  // powerOff uses the 8-byte version of RXM-PMREQ - supported by older (M8) modules
  //gps.powerOff(sleepForSecs * 1000);

  // powerOffWithInterrupt uses the 16-byte version of RXM-PMREQ - supported by the M10 etc.
  // powerOffWithInterrupt allows us to set the force flag. The M10 integration manual states:
  // "The "force" flag must be set in UBX-RXM-PMREQ to enter software standby mode."
  gps.powerOffWithInterrupt(sleepForSecs, 0, true); // No (additional) wakeup sources. force = true

  #ifdef devMode
    SerialUSB.print("Transmitting with a packet size of: ");
    SerialUSB.println(sizeof(transmittingData));
  #endif
  transmit(transmittingData); // This is blocking and takes a tad bit more than 5 seconds.
  #ifdef devMode
    SerialUSB.println("Done transmitting. Sleeping LoRa.");
  #endif
  LoRa.sleep(); // The LoRa module wakes up automatically when the SPI interface is active.
}

void transmit(const struct __attribute__((packed)) dataStruct &data) {
  // Send the packet over LoRa.
  LoRa.beginPacket();
  LoRa.write((byte *)&data, sizeof(data));
  LoRa.endPacket();
}

My code needs the GPS module to be on for the least amount of time possible to save power. Note that there isn't an MCU sleep or delay in the code because I'm relying on the transmit function to block the sketch for more than 5 seconds. One thing that could be causing issues in my code is the fact that I have a LoRa module and a GPS module on the same breadboard, and RF interference may be occurring.

Thanks for all your help,
Charlie

@PaulZC
Copy link
Collaborator

PaulZC commented Dec 2, 2023

Hi Charlie,

TL;DR: If you move getConfirmedTime inside the getPVT loop, it will (probably) start working again.

The confirmedTime flag is one bit of flags2 of the PVT (Position Velocity Time) message.
getConfirmedTime will call getPVT internally if it needs to.
If getPVT times out, because the module is still doing its cold start and doesn't want to communicate right now, then getConfirmedTime will return whatever random bit is in the memory that holds the PVT message (or will hold the PVT message when one arrives!).

The methods like getConfirmedTime are not truly safe. Strictly, I should have written them like this:

bool getConfirmedTime(bool *confirmedTime, uint16_t maxWait)

with the confirmedTime bit being written into the bool pointed to by *confirmedTime. The method itself would (only) return true if it knows the confirmedTime is valid - i.e. the getPVT was successful, or had previously been successful.

getPVT only returns true if a PVT message was successfully received. If you call getConfirmedTime inside the if (getPVT() == true) loop, you know the returned value is valid (not stale, not random).

All the best,
Paul

PS. Later, I did start to add safe versions of (previously) unsafe methods like this. Strictly, I should do the same thing everywhere... Including getConfirmedTime. Sorry about that...

bool getESFAutoAlignment(bool *enabled, uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait);
bool getESFAutoAlignment(uint8_t layer = VAL_LAYER_RAM, uint16_t maxWait = kUBLOXGNSSDefaultMaxWait); // Unsafe overload

@crnicholson
Copy link
Author

Hi Paul,

I did what you said, but the loop is simply skipping over the getPVT() because, according to the sketch, there is no available GPS data. Because of this, I modified the sketch to comment the getPVT() out. It is still not working correctly, though. It still is not validating the time data before the power off.

Here is a snippet of my code, and I hope I implemented it correctly. Let me know if I did it incorrectly.

if (firstAwake) {
  // if (gps.getPVT()) { // Commented this out because it was returning false every time. 
    while (!gps.getConfirmedTime()) {
      SerialUSB.println("Waiting for confirmed time...");
      SerialUSB.print("Date/Time: ");
      SerialUSB.print(gps.getYear());
      SerialUSB.print("-");
      SerialUSB.print(gps.getMonth());
      SerialUSB.print("-");
      SerialUSB.print(gps.getDay());
      SerialUSB.print(" ");
      SerialUSB.print(gps.getHour());
      SerialUSB.print(":");
      SerialUSB.print(gps.getMinute());
      SerialUSB.print(":");
      SerialUSB.println(gps.getSecond());

      delay(1000);
    }
    /*
    SerialUSB.println("Confirmed time obtained!");
  } else {
    SerialUSB.println("Failed to get PVT data from GPS");
  }
  */
}

Thanks again for all your help,
Charlie

@PaulZC
Copy link
Collaborator

PaulZC commented Dec 2, 2023

Hi Charlie,

You don't need the gps.factoryDefault() and gps.hardReset(). Those were just to get the module into a known state for the sleep test. You can safely comment or delete those.

After a cold start, getPVT will keep returning false until the module has completed its cold start and is ready to communicate. This takes a couple of seconds. You need to keep calling getPVT until it returns true, then you can safely call getConfirmedTime. So try something like:

  static bool firstAwake = true;

  if (firstAwake) {
    bool confirmedTime = false;
    while (!confirmedTime) {
      if (myGNSS.getPVT()) {
        mySerial.print("Date/Time: ");
        mySerial.print(myGNSS.getYear());
        mySerial.print("-");
        mySerial.print(myGNSS.getMonth());
        mySerial.print("-");
        mySerial.print(myGNSS.getDay());
        mySerial.print(" ");
        mySerial.print(myGNSS.getHour());
        mySerial.print(":");
        mySerial.print(myGNSS.getMinute());
        mySerial.print(":");
        mySerial.println(myGNSS.getSecond());
        confirmedTime = myGNSS.getConfirmedTime();
      }
      else {
        mySerial.println("No PVT data received. Retrying...");
      }
    }
  }

  firstAwake = false;

Best wishes,
Paul

@crnicholson
Copy link
Author

Hi Paul,

The code now waits for the time to be validated before moving on. Although it now waits, after sleep the time comes out unvalidated. Is there something else I should check before going to sleep?

Cheers,
Charlie

@PaulZC
Copy link
Collaborator

PaulZC commented Dec 3, 2023

Hi Charlie,

I think that's all as expected. The GNSS knows that its internal clock will drift a little while it is asleep and not receiving satellite signals. So it's expected that it will need to re-sync / re-validate when it wakes.

Cheers,
Paul

@crnicholson
Copy link
Author

Hi Paul,

Thanks for all your help in this project. I think I figured it out, though by reading the u-blox docs over and over again. Before sleep, you need to check the satellites and if there is a 3D fix before sleep. You also need to configure the GNSS to only use GPS.

Here are snippets for anyone wondering:

static bool firstAwake = true;

  if (firstAwake) {
    int fixType = 0;
    int sats = 0;
    while ((fixType < 3) && (sats < 5)) {
      if (gps.getPVT()) {
        SerialUSB.print("Date/Time: ");
        SerialUSB.print(gps.getYear());
        SerialUSB.print("-");
        SerialUSB.print(gps.getMonth());
        SerialUSB.print("-");
        SerialUSB.print(gps.getDay());
        SerialUSB.print(" ");
        SerialUSB.print(gps.getHour());
        SerialUSB.print(":");
        SerialUSB.print(gps.getMinute());
        SerialUSB.print(":");
        SerialUSB.print(gps.getSecond());
        fixType = gps.getFixType();
        sats = gps.getSIV();
        SerialUSB.print(" Fix type: ");
        SerialUSB.print(fixType);
        SerialUSB.print(" Satellites: ");
        SerialUSB.println(sats);
      }
      else {
        SerialUSB.println("No PVT data received. Retrying...");
      }
    }
  }

  firstAwake = false;

and disable other GNSS modes

gps.enableGNSS(true, SFE_UBLOX_GNSS_ID_GPS); // Only enable GPS for proper software backup mode
gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_SBAS); // Disable SBAS
gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_GALILEO); // Disable Galileo
gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_BEIDOU); // Disable BeiDou
gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_IMES); // Disable IMES
gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_QZSS); // Disable QZSS
gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_GLONASS); // Disable GLONASS

and I needed to send with a wakeup interrupt:

void gpsSleep() {
  // Sleep for 10 seconds
  byte UBLOX_RXM_PMREQ[] = {
    0xB5, 0x62, 0x02, 0x41, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xF0, 0x38
  };
  sendI2CData(0x42, UBLOX_RXM_PMREQ, sizeof(UBLOX_RXM_PMREQ));
}

void gpsWakeup() {
  digitalWrite(wakeupPin, HIGH);
  delay(10);
  digitalWrite(wakeupPin, LOW);
}

void sendI2CData(byte address, byte data[], int length) {
  Wire.beginTransmission(address);  // Start transmission to device
  for (int i = 0; i < length; i++) {
    Wire.write(data[i]);  // Send data byte by byte
  }
  Wire.endTransmission();  // End transmission
}

and here is my all of my code in the end:

#include <SPI.h>                      // Needed for LoRa.
#include <LoRa.h>                     // LoRa.
#include <Wire.h>                     // Needed for I2C to GPS module.
#include <SparkFun_u-blox_GNSS_v3.h>  // http://librarymanager/All#SparkFun_u-blox_GNSS_v3.

// Settings:
#define comment "XXXXXX"
#define baudRate 115200        // SerialUSB baud rate.
#define syncWord 0xFB          // Only other devices with this sync word can receive your broadcast.
#define ssPin 38               // Arduino pin that connects to LoRa module. AKA csPin.
#define resetPin 5             // Arduino pin that connects to LoRa module.
#define DIO0Pin 2              // Arduino pin that connects to LoRa module.
#define frequency 433E6        // Frequency of your LoRa module.
#define spreadingFactor 12     // Choose this based on this https://forum.arduino.cc/t/what-are-the-best-settings-for-a-lora-radio/449528.
#define bandwidth 62.5E3       // Choose this based on this https://forum.arduino.cc/t/what-are-the-best-settings-for-a-lora-radio/449528.
#define wakeupPin 8
#define devMode                // Comment this out when you're ready to do the actual flight to save even more power.
// #define testCoord // Uncomment this to use test coordinates instead of actual coordinates.

struct __attribute__((packed)) dataStruct {
  float lat;
  float lon;
  long alt;
  long sats;
  long speed;
  long course;
  long txCount = 0;
  char text[50] = comment;
} transmittingData;

#ifdef testCoord
float lat = 42.316651;
float lon = -71.366030;
int alt = 96;
int sats = 9;
int speed = 60;
int course = 360;
#endif

SFE_UBLOX_GNSS gps;

void setup() {
  pinMode(wakeupPin, OUTPUT);
  digitalWrite(wakeupPin, LOW);

#ifdef devMode
  SerialUSB.begin(baudRate);
  while (!SerialUSB)
    ;  // Wait until SerialUSB is all good.
  SerialUSB.println("LoRa Tracker v2.1");
#endif

  LoRa.setPins(ssPin, resetPin, DIO0Pin);  // SS, reset, and DIO0. Has to be before LoRa.begin().

  if (!LoRa.begin(frequency)) {
#ifdef devMode
    SerialUSB.println("Starting LoRa failed!");
#endif
    while (1)
      ;
  }

  LoRa.setSyncWord(syncWord);
  LoRa.setSpreadingFactor(spreadingFactor);
  LoRa.setSignalBandwidth(bandwidth);
  LoRa.crc();

  Wire.begin();

  if (gps.begin() == false) {  // Connect to the u-blox module using Wire port.
#ifdef devMode
    SerialUSB.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing."));
#endif
    while (1)
      ;
  }

  gps.enableGNSS(true, SFE_UBLOX_GNSS_ID_GPS); // Make sure GPS is enabled (we must leave at least one major GNSS enabled!)
  gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_SBAS); // Disable SBAS
  gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_GALILEO); // Disable Galileo
  gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_BEIDOU); // Disable BeiDou
  gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_IMES); // Disable IMES
  gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_QZSS); // Disable QZSS
  gps.enableGNSS(false, SFE_UBLOX_GNSS_ID_GLONASS); // Disable GLONASS

  // gps.factoryDefault();  // Clear any saved configuration

  // Wait for 5 seconds
#ifdef devMode
  SerialUSB.println("Waiting for 5 seconds before loop.");
#endif
  for (int i = 0; i < 5; i++) {
    delay(1000);
#ifdef devMode
    SerialUSB.print(".");
#endif
  }

  // gps.hardReset();  // Hard reset - force a cold start
}

void loop() {
  static bool firstAwake = true;

  if (firstAwake) {
    int fixType = 0;
    int sats = 0;
    while ((fixType < 3) && (sats < 5)) {
      if (gps.getPVT()) {
        SerialUSB.print("Date/Time: ");
        SerialUSB.print(gps.getYear());
        SerialUSB.print("-");
        SerialUSB.print(gps.getMonth());
        SerialUSB.print("-");
        SerialUSB.print(gps.getDay());
        SerialUSB.print(" ");
        SerialUSB.print(gps.getHour());
        SerialUSB.print(":");
        SerialUSB.print(gps.getMinute());
        SerialUSB.print(":");
        SerialUSB.print(gps.getSecond());
        fixType = gps.getFixType();
        sats = gps.getSIV();
        SerialUSB.print(" Fix type: ");
        SerialUSB.print(fixType);
        SerialUSB.print(" Satellites: ");
        SerialUSB.println(sats);
      }
      else {
        SerialUSB.println("No PVT data received. Retrying...");
      }
    }
  }

  firstAwake = false;

#ifdef testCoord
  transmittingData.lat = lat;
  transmittingData.lon = lon;
  transmittingData.alt = alt;
  transmittingData.sats = sats;
  transmittingData.speed = speed;
  transmittingData.course = course;
  transmittingData.txCount++;
#endif

#ifndef testCoord
  if (gps.getPVT()) {
    transmittingData.lat = gps.getLatitude();
    transmittingData.lon = gps.getLongitude();
    transmittingData.alt = gps.getAltitude();
    transmittingData.sats = gps.getSIV();
    transmittingData.speed = 0;
    transmittingData.course = 0;
    transmittingData.txCount++;
    transmittingData.lat = transmittingData.lat / 10000000;  // Convert to float.
    transmittingData.lon = transmittingData.lon / 10000000;  // Convert to float.
    transmittingData.alt = transmittingData.alt / 1000;      // Convert to meters.
  }
#endif

#ifdef devMode
  SerialUSB.print("Lat: ");
  SerialUSB.print(transmittingData.lat, 6);
  SerialUSB.print(" Lon: ");
  SerialUSB.print(transmittingData.lon, 6);
  SerialUSB.print(" Date/Time: ");
  SerialUSB.print(gps.getYear());
  SerialUSB.print("-");
  SerialUSB.print(gps.getMonth());
  SerialUSB.print("-");
  SerialUSB.print(gps.getDay());
  SerialUSB.print(" ");
  SerialUSB.print(gps.getHour());
  SerialUSB.print(":");
  SerialUSB.print(gps.getMinute());
  SerialUSB.print(":");
  SerialUSB.println(gps.getSecond());
#endif

#ifdef devMode
  SerialUSB.println("Entering GPS low power mode.");
#endif

  // powerOff uses the 8-byte version of RXM-PMREQ - supported by older (M8) modules
  //gps.powerOff(sleepForSecs * 1000);

  // powerOffWithInterrupt uses the 16-byte version of RXM-PMREQ - supported by the M10 etc.
  // powerOffWithInterrupt allows us to set the force flag. The M10 integration manual states:
  // "The "force" flag must be set in UBX-RXM-PMREQ to enter software standby mode."
  // gps.powerOffWithInterrupt(sleepForSecs, 0, true);  // No (additional) wakeup sources. force = true

  gpsSleep();

#ifdef devMode
  SerialUSB.print("Transmitting with a packet size of: ");
  SerialUSB.println(sizeof(transmittingData));
#endif
  transmit(transmittingData);  // This is blocking and takes a tad bit more than 5 seconds.
#ifdef devMode
  SerialUSB.println("Done transmitting. Sleeping LoRa.");
#endif
  LoRa.sleep();  // The LoRa module wakes up automatically when the SPI interface is active.
  gpsWakeup(); // Wakeup GPS
}

void transmit(const struct __attribute__((packed)) dataStruct &data) {
  // Send the packet over LoRa.
  LoRa.beginPacket();
  LoRa.write((byte *)&data, sizeof(data));
  LoRa.endPacket();
}

void gpsSleep() {
  // Sleep for 10 seconds
  byte UBLOX_RXM_PMREQ[] = {
    0xB5, 0x62, 0x02, 0x41, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xF0, 0x38
  };
  sendI2CData(0x42, UBLOX_RXM_PMREQ, sizeof(UBLOX_RXM_PMREQ));
}

void gpsWakeup() {
  digitalWrite(wakeupPin, HIGH);
  delay(10);
  digitalWrite(wakeupPin, LOW);
}

void sendI2CData(byte address, byte data[], int length) {
  Wire.beginTransmission(address);  // Start transmission to device
  for (int i = 0; i < length; i++) {
    Wire.write(data[i]);  // Send data byte by byte
  }
  Wire.endTransmission();  // End transmission
}

Thanks again for all the help, Paul!

Cheers,
Charlie

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants