ArduinoCore-stm32l0 icon indicating copy to clipboard operation
ArduinoCore-stm32l0 copied to clipboard

GNSS library - power consumption

Open SloMusti opened this issue 5 years ago • 23 comments

After extensive testing with Ublox M8Q and M8B with this library, I have come across a potential issue and am working on documenting it.

Setup: Murata ABZ module on a custom board. Prior to GNSS.setup() function the power consumption is 2uA of the system with STM32L0.stop(). After GNSS.setup() is called, done and GNSS.suspend is called, the power consumption is 30uA.

This 30uA is split as follows:

  • 2uA MCU/board power consumption
  • 13uA GPS backup power (disconnecting GPS or setting the backup power pin low removes this consumption)
  • 15uA - unknown

The test has been repeated by running a board without GPS installed and the power consumption is about 17uA, jumping to that after the GNSS.begin() has been completed. Thus it appears some internal STM32L0 periphery is turned on consuming this power and is not disabled when GPS is in sleep mode. Likewise if GNSS.end() is called, the consumption remains unchanged.

some pseudo code to tell what is happening in this graph (vertical scale is limited to 50uA for low-current visibility): Screenshot from 2019-06-22 22-19-22

    STM32L0.stop(5000); // 1.9uA
    GNSS.begin(); // about 50mA
    GNSS.suspend();
    STM32L0.stop(5000); // 29uA
    digitalWrite(GPS_BCK,LOW);
    pinMode(GPS_BCK,INPUT_PULLDOWN);
    STM32L0.stop(5000); // 15uA
    GNSS.end();
    STM32L0.stop(5000); // 15uA - expecting 1.9uA as in the beginning

This issue is important because the consumption could be lower in sleep between fixes as well as if GPS is disabled.

SloMusti avatar Jun 22 '19 20:06 SloMusti

Continuing the investigation, the power consumption is subject to the use of GNSS.begin() and GNSS.end() functions. Currently it appears that calling begin more then once results in such case as well as calling the function end just once. See minimal code examples below for testing. Hardware used is a Barebone murata board with no periphery attached.

#include "STM32L0.h"
#include "GNSS.h"

void setup() {
  //copy pase code from below for different tests
  // 1uA in stop mode
  GNSS.begin(Serial1, GNSS.MODE_UBLOX, GNSS.RATE_1HZ);
  STM32L0.stop(1000);
  GNSS.suspend();
  STM32L0.stop(1000);

}

void loop() {
}

/* Results in 1uA in stop
GNSS.begin(Serial1, GNSS.MODE_UBLOX, GNSS.RATE_1HZ);
STM32L0.stop(1000);
GNSS.suspend();
STM32L0.stop(1000);
GNSS.suspend();
STM32L0.stop();
*/

/* Results in 15uA in stop
GNSS.begin(Serial1, GNSS.MODE_UBLOX, GNSS.RATE_1HZ);
STM32L0.stop(1000);
GNSS.suspend();
STM32L0.stop(1000);
GNSS.begin(Serial1, GNSS.MODE_UBLOX, GNSS.RATE_1HZ);
STM32L0.stop(1000);
GNSS.suspend();
STM32L0.stop();
*/

/* Results in 1uA in stop
GNSS.begin(Serial1, GNSS.MODE_UBLOX, GNSS.RATE_1HZ);
STM32L0.stop(1000);
GNSS.suspend();
STM32L0.stop(1000);
//GNSS.begin(Serial1, GNSS.MODE_UBLOX, GNSS.RATE_1HZ);
STM32L0.stop(1000);
GNSS.suspend();
STM32L0.stop();
*/

/* Results in 15uA in stop
GNSS.begin(Serial1, GNSS.MODE_UBLOX, GNSS.RATE_1HZ);
STM32L0.stop(1000);
GNSS.suspend();
STM32L0.stop(1000);
GNSS.end();
STM32L0.stop();
*/

/* Results in 15uA in stop
GNSS.begin(Serial1, GNSS.MODE_UBLOX, GNSS.RATE_1HZ);
STM32L0.stop(1000);
GNSS.end();
STM32L0.stop();
*/

/* Results in 15uA in stop
GNSS.begin(Serial1, GNSS.MODE_UBLOX, GNSS.RATE_1HZ);
STM32L0.stop(1000);
GNSS.begin(Serial1, GNSS.MODE_UBLOX, GNSS.RATE_1HZ);
STM32L0.stop(1000);
GNSS.suspend();
STM32L0.stop();
*/

/* Results in 1uA in stop
GNSS.begin(Serial1, GNSS.MODE_UBLOX, GNSS.RATE_1HZ);
STM32L0.stop(1000);
GNSS.suspend();
STM32L0.stop(1000);
GNSS.resume();
STM32L0.stop(1000);
GNSS.suspend();
STM32L0.stop(1000);
GNSS.resume();
STM32L0.stop(1000);
GNSS.suspend();
STM32L0.stop();
*/

SloMusti avatar Jun 28 '19 18:06 SloMusti

I have traced down this problem to insufficient handing of UART end() function. https://github.com/GrumpyOldPizza/ArduinoCore-stm32l0/blob/555b4deac26d2a2b4cb6f7b46ae8dc4135461426/cores/arduino/Uart.cpp#L107 Uart disable should be destroy and then the power consumption is correctly restored to minimal.

SloMusti avatar Jun 30 '19 17:06 SloMusti

Something odd is going on with your custom board (and perhaps the usage of the GNSS library). We see about 7uA for our setups with GNSS.suspend() and STM32L0.stop().

GNNS.suspend() will turn off the GNSS, but keep the backup power alive. This should be 7uA to 15uA as per uBLOX documentation.

GNSS.end() should take the backup power away, which means the whole system should be at 2uA.

GrumpyOldPizza avatar Jul 03 '19 11:07 GrumpyOldPizza

@GrumpyOldPizza thank you for the reply. We have managed to exclude the hardware largely, as we see the power consumption increase if there is no GPS connected at all. But as mentioned above, the UART disable to destroy change has resolved the issue.

SloMusti avatar Jul 03 '19 11:07 SloMusti

The stm32l0_uart_destroy() is not applicable there. It's really there to implement a object destructor, which at the end of the day is a NOP more or less. So there is something different going on.

Do you have a simple reproducer without GNSS connected, something I can look at with a power measuring tool ?

GrumpyOldPizza avatar Jul 03 '19 11:07 GrumpyOldPizza

@GrumpyOldPizza sure, you can use the code above (https://github.com/GrumpyOldPizza/ArduinoCore-stm32l0/issues/90#issuecomment-506834354) to verify this power consumption as the bare-minimum sketch.

SloMusti avatar Jul 03 '19 12:07 SloMusti

You said you had code that showed the issue without GNSS connected, i.e. without GNSS library involved. That is the example I am after. You code in that comment involves always the GNSS class. If you use that without hardware connected, it will spin in the background and consume power.

GrumpyOldPizza avatar Jul 03 '19 12:07 GrumpyOldPizza

I can confirm this same bug using the Gnat variant. The GNSS.end() function still leaves the device pulling ~15uA when stopped. When changing that uart line from disable to destroy, calling GNSS.end() correctly returns the current draw back to around 2uA.

I'm also finding that I have to sprinkle in while (GNSS.busy()) { } between each GNSS function or else many of them don't work including suspend().

ZaneL avatar Feb 11 '20 06:02 ZaneL

@ZaneL did you try to do GNSS.begin after the end function. I was unable to get that to work without a reset.

SloMusti avatar Feb 11 '20 06:02 SloMusti

Uh oh...looks like you are right. Once the handle is deleted, begin() no longer works. I'll look into it tomorrow and see if I can figure out a proper fix. There are a few wonky things going on with my GNSS unit and this library...it mostly works but I'm still seeing some bizarre behavior.

ZaneL avatar Feb 11 '20 07:02 ZaneL

@ZaneL feel free to look at our implementation here: https://github.com/IRNAS/smartparks-rhino-tracker-firmware

Key thing is to not use TimerMillis and then everything should generally work, as long as end is not called.

SloMusti avatar Feb 11 '20 07:02 SloMusti

How are you getting the device down to 15uA when suspended? Even when I suspend the GNSS unit it pulls 30uA, rather than 15. I've tried copying your GPS routines from your code (rather than using suspend) and it's still pulling 30uA. It's driving me nuts! It seems like some weird issue with the UART library, but there is tons of complex DMA code in there, so I can't figure it out.

ZaneL avatar Feb 14 '20 19:02 ZaneL

@ZaneL yes, tricky to get there. Please reach out to me privately if you can share the schematic of your system and we can figure out what is going on. Attached is an example test code with which you should get to the right power consumption. GNSS_external_rhino_test_15ua.zip

SloMusti avatar Feb 14 '20 20:02 SloMusti

Thanks so much, but is something missing in that code?? I don't see a GNSS suspend command or a low digital write to turn off the GPS.

ZaneL avatar Feb 14 '20 21:02 ZaneL

Couple of things here. You got to define a bunch of GPIOs in your specific variant files:

STM32L0_CONFIG_PIN_GNSS_ENABLE STM32L0_CONFIG_PIN_GNSS_BACKUP STM32L0_CONFIG_PIN_GNSS_PPS

Without those the code does not attempt to properly put the UART into lowest power mode, especially if STM32L0_CONFIG_PIN_GNSS_ENABLE is missing.

I am not sure a ::begin() ::end() ::begin() sequence will work properly. Wasn't meant to do that without STM32L0_CONFIG_PIN_GNSS_BACKUP defined. ::begin() ::begin() ::end() is not supposed to work, but there is not internal check that would prevent a massive scewup.

::suspend() / ::resume() will not work without STM32L0_CONFIG_PIN_GNSS_ENABLE defined. There is some tricky state management when you first have to tell the UBLOX part that it's supposed to save it's state before you can turn it off.

The internal UART handling (i.e. enable/disable) is conditional upon the STM32L0_CONFIG_PIN_GNSS_ENABLE define. So unless you have that, the code assumes that you cannot enable/disable the power to the UBLOX part, and hence keeps the UART alive.

GrumpyOldPizza avatar Feb 15 '20 12:02 GrumpyOldPizza

Thanks for the info! My issue is that I'm not using a custom board. I'm using a Gnat and running this code puts it at ~30uA:

https://raw.githubusercontent.com/kriswiner/CMWX1ZZABZ/master/Gnat/AssetTracker_Gnat.v02.ino

I'm not sure why it isn't properly suspending the GNSS...when I build the code and load it onto the Gnat using three different computers, I still have the same problem. I've even tried using an older version of the library (0.9) and selecting the different compiler optimization settings.

ZaneL avatar Feb 17 '20 03:02 ZaneL

The sketch you reference to points to a BMA400. If you have that part active you'll have of course a higher current consumption.

GrumpyOldPizza avatar Feb 17 '20 13:02 GrumpyOldPizza

Some of the pins were not restored to their default state at GNSS.end(). This should be fixed now. Mind checking out the github directly to verify ?

GrumpyOldPizza avatar May 02 '20 11:05 GrumpyOldPizza

@ZaneL gnat basic sketch should consume 2 to 4uA.

Try the following sketch on gnat

#include <STM32L0.h>
#include "LoRaWAN.h"

#define myLed1    10 // blue led
#define VbatMon   A1
#define Vbat_en    2

float VDDA, VBUS, Temperature, VBAT;
uint32_t UID[3] = {0, 0, 0};
char buffer[32];

void setup() 
{
  pinMode(myLed1, OUTPUT);
  digitalWrite(myLed1, HIGH);   // start with led1 off, since active LOW

  Serial.begin(115200);

  while(!Serial && millis()<5000) {
    digitalWrite(myLed1, LOW); 
    delay(25);
    digitalWrite(myLed1, HIGH); 
    delay(225);
  }
  Serial.println("Serial enabled!");
 
  pinMode(Vbat_en, OUTPUT);

  pinMode(VbatMon, INPUT);    // ADC for reading battery voltage
  analogReadResolution(12);   // use 12-bit ADC

  STM32L0.getUID(UID);
  Serial.print("STM32L0 MCU UID = 0x"); Serial.print(UID[0], HEX); Serial.print(UID[1], HEX); Serial.println(UID[2], HEX); 
  LoRaWAN.getDevEui(buffer, 18);
  Serial.print("STM32L0 Device EUI = "); Serial.println(buffer); 

  uint8_t c = 5;
  while (c--) {
    digitalWrite(myLed1, LOW); 
    delay(50);
    digitalWrite(myLed1, HIGH); 
    delay(950);
  }

}

void loop() 
{
  // Internal STM32L0 functions
  VDDA = STM32L0.getVDDA();
  VBUS = STM32L0.getVBUS();
  Temperature = STM32L0.getTemperature();
  
  Serial.print("VDDA = "); Serial.println(VDDA, 2); 
  if(VBUS ==  1)  Serial.println("USB Connected!"); 
  Serial.print("STM32L0 MCU Temperature = "); Serial.println(Temperature, 2);

  // LiPo battery monitor
  digitalWrite(Vbat_en, HIGH); // enable monitor
  VBAT = 1.27f * VDDA * ((float) analogRead(VbatMon)) / 4096.0f; // read ADC
  Serial.print("VBAT = "); Serial.print(VBAT, 2); Serial.println(" V");
  digitalWrite(Vbat_en, LOW); // disable monitor

  digitalWrite(myLed1, LOW);   // toggle blue led on
  delay(100);                  // wait 100 millisecond
  digitalWrite(myLed1, HIGH);  // toggle blue led off
  delay(100);
  
  STM32L0.stop(5000);
}

hallard avatar May 02 '20 17:05 hallard

Hello Mr @hallard I'm trying to make a function to read the battery level, can you please explain what hardware connection I need to do to read the battery level using your code above, Is it connecting positive part of the battery to A1? and also what does Vbat_en do?

I'm sorry for the basic question, I'm totally new to this

Nasserkhaled avatar Aug 23 '20 06:08 Nasserkhaled

Gnat has 2 pins to monitor battery, one to enable reading (so disabled when not reading to save battery) and one analog one to read the value. the enabled mode is done by 2 mosfets

check schematic on tindie part with A1 and D12 https://www.tindie.com/products/tleracorp/gnat-loragnss-asset-tracker/

hallard avatar Sep 19 '20 09:09 hallard

Latest results from the Gnat v.03d; Gnat average ppk 10s

kriswiner avatar May 10 '23 19:05 kriswiner

Data taken with MCU clock at 4 MHz using a 25 mm x 25 mm active patch antenna with EPHE fix criterion set to 30. So after the cold start and second hot start where ephemeris data download is completed the average current for the subsequent 10 hot starts (at five minute intervals) is 5.59 mA or ~0.56 mA per fix. Sleep current (MCU in STOP mode, GNSS backup enabled) averages 15.5 uA. When the GNSS duty cycle is a more realistic 2 hours the average current is expected (and has been measured over several weeks) to be (5.59 - 0.016)mA * 5/120 = 232 + 15.5 uA ~ 250 uA.

kriswiner avatar May 10 '23 19:05 kriswiner