ARMmbed / mbed-os-example-lorawan

Simple LoRaWAN example application for mbed OS
Apache License 2.0
78 stars 92 forks source link

Can not reach low power state when radio is declared outside of main() #227

Open charlesmodrich-tc opened 3 years ago

charlesmodrich-tc commented 3 years ago

Description of defect

Inability to enter sleep mode (or at least reach expected sleep mode average current) after calling static LoRaWANInterface lorawan(radio); when working with NRF52840-DK + SX1262MB2xAS mbed shield

even when building with Develop profile. Results in average "sleep" current of ~6.5mA. Uncommenting all code before main() (except for above line) and removing all code except sleep_for within main() yields expected sleep current (~14uA).

Target(s) affected by this defect ?

NRF52840-DK

Toolchain(s) (name and version) displaying this defect ?

Mbed Studio 1.4.0

What version of Mbed-os are you using (tag or sha) ?

6.3.0

What version(s) of tools are you using. List all that apply (E.g. mbed-cli)

Mbed CLI 2

How is this defect reproduced ?

Set up 2 NRF52840 DK's: one with PowerProfilerKit, another with SX1262MB2xAS mbed shield. Connect both to PC via microUSB (not nRF USB). Have PPK DK power LoRa shield DK via External supply, switch power on LoRaDK ON, nRF ONLY switch, VEXT->nRF switch to ON. Load up nRF Connect utility for the power profiler kit application, and enable DUT power and monitoring.

Use the default example for this sketch, but remove most of the code such that all that remains looks as below:

#include <stdio.h>

#include "lorawan/LoRaWANInterface.h"
#include "lorawan/system/lorawan_data_structures.h"
#include "events/EventQueue.h"

// Application helpers
#include "SX126X_LoRaRadio.h"
SX126X_LoRaRadio radio(D11,//MOSI
                       D12,//MISO
                       D13,//SCK
                       D7,//CS/NSS
                       A0,//RESET
                       D5,//DIO1
                       D3,//BUSY
                       A1,//FREQ_SEL
                       A2,//DEV_SEL
                       A3,//XTAL_SEL
                       D8);//ANT_SW

#include "mbed.h"

//DigitalOut led1(LED1);
using namespace events;
uint8_t tx_buffer[30];
uint8_t rx_buffer[30];
#define TX_TIMER                        20000
#define MAX_NUMBER_OF_EVENTS            10
#define CONFIRMED_MSG_RETRY_COUNTER     3
#define PC_9                            0

static EventQueue ev_queue(MAX_NUMBER_OF_EVENTS *EVENTS_EVENT_SIZE);
static void lora_event_handler(lorawan_event_t event);
static lorawan_app_callbacks_t callbacks;

static LoRaWANInterface lorawan(radio);

int main()
{       
rtos::ThisThread::sleep_for(1000);
}

change the following in mbed_app.json:

"target_overrides": { "*": { "platform.stdio-convert-newlines": true, "platform.stdio-baud-rate": 115200, "platform.default-serial-baud-rate": 115200, "lora.over-the-air-activation": true, "lora.duty-cycle-on": false, "lora.phy": "US915", "lora.device-eui": "{ 0x00, 0x80, 0xA0, 0xD0, 0x09, 0xEA, 0x82, 0x37 }", "lora.application-eui": "{ 0x70, 0xB3, 0xD5, 0x7E, 0xD0, 0x03, 0xE4, 0x02 }", "lora.application-key": "{ 0xEE, 0xB6, 0x72, 0x7D, 0xEB, 0x52, 0x08, 0x1C, 0x2E, 0x32, 0xC4, 0x3B, 0xB8, 0x96, 0x86, 0x6D }", "target.components_add": ["SX126X"], "lora.app-port": 2, "lora.fsb-mask": "{0xFF00, 0x0000, 0x0000, 0x0000, 0x0002}" },

"macros": ["MBEDTLS_USER_CONFIG_FILE=\"mbedtls_lora_config.h\"", "MBED_TICKLESS=1"]

Now, compile, upload and compare the average currents when static LoRaWANInterface lorawan(radio); is commented, and uncommented. For me, results for commented are ~14uA average sleep current over 52ms period, while uncommented shows an average sleep current of approximately 6.5mA over 52ms period.

ciarmcom commented 3 years ago

Thank you for raising this detailed GitHub issue. I am now notifying our internal issue triagers. Internal Jira reference: https://jira.arm.com/browse/IOTOSM-3922

evandavey commented 3 years ago

@charlesmodrich-tc I have similar issues in #222. Did you find a solution? For tcxo control, I am exploring modifying the set_operation_mode call in the driver i.e. set to 0 when RF_OPMODE_SLEEP otherwise 1. But that still leaves a high residual ~300uA.

charlesmodrich-tc commented 3 years ago

Unfortunately no, I wasn't able to find a solution. Ultimately I was forced to move away from Mbed OS for this reason since I wasn't able to get the low current performance all of my devices would require.

--

Charles Modrich | Senior Hardware Engineer TeraCode | 405 Cochituate Road | Suite 205 | Framingham MA 01701 M: 847-951-7543 | F: 781-207-0724 http://www.teracode.com | LinkedIn https://www.linkedin.com/in/charlesmodrich/

On Sat, Jun 26, 2021 at 4:58 AM Evan Davey @.***> wrote:

@charlesmodrich-tc https://github.com/charlesmodrich-tc I have similar issues in #222 https://github.com/ARMmbed/mbed-os-example-lorawan/issues/222. Did you find a solution? For tcxo control, I am exploring modifying the set_operation_mode call in the driver i.e. set to 0 when RF_OPMODE_SLEEP otherwise 1. But that still leaves I high residual.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ARMmbed/mbed-os-example-lorawan/issues/227#issuecomment-868978263, or unsubscribe https://github.com/notifications/unsubscribe-auth/ARXQAUS7K7DS4CDEOWWATCDTUWQDTANCNFSM435LKRXQ .

evandavey commented 3 years ago

@charlesmodrich-tc - very frustrating.

charlesmodrich-tc commented 3 years ago

It really is! I spent a lot of time and hope on this before giving up.

evandavey commented 3 years ago

Yes, have been in the same boat! Having to revert back to the manufacturer SDKs is its own path of pain given all the messy #defines to support multiple targets and tight coupling of business logic to SDK logic. It's a shame support resources for MBED seem to have dropped off recently and it is stuck being half a corporate project and half an open source project with neither seeming to have particularly strong momentum.

evandavey commented 2 years ago

@charlesmodrich-tc - I have been doing some more debugging, this time on a Murata ISJ dev board (link). This is a STM32L072 + SX126X radio. With the Murata supplied AT firmware, I measure this board at the expected 2uA, so I know this is the baseline. I am using a new example from scratch, but using the master branch of mbed-os (as I needed to make some non power related changes to the SX126X driver).

With the code below, I am able to achieve an average of 50uA. Interestingly, if I power up the board (which puts the radio to sleep), then re-upload the code with the radio.sleep() line commented out, current falls to 11uA, which is very similar to the 14uA you measured. I think this is related to GPIO/peripheral configuration in sleep but I haven't been able to figure out why, or where I am losing 9uA relative to the AT firmware.

mbed_app.json

{
  "requires": [
    "bare-metal",
    "events",
    "storage",
    "spif-driver",
    "lora",
    "mbedtls",
    "sx1276-lora-driver",
    "SX126X-lora-driver"
  ],
  "target_overrides": {
    "*": {
      "target.macros_add": ["MBED_TICKLESS"],
      "target.lpticker_lptim": 0
    },
    "MURATA_TYPE1SJ_EVB": {
      "SX126X-lora-driver.freq-support": "MATCHING_FREQ_915",
      "SX126X-lora-driver.device-variant": "SX1262"
    }
  }
}

main.cpp

#include "mbed.h"
#include "events/EventQueue.h"
#include "radio.h"
#include "SX126X_LoRaRadio.h"

#define MAX_NUMBER_OF_EVENTS 20

static EventQueue ev_queue(MAX_NUMBER_OF_EVENTS *EVENTS_EVENT_SIZE);

SX126X_LoRaRadio radio(LORA_SPI_MOSI,
                       LORA_SPI_MISO,
                       LORA_SPI_SCLK,
                       LORA_SPI_CS,
                       LORA_RESET,
                       LORA_DIO1,
                       LORA_BUSY,
                       LORA_FREQ_SELECT,
                       LORA_DEVICE_SELECT,
                       LORA_CRYSTAL_SELECT,
                       LORA_ANT_SWITCH);

int main(void)
{

    radio.sleep();
    ev_queue.dispatch_forever();
}
jeromecoutant commented 2 years ago
  "target.lpticker_lptim": 0

This is not tested, not validated, so not recommended... Keep the default configuration

evandavey commented 2 years ago

@jeromecoutant - this line saves about 30uA . At the moment my objective is to just get current down to 2uA with nothing running, then start adding back functionality to be able to do a full LoRaWAN test so I'll admit I've been trying things such as that line without fulling understanding the impact. The 2uA firmware is written in https://www.st.com/en/embedded-software/i-cube-lrwan.html but I find the structure such a headache to deal with.

hallard commented 2 years ago

That's interesting because I wanted to post a comment this night but went to bed before. In the meantime conversation has new comments. I'm currently switching to mbed for my lorawan E5 and RAK3172 board because it's really nice simple with lot of features and this lorawan example works. But my next step is to go to low power testing and seing this makes me feel inconfortable because the goal of LoRaWAN device is to be low lower. I'm going to 1.5uA with old murata one on custom code without mbed so 11uA or 50uA is not an option on my side. Would be interesting to have @jeromecoutant, or @MarceloSalazar on this point, in the meantime I will give some tries on my boards.

hallard commented 2 years ago

The 2uA firmware is written in https://www.st.com/en/embedded-software/i-cube-lrwan.html but I find the structure such a headache to deal with.

I really don't understand how can this structure give you headache (kidding), but I'm glad I don't feel alone on this ST structure 👍

evandavey commented 2 years ago

@hallard - I have some E5's on order along with the https://www.seeedstudio.com/LoRa-E5-mini-STM32WLE5JC-p-4869.html. Is there a target already that works with this chip and can give low power? I don't care what chip I use at this point as long as I can get something functional - don't know if there is anyone using MBED lorawan in the real world.

As to i-cube-lrwan, honestly my adaptions are relatively minor and I could probably mod it to get it working, but :scream: the business logic would be so tightly coupled with that structure I couldn't deliver in good conscience.

hallard commented 2 years ago

@hallard - I have some E5's on order along with the https://www.seeedstudio.com/LoRa-E5-mini-STM32WLE5JC-p-4869.html. Is there a target already that works with this chip and can give low power?

Sure take a look there (not tested on Low Power yet )

for STM32WL i-cude-lrwan has been replaced by STM32Cube MCU Package for STM32WL series(SDK) may be it was too simple. More information here but don't dream, file and folder architecture won't remove you any headache

evandavey commented 2 years ago

@hallard thanks for the links. Not Low Power tested is the concern. I've tried the Type ABZ (custom build and DISCO LRWAN1), Type 1SJ and STM32L0/RFM95 (draguino LNS50 node) so far with no luck for easy low power < 10uA (and even struggling for <100uA). I guess the trade-off for using a simplified framework like MBED is you lose that fine-grain control to library implementations. Still, it is such a common problem I'd hope there would be sufficient community support / documentation to get things running at least with a viable battery life for a low power long-term node (i.e. 1 year plus). Especially for dev boards like the DISCO LRWAN1.

hallard commented 2 years ago

Worth looking at this sample code https://os.mbed.com/users/kenjiArai/code/Check_DeepSleep_os6/ For sure need to be adapted for STM32 target (Murata STM32L0 in your case) but could be an excellent starting point because on some boards he got 2uA which is both our target :-)

evandavey commented 2 years ago

@hallard I have attempted something like the "LowPowerConfiguration" function before. Firstly, I don't think "AHBENR" exists for a L0 target (so I switched for "IOPENR"). Secondly, I think the presence of the radio messes with this code as I have experienced worse performance after calling such a function following radio.sleep (as in mA). Otherwise, I don't see anything obvious in this code that is doing anything I haven't tried before. I haven't found anything documented as to what happens to peripherals/gpio when deep sleep is called.

evandavey commented 2 years ago

BTW, with the exact some code and the LNS50 as a target (so swapping the SX126X driver for SX1276) I see 500uA. But I am not convinced the pin mappings for the SX1276 are correct for the RFM95 module (https://github.com/dragino/Lora/blob/master/LSN50/v2.0/LoRa%20ST%20Sensor%20Node%20v2.0.sch.pdf and https://raw.githubusercontent.com/dragino/Lora/master/LoRaST/v1.0/LoRa%20ST%20v1.0_Sch.pdf).

hallard commented 2 years ago

Ok done some testing on my LoRa-E5 board, after removing all code of the sample (and all GPIO config such as LED and Button) here we go

image

1.43uA in deep sleep mode, perfect !!

mbed_app.json

{
    "requires": ["bare-metal", "events", "lora", "mbedtls"],
    "target_overrides": {
        "*": {
            "target.printf_lib": "std"
        }
    }
}

code

//  Include --------------------------------------------------------------------
#include "mbed.h"

//  Constructor ----------------------------------------------------------------
//DigitalIn   my_sw(BUTTON1);
//DigitalOut  myled(LED1,1);
static BufferedSerial pc(CONSOLE_TX, CONSOLE_RX, 115200);

//------------------------------------------------------------------------------
//  Control Program
//------------------------------------------------------------------------------
int main()
{
    while (true) {
//        myled = 0 ; // ON
//        ThisThread::sleep_for(1s);
//        myled = 1 ; // Off
//        ThisThread::sleep_for(2s);
        hal_deepsleep();
    }
}

Please not that using ThisThread::sleep_for(2s) instead sleep at 3mA

evandavey commented 2 years ago

@hallard - great, sounds promising. Is this the Seeed Studio dev board or one of your own? I think with bare metal you need to use thread_sleep_for rather than ThisThread::sleep_for for the sleep manager to work (or events.dispatch_forever() like I used). What's happening with the radio? Is it defaulting to off for this board? What are the settings for PeripheralPins.c? What is the system clock configuration?

This code gives me ~1.14mA on the Type 1SJ dev board (which is most likely the radio being on).

hallard commented 2 years ago

@evandavey yes it's my basic own board, pin definitions are here and the radio is included into the STM32WL chip.

hal_deepsleep() may be not the best because I'm not sure we can wake up with a timer, but I'm wondering how the difference between with thread_sleep_for()

As you can see as soon as I'm using this code consumption in sleep goes from 1.5uA to 2.9mA

int main()
{
    while (true) {
          thread_sleep_for(5000);
    }
}

image

evandavey commented 2 years ago

@hallard - I might be wrong on thread_sleep_for https://forums.mbed.com/t/clarification-on-os-6-empty-vs-os-6-bare-metal-example-programs-1-0-0/8950 and it is equivalent and doesn't call the sleep manager. What do you get using events.dispatch_forever()? Maybe the radio defaults to its sleep state on the STM32WL?

hallard commented 2 years ago

Same thing with

int main()
{
    EventQueue queue;
    queue.dispatch_forever();
}

mbed_app.json => 2.9mA

{
    "requires": ["bare-metal", "events", "lora", "mbedtls"],
    "target_overrides": {
        "*": {
            "target.printf_lib": "std"
        }
    }
}
evandavey commented 2 years ago

Same code but not using bare-metal?

hallard commented 2 years ago

Sorry same code, same config so yes bare-metal

evandavey commented 2 years ago

Sorry, I meant, what do you get running the same code but not in bare-metal?

hallard commented 2 years ago

Here to follow up my tests

int main()
{
    EventQueue queue;
    queue.dispatch_forever();
}

mbed_app.json => 6.5mA

{
    "requires": ["bare-metal", "events", "lora", "mbedtls"],
    "target_overrides": {
        "*": {
            "target.printf_lib": "std",
            "target.macros_add": ["MBED_TICKLESS"],
            "target.lpticker_lptim": 0
        }
    }
}

mbed_app.json => 2.9mA

{
    "requires": ["bare-metal", "events", "lora", "mbedtls"],
    "target_overrides": {
        "*": {
            "target.printf_lib": "std",
            "target.macros_add": ["MBED_TICKLESS"]
        }
    }
}

mbed_app.json => 2.9mA

{
    "requires": ["bare-metal", "events", "lora", "mbedtls"],
    "target_overrides": {
        "*": {
            "target.printf_lib": "std"
        }
    }
}
evandavey commented 2 years ago

So "target.lpticker_lptim": 0 increases 3.6mA?

For not bare-metal I mean

int main()
{
    EventQueue queue;
    queue.dispatch_forever();
}
{
    "target_overrides": {
        "*": {
            "target.printf_lib": "std"
        }
    }
}
hallard commented 2 years ago

and here we go without bare-metal (sorry took some time to compile), same results, and target.lpticker_lptim": 0 increase 3.6mA

int main()
{
    EventQueue queue;
    queue.dispatch_forever();
}

mbed_app.json => 6.5mA

{
    "target_overrides": {
        "*": {
            "target.printf_lib": "std",
            "target.macros_add": ["MBED_TICKLESS"],
            "target.lpticker_lptim": 0
        }
    }
}

mbed_app.json => 2.9mA

{
    "target_overrides": {
        "*": {
            "target.printf_lib": "std",
            "target.macros_add": ["MBED_TICKLESS"]
        }
    }
}

mbed_app.json => 2.9mA

{
    "target_overrides": {
        "*": {
            "target.printf_lib": "std"
        }
    }
}
evandavey commented 2 years ago

I've done a little more testing myself.

3.4ma

int main()
{
    EventQueue queue;
    queue.dispatch_forever();
}

2.4ma

#include "SX126X_LoRaRadio.h"
SX126X_LoRaRadio radio(LORA_SPI_MOSI,
                       LORA_SPI_MISO,
                       LORA_SPI_SCLK,
                       LORA_SPI_CS,
                       LORA_RESET,
                       LORA_DIO1,
                       LORA_BUSY,
                       LORA_FREQ_SELECT,
                       LORA_DEVICE_SELECT,
                       LORA_CRYSTAL_SELECT,
                       LORA_ANT_SWITCH);

int main()
{
    EventQueue queue;
    queue.dispatch_forever();
}

70uA

#include "SX126X_LoRaRadio.h"
SX126X_LoRaRadio radio(LORA_SPI_MOSI,
                       LORA_SPI_MISO,
                       LORA_SPI_SCLK,
                       LORA_SPI_CS,
                       LORA_RESET,
                       LORA_DIO1,
                       LORA_BUSY,
                       LORA_FREQ_SELECT,
                       LORA_DEVICE_SELECT,
                       LORA_CRYSTAL_SELECT,
                       LORA_ANT_SWITCH);

int main()
{
    EventQueue queue;
    radio.sleep();
    queue.dispatch_forever();
}
{
  "requires": [
    "bare-metal",
    "events",
    "lora",
    "mbedtls",
    "SX126X-lora-driver"
  ],
  "target_overrides": {
    "*": {
      "target.macros_add": ["MBED_TICKLESS"],
      "target.reset_gpio_on_init": 1
    },
    "MURATA_TYPE1SJ_EVB": {
      "SX126X-lora-driver.freq-support": "MATCHING_FREQ_915",
      "SX126X-lora-driver.device-variant": "SX1262"
    }
  }
}
evandavey commented 2 years ago

I am pretty sure that in both bare-metal and RTOS queue->dispatch_forever() should go into deep sleep when idle (and this is what I am experiencing). Can you turn on sleep tracing to see if anything is blocking deep sleep?

hallard commented 2 years ago

May be the Serial port console? do you know how to be sure that any serial is disabled?

evandavey commented 2 years ago

You could try: mbed_file_handle(STDIN_FILENO)->enable_input(false) Console and Deep Sleep

hallard commented 2 years ago

With radio driver

#include "mbed.h"
#include "STM32WL_LoRaRadio.h"
STM32WL_LoRaRadio radio;

int main()
{
    EventQueue queue;
    mbed_file_handle(STDIN_FILENO)->enable_input(false);
    radio.sleep();
    queue.dispatch_forever();
}

mbed_app.json => 6mA average

{
    "requires": ["bare-metal", "events", "lora", "mbedtls", "stm32wl-lora-driver"],
    "target_overrides": {
        "*": {
            "target.printf_lib": "std"
        }
    }
}

image

evandavey commented 2 years ago

And you're using a develop or release build profile (not debug?)

hallard commented 2 years ago

ah ah good catch, it's a debug, compiling above code as release and here we go

Avg 1.7uA and peak are 4mA for approx 2ms

image

evandavey commented 2 years ago

OK, that's great!! And is it the same without the radio.sleep() call?

Perhaps we need to compare the differences between "STM32WL_LoRaRadio.h" and "SX126X_LoRaRadio.h".

Or just use STM32WL chips! :laughing: Still waiting for mine to arrive.

hallard commented 2 years ago

yes same things without radio.sleep(), I already had 1.5uA with murata ABZ (STM32L0 + SX126x) with another framework so it's do able and may be your issue is related to mbed. Do you have same issue on mbed 5 where ABZ is officially supported?

evandavey commented 2 years ago

What framework? I have tried stm32duino / arduino-lmic but similar issues. I am actually using a Type 1SJ for these tests. My LRWAN1 board had to be repurposed in a field test and I haven't re-checked on my custom board. But experiencing similar issues on the Draguino LNS50. Definetely there is some config issues with the framework, but they should be solveable with support....

Please post your results when you have done a end-to-end test with LoRaWAN active.

WIll let you know how the SeeedStudio E5 dev board goes when it arrives (looks like they have landed in Sydney so hopefully early next week).

hallard commented 2 years ago

using this one https://github.com/GrumpyOldPizza/ArduinoCore-stm32l0 By the way do your board manage USB because I remember having issues with that, very tricky, needed to disable some USB stuff and more, needed to reset (start the board) without usb connected, removing USB cable once started prevented to go to low power, got some headache before discovering that. Likely my debug mode instead of release, thanks for the trick.

Hope you will be able to play with your E5 Dev Boards, absolutely amazing, everything is very well designed, you can disable everything and even a jumper present to be able to measure current consumption, it's the best LoRa Board Dev Kit I've ever seen and price rocks and RS485 as cherry on the cake

evandavey commented 2 years ago

I used that GrumpyOldPizza framework very early on but ran into LoRa implementation issues, can't remember exactly what now, and gave up. Could have been related to early days of them supporting AU915. If you have any code you can share that would be greatly appreciated.

I experience a similar issue with the Draguino LNS50 where I have to use a ST-Link and need it unplugged for low power. Have tested the Type 1SJ both with and without the (on-board) debugger connected and it makes no difference (and the ~45uA I am seeking is too low for that anyway).

hallard commented 2 years ago

Here part of my code working for GrumpyOldPizza (with git version since some API have changed since)


#include "STM32L0.h"
#include "LoRaWAN.h"
#include "TimerMillis.h"

// Transmit period in s
#define LORA_TRANSMIT_PERIOD  300

// for USB
#define DEBUG_USB
#define DEBUG_SERIAL 

// Disable USB after boot
#define DISABLE_USB_SERIAL

#ifdef  DEBUG_SERIAL
#define SERIAL_DEBUG  Serial1
#elif defined (DEBUG_USB)
#define SERIAL_DEBUG  Serial
#endif

#if defined (DEBUG_USB) || defined (DEBUG_SERIAL)
#define debug(VAL)          { SERIAL_DEBUG.print(VAL); }
#define debug2(VAL, MODE)   { SERIAL_DEBUG.print(VAL, MODE); }
#define debugln(VAL)        { SERIAL_DEBUG.println(VAL); }
#define debug2ln(VAL, MODE) { SERIAL_DEBUG.println(VAL, MODE); }
#define debugflush()        { SERIAL_DEBUG.flush(); }
#else
#define debug(VAL)          {}
#define debug2(VAL, MODE)   {}
#define debugln(VAL)        {}
#define debug2ln(VAL, MODE) {}
#define debugflush()        {}
#endif

#define WAKE_BTN YOUR_BUTTON_IO_NAME

uint32_t transmitPeriod = LORA_TRANSMIT_PERIOD; 
bool VUSB, hasUSB;

volatile bool wake_btn = false; 
volatile bool wake_timer = false; 
volatile bool wake_join = false; 

TimerMillis wakeTimer;

void ledOff()
{ 
  // Letting PWM active prevent stop mode and thus
  // stop()/deepsleep() does not go into sleep mode.
  pinMode(LED_RED, OUTPUT);     
  pinMode(LED_GRN, OUTPUT);     
  pinMode(LED_BLU, OUTPUT); 
  digitalWrite(LED_RED, LEDOFF);     
  digitalWrite(LED_GRN, LEDOFF);     
  digitalWrite(LED_BLU, LEDOFF);     
}

void btn_interrupt()
{
  wake_btn = true; 
  STM32L0.wakeup();
}

void timer_interrupt(void)
{
  wake_timer = true;
  STM32L0.wakeup();
}

void joinCallback(void)
{
  debug("joinCallback() ");
  wake_join = true;
  STM32L0.wakeup();
}

void setup() 
{
  char buffer[18];

  VUSB = USBDevice.attached();
  hasUSB = USBDevice.connected();

  pinMode(WAKE_BTN, INPUT);

  LoRaWAN.begin(EU868);
  LoRaWAN.getDevEui(buffer,18);

#ifdef DEBUG_SERIAL
  Serial1.begin(115200);
  while (!Serial1 && millis()<=2000 ) {
  }
  Serial1.println("Serial1 OK");
#endif

#ifdef DEBUG_USB
  Serial.begin(115200);

  while (!Serial && millis()<=5000 ) {
  }
  Serial.println("OK");
#endif

  LoRaWAN.setADR(true);
  LoRaWAN.setDutyCycle(false);
  LoRaWAN.onJoin(joinCallback);
  LoRaWAN.onReceive(receiveCallback);

  debugln("LoRa Joining");  
  LoRaWAN.joinOTAA(appEui, appKey, (const char *) buffer);

#ifdef DEBUG_USB
#ifdef DISABLE_USB_SERIAL
  // Switch to real uart
  Serial.println("Stopping USB");
  Serial.end();
#else
  Serial.println("!!!! Leaving USB Serial running !!!!");
#endif
#endif

// In all case even just Serial 
#ifdef DISABLE_USB_SERIAL
  USBDevice.detach();
#endif

  // Initialize our wake timer but do net setup yet
  wakeTimer.start( timer_interrupt, 10000);
  wakeTimer.stop();
}

void loop() 
{
  unsigned long next_transmit = transmitPeriod;

  if (wake_btn) {
    debugln("button");  
  }

  if (wake_join) {

    if (!LoRaWAN.joined()) {
      debug("NOT ");
    }
    debugln("JOINED");
    wake_join = false;
  }

  if (wake_timer) {
    // Security to be sure to be waked
    wake_timer = false;
  }

  ledOff();
  wake_btn = false;

  // reset heartbeat every 4 x transmit period
  wakeTimer.restart( LORA_TRANSMIT_PERIOD * 4 * 1000 + 5000);

    debug("Going to Sleep ("); 
  debug(next_transmit); 
  debugln("s)..."); 
  debugflush();

  attachInterrupt(WAKE_BTN, btn_interrupt, RISING);   

  STM32L0.deepsleep(next_transmit*1000);

   // Just waked up
  detachInterrupt(WAKE_BTN);   
}
evandavey commented 2 years ago

Awesome, thanks! Can't see too much different from MBED in this application code so it must be the initial pin config / deep sleep code that is done differently.

hallard commented 2 years ago

can you share your HW schematics or not because could be some diff on vdd_txo / txco pin used?

evandavey commented 2 years ago

Type 1SJ EVM I think technically can't be shared (though they give you access if you buy the board) - I asked if I can share my target files with the mbed-os community but so far no response. My custom board I can't as it is a client design (but I have accounted for TCXO) - it is pretty much the reference design with provision for an external crystal and for TCXO to be GPIO switched. I'd build another one to test with bare minimum components but can't source the Type ABZ modules at the moment. This issue shows up on the DISCO LRWAN1 board too. If I dig in to the SX1276 driver though you supply a TCXO pin I don't think it actually does anything with it so I have manually disabled it. You can measure the impact (about 1mA from memory) but that doesn't help (see https://github.com/ARMmbed/mbed-os-example-lorawan/issues/222).

hallard commented 2 years ago

Here mine

D12 is PB14 in my case

image

evandavey commented 2 years ago

Does the "LoRaWAN.h" in GrumpPizza switch it? If I search _tcxo in mbed SX1276_LoRaRadio.cpp I can see it turned on on line 212 but then not referenced again.

evandavey commented 2 years ago

Yep - I can see specific calls in the libraries and looks like the GPIOs are configured better too when it's off for deep sleep:

# https://github.com/GrumpyOldPizza/ArduinoCore-stm32l0/blob/b1cf1cd6eba2f06bbe4edbe76aa43ae5971e8d8c/system/STM32L0xx/Source/LoRa/Boards/cmwx1zzabz-board.c
# Line 134
void SX1276SetBoardTcxo( bool state )
{
    if (RADIO_TCXO_VCC != STM32L0_GPIO_PIN_NONE)
    {
        if( state == true )
        {
            stm32l0_gpio_pin_configure(RADIO_TCXO_VCC, (STM32L0_GPIO_PARK_NONE | STM32L0_GPIO_PUPD_NONE | STM32L0_GPIO_OSPEED_HIGH | STM32L0_GPIO_OTYPE_PUSHPULL | STM32L0_GPIO_MODE_OUTPUT));
            stm32l0_gpio_pin_write(RADIO_TCXO_VCC, 1);
        }
        else
        {
            stm32l0_gpio_pin_configure(RADIO_TCXO_VCC, (STM32L0_GPIO_PARK_NONE | STM32L0_GPIO_MODE_ANALOG));
        }
    }
}

Then called to turn off in the SX1276 sleep function

# https://github.com/GrumpyOldPizza/ArduinoCore-stm32l0/blob/b1cf1cd6eba2f06bbe4edbe76aa43ae5971e8d8c/system/STM32L0xx/Source/LoRa/Radio/sx1276/sx1276.c
# Line 885
 if( SX1276.TcxoOn )
    {
        SX1276SetBoardTcxo( false );

        SX1276.TcxoOn = false;
    }

And back on again in the standby function

# https://github.com/GrumpyOldPizza/ArduinoCore-stm32l0/blob/b1cf1cd6eba2f06bbe4edbe76aa43ae5971e8d8c/system/STM32L0xx/Source/LoRa/Radio/sx1276/sx1276.c
# Line 912
  if( !SX1276.TcxoOn )
    {
        SX1276SetBoardTcxo( true );

        tcxoTimeout = SX1276GetBoardTcxoWakeupTime( );

        if( tcxoTimeout )
        {
            SX1276Delay( tcxoTimeout );
        }

        SX1276.TcxoOn = true;
    }
evandavey commented 2 years ago

But this is a (related but) separate issue to deep sleep current when TCXO is hard-coded off (though does imply this library has a more detailed implementation so more likely to minimise current consumption).

evandavey commented 2 years ago

I have had a break-through and am now getting down to 7uA!

I created a new SX1276 radio class which extends the original and overrides the sleep() and wakeup() methods.

I have only tested sleep but it looks like this so far:

 void sleep() final
{
    // call actual sleep method
    SX126X_LoRaRadio::sleep();

    // Set low power for pins

    GPIO_InitTypeDef GPIO_InitStructure = {0};

    // Disable ant switch (PA_15)
    GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStructure.Pull = GPIO_NOPULL;
    GPIO_InitStructure.Pin = (GPIO_PIN_15);
    __GPIOA_CLK_ENABLE();
    HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
    __GPIOA_CLK_DISABLE();

    // Disable SPI pins (SCLK PB_13, MISO PB_14, MOSI PB_15)
    GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStructure.Pull = GPIO_NOPULL;
    GPIO_InitStructure.Pin = (GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);
    __GPIOB_CLK_ENABLE();
    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
    __GPIOB_CLK_DISABLE();
}

I have tried with CS (PB_12) with various pull up settings but this always results in ~1mA so assume this is turning the radio back on. Don't know if this accounts for the missing 5uA.

Don't know if there is a better MBED way of doing this in PeripheralPins.c. Also not sure if there are any global sleep / wakeup hooks that could be used instead as part of the sleep manager.

Will also need to work-out how to reverse in the wakeup() override.

evandavey commented 2 years ago

HW deinit seems like it might be a (very long term) outstanding mbed issue https://github.com/ARMmbed/mbed-os/issues/3106 @jeromecoutant - any way to deinit / init SPI just for an STM32L0 target?