GrumpyOldPizza / ArduinoCore-stm32l0

Arduino Core for STM32L0
125 stars 67 forks source link

GNSS library - power consumption #90

Open SloMusti opened 5 years ago

SloMusti commented 5 years ago

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:

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 commented 5 years ago

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 commented 5 years ago

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.

GrumpyOldPizza commented 5 years ago

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.

SloMusti commented 5 years ago

@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.

GrumpyOldPizza commented 5 years ago

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 ?

SloMusti commented 5 years ago

@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.

GrumpyOldPizza commented 5 years ago

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.

ZaneL commented 4 years ago

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().

SloMusti commented 4 years ago

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

ZaneL commented 4 years ago

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.

SloMusti commented 4 years ago

@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.

ZaneL commented 4 years ago

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.

SloMusti commented 4 years ago

@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

ZaneL commented 4 years ago

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.

GrumpyOldPizza commented 4 years ago

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.

ZaneL commented 4 years ago

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.

GrumpyOldPizza commented 4 years ago

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 commented 4 years ago

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 ?

hallard commented 4 years ago

@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);
}
Nasserkhaled commented 3 years ago

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

hallard commented 3 years ago

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/

kriswiner commented 1 year ago

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

kriswiner commented 1 year ago

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.