Hope RFM95/98 power usage in sleep mode?



I faced this kind of problem when played with ULPNode, In my case I was powering down RFM module with a mosfet to be sure 0 power will be consumed but had problem with port config pull up/down, I solved with the following code

The one part interesting for you is the part after f (!power)

/* ======================================================================
Function: powerRadio
Purpose : expose driver method of power or unpower the RF module 
Input   : true to power on false to power off
Output  : true if powered and module found
Comments: -
====================================================================== */
boolean ULPNode::powerRadio(uint8_t power)

  // do we need to power up the sensors
  if ( power) {
    uint8_t status_mask = 0;

    // From here and with latest Arduino version we have a problem
    // Arduino SPI library now check if SPI has already been initialized
    // if so, init is not done again and as we changed SS pin and some 
    // others to have full Low Power, we need to enhance back all as it
    // should be done in a real Spi init EACH time.
    //SPCR |= _BV(SPE);

    // Warning: if the SS pin ever becomes a LOW INPUT then SPI
    // automatically switches to Slave, so the data direction of
    // the SS pin MUST be kept as OUTPUT.

    // set back CSN pin with pullup (it was input)
    digitalWrite(RF_CSN_PIN, HIGH); 
    // now set it as output high
    pinMode(RF_CSN_PIN, OUTPUT); 
    digitalWrite(RF_CSN_PIN, HIGH); 

    // Power Up Modules SPI 

    // Enable back SPI and set as MASTER
    SPCR |= _BV(SPE);
    SPCR |= _BV(MSTR);

    // MISO pin automatically overrides to INPUT.
    // By doing this AFTER enabling SPI, we avoid accidentally
    // clocking in a single bit since the lines go directly
    // from "input" to SPI control.
    // http://code.google.com/p/arduino/issues/detail?id=888
    // Not needed because we didn't changed these pins 
    //pinMode(SCK, OUTPUT);
    //pinMode(MOSI, OUTPUT);

    // power ON VCC the radio module

    // RF module settle delay 
    sleepQuickWake( WDTO_15MS );

    if (_radio_type == RF_MOD_NRF24)
      status_mask = RF_NODE_STATE_NRF24;
    if (_radio_type == RF_MOD_RFM69)
      status_mask = RF_NODE_STATE_RFM69;

    // Init the radio driver with moteino config
    if (!driver.init()) {
      // Radio state not OK
      _status &= ~status_mask;
      return false;

    // Radio is okay
    _status |= status_mask;

    // Specific init for RFM69
    if ( _status & RF_NODE_STATE_RFM69) {
      RH_RF69 * prf69_drv = (RH_RF69 *) &driver;

      // Moteino settings
      // Copied from LowPowerLab 
      prf69_drv->spiWrite(RH_RF69_REG_29_RSSITHRESH, 220);

      // default moteino Frequency For 433 MHz 

    // Specific init for NRF24
    if ( _status & RF_NODE_STATE_NRF24) {
      // Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm
      //nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm))
  // So this is a power off
  if ( !power) {
    // This will configure the radio pins for correct low power mode

    // We're going to sleep, we've done our job no need to be awake by
    // RF module firing up a IRQ when we're in power down (can cause trouble?)

    // Once agin, very important even if we power off the module, because 
    // of pullup, module still powered via SS/IRQ Pin. if we don't do this
    // even if VDD of RFModule set to "float" using mosfet, current is get
    // drawn by other pins pullup (CS or IRQ), so disable pull up
    pinMode(RF_CSN_PIN, INPUT); 
    digitalWrite(RF_CSN_PIN, 0); 

    // Disable SPI device
    SPCR &= ~_BV(SPE);
    // unpower SPI of Arduino

    // unpower RF module

  return (true);


And for those interested here the code to disable all ATMega328 device for low power

Remember that I power down devices (I2C and SPI) using a mosfet to enable/disable them so all VDD pin of devices is then left float

/* ======================================================================
Function: disableCPUDevices
Purpose : disable Atmel integrated devices (for low power)
Input   : -
Output  : - 
Comments: - 
====================================================================== */
void ULPNode::disableCPUDevices(void)
  // Disable ADC 
  ADCSRA &= ~_BV(ADEN)  ; 

  // disable Analog comparator  
  ACSR |= _BV(ACD); 
  // Disable digital input buffers on all ADC0-ADC5 pins
  //DIDR0 = 0x3F;    

  // set I2C pin as input no pull up
  // this prevent current draw on I2C pins that
  // completly destroy our low power mode

  //Disable I2C interface so we can control the SDA and SCL pins directly
  TWCR &= ~(_BV(TWEN)); 

  // disable I2C module this allow us to control
  // SCA/SCL pins and reinitialize the I2C bus at wake up
  TWCR = 0;
  pinMode(SDA, INPUT);
  pinMode(SCL, INPUT);
  digitalWrite(SDA, LOW);
  digitalWrite(SCL, LOW);  




Even easier to minimize power use during sleep mode is to define unused IO pins as output (commonly used on ATmega/'Arduino').

(Jtp) #24

Hi @tomtor,

I’m really new in the use of the end-devices and I have some troubles to achieve a low consumption in sleep mode (I get 0,13mA).

I’m using in my end device the CMWX1ZZABZ-078 micro-controller with a Chipset: Semtech (SX1276) + STM (STM32L) ) and I have also the B-L072Z-LRWAN1 LoRa Discovery kit. I use the example “LRWAN-NS1” from the I_CUBE_LRWAN which is an example to start a simple use of a LORA en device.

It’s been a couple of weeks that I’m trying to understand the sleep mode to be sure that I’m able to go lower than 0,13mA for a longer time life of my batteries. I appreciate any help you can give me to keep going with this end device.

Best Regards,

(Tom Vijlbrief) #25

Hi @JTP,

0,13mA is not that bad, you will run over 1.5 years on a 2000mAh cell.

I assume your software is OK, but that either a voltage regulator is consuming current or you have floating input lines. Hard to say without a schematic of your design…

(Jtp) #26

I saw that it’s able to get something around 4uA which is something really useful. I use a 2600mAh cell, but I don’t have the equipment to measure the sending time. I have 3 currents in my Lora end-device ( 1.- Sleep mode: 0.13mA. 2.- Wake-up: 6,05mA. 3.- Data sending: 23mA.). I have a circuit that measures the capacity percentage of the batteries cell in I2C, but I have some troubles in the I2C communication (I’m not sure if it’s a software or hardware problem).

You can see my circuit in the picture bellow:

I appreciate all your help.

Best regards


@JTP This is likely more a STM32L0 question than that it is a Hope RFM95/98 power usage issue. This STM32L0 related article might help. Also the STM32 forum is probably a good source for more information.


Completely powering off the RFM9x could also be an option for minimizing battery usage.
Ah I just see @Charles already suggested this.

(Lorawanhere) #29

@tomtor Thanks for your wonderful post https://www.thethingsnetwork.org/labs/story/a-cheap-stm32-arduino-node

Unfortunately, the code doesn’t compile for my setup ( https://github.com/stm32duino/Arduino_Core_STM32 ) with the bluepill board stm32f103 gives some function not defined error for stdout streams.

On the other hand the “official” library from arduino library manager (1.5+arduino-2 from drop down menu) compiles, but interrupts are creating some problems. This is caused by disableIRQs function where it disables interrupts. if i disable it be commenting it out, i get upto the point of Starting, Packet Queued. I can see the packets in my TTN console…but there is not event notified whether the packet was successful.

So i can not send any more data like sensor values…This also applies to your library.

Kindly help, so that this cheap but capable board can really fly up to its potential…



FYI: New thread for STM32: Big STM32 boards topic

(Lorawanhere) #31

@bluejedi Shall i doublepost to that forum?

In the meanwhile this is what i get from the OTAA example, with DEBUG set to 2:


149062: engineUpdate, opmode=0x8
149248: Scheduled job 0x20000ab0, cb 0x8002f0f ASAP
Packet queued
156344: Running job 0x20000ab0, cb 0x8002f0f, deadline 0
177014: engineUpdate, opmode=0xc
177198: Uplink join pending
177354: Airtime available at 297137 (previously determined)
177686: Uplink delayed until 297137
177887: Scheduled job 0x20000ab0, cb 0x8002f19 at 297012
297012: Running job 0x20000ab0, cb 0x8002f19, deadline 297012
297356: engineUpdate, opmode=0xc
297540: Uplink join pending
297696: Airtime available at 297137 (previously determined)
298029: Ready for uplink
298212: Updating info for TX at 297540, airtime will be 3856. Setting available time for band 0 to 4153540
298983: TXMODE, freq=433175000, len=23, SF=7, BW=125, CR=4/5, IH=0


(Tom Vijlbrief) #32

I will have to merge some upstream changes but my repo should compile:

Note that the upstream repository is also different: https://github.com/rogerclarkmelbourne/Arduino_STM32
from the one you are using.

You should show the compile errors and link to your sources otherwise it is hard to help you.

(Lorawanhere) #33

@tomtor Thanks for responding.

With your library arduino-LMIC (https://github.com/tomtor/arduino-lmic) with the stmduino core (this one: https://github.com/stm32duino/Arduino_Core_STM32) without any modifications, running the abp or otaa example from examples menu, i get the following output:
If i set


in src>lmic>config.h
i get the following output:

482261: irq: dio: 0x2 flags: 0x0
482470: Scheduled job 0x20000aa4, cb 0 ASAP
482725: Cleared job 0x20000aa4
482918: engineUpdate, opmode=0x8
483105: Scheduled job 0x20000aa4, cb 0x80019b3 ASAP
Packet queued
588697: irq: dio: 0x2 flags: 0x0
588907: Scheduled job 0x20000aa4, cb 0x80019b3 ASAP
589196: Running job 0x20000aa4, cb 0x80019b3, deadline 0
905338: engineUpdate, opmode=0xc
905523: Uplink join pending
905678: Airtime available at 669098 (previously determined)
906010: Ready for uplink
906204: Updating info for TX at 905523, airtime will be 3856. Setting available time for band 0 to 4761523
906912: TXMODE, freq=868300000, len=23, SF=7, BW=125, CR=4/5, IH=0
907288: Running job 0x20000aa4, cb 0x800111d, deadline 0
907606: Scheduled job 0x20000aa4, cb 0x8000f25 at 981569
908387: irq: dio: 0x2 flags: 0x0
908597: Cleared job 0x20000aa4
908770: Scheduled job 0x20000aa4, cb 0x8000f25 ASAP
909060: Running job 0x20000aa4, cb 0x8000f25, deadline 0

then it just stops doing nothing.

With the roger clark core it shows:

then nothing

With this core:

i get the following:

482370: engineUpdate, opmode=0x8
482557: Scheduled job 0x20000aa4, cb 0x80019b3 ASAP
Packet queued
588149: irq: dio: 0x2 flags: 0x0
588359: Scheduled job 0x20000aa4, cb 0x80019b3 ASAP
588648: Running job 0x20000aa4, cb 0x80019b3, deadline 0
904791: engineUpdate, opmode=0xc
904975: Uplink join pending
905131: Airtime available at 654022 (previously determined)
905463: Ready for uplink
905656: Updating info for TX at 904975, airtime will be 3856. Setting available time for band 0 to 4760975
906365: TXMODE, freq=868100000, len=23, SF=7, BW=125, CR=4/5, IH=0
906740: Running job 0x20000aa4, cb 0x800111d, deadline 0
907059: irq: dio: 0x2 flags: 0x0
907269: Scheduled job 0x20000aa4, cb 0x800111d ASAP
907558: Scheduled job 0x20000aa4, cb 0x8000f25 at 966493
907876: Running job 0x20000aa4, cb 0x8000f25, deadline 0

then nothing.

And if i remove the comments lines from disableinterrupts, it just does nothing after showing


Now, if i comment out the assert line in radio.c line 660 and remove disableinterrupts line, it runs without giving an error. packets are now visible in ttn application. but i think it still can’t receive downstream messages:

Packet queued
905114: engineUpdate, opmode=0xc
905298: Uplink join pending
905453: Airtime available at 608039 (previously determined)
905786: Ready for uplink
905978: Updating info for TX at 905297, airtime will be 3856. Setting available time for band 0 to 4761297
906742: TXMODE, freq=433175000, len=23, SF=7, BW=125, CR=4/5, IH=0

(Lorawanhere) #34

@tomtor I’ve installed roger clark core (i am proceeding step by step) With your sketch in the solar example with deep sleep, it compiles.

  • Serial.print doesn’t give me anything. i replaced it with Serial1 then i’ve some thing on my usb->serial which i monitor using putty.

  • I get many extraneous characters on my putty. is it due to interference? SPI@1MHZ or the LORWAN radio@433mhz

  • If i comment out //#define SLEEP, it complains :
    'mdelay' was not declared in this scope

  • I want to add an OLED (ssd 1366 128x64 i2c) using adafruit library. i replaced all Serial.println() with msgOLED() defined below:

    void msgOLED(String s)


    void msgOLED(int s)
    char temp[20];itoa(s,temp,10);


    In the serial monitor i get:

    Enter do_se⸮

And then nothing. I don’t understand the sleep states properly, any help?

I think an oled would be a nice addition. this could be woken up by a GPIO pin or using a special downstream packet from the TTN console…

(Lorawanhere) #35

@tomtor With the stm32duino core (not the roger clark one) these are my observations:

SPIClass mySPI(USE_SPI); gave errors, so i replaced it with SPI.begin() which gave the same problem of not doing anything after a call to osinit() is made… Earlier i resolved the error by disabling everything relating to disabling interrupts in hal.cpp:

static uint8_t irqlevel = 0;

void hal_disableIRQs () {
 //   noInterrupts();
 //   irqlevel++;

void hal_enableIRQs () {
 //   if(--irqlevel == 0) {
  //      interrupts();

        // Instead of using proper interrupts (which are a bit tricky
        // and/or not available on all pins on AVR), just poll the pin
        // values. Since os_runloop disables and re-enables interrupts,
        // putting this here makes sure we check at least once every
        // loop.
        // As an additional bonus, this prevents the can of worms that
        // we would otherwise get for running SPI transfers inside ISRs
//        hal_io_check();
//    }

Further, hal_io_check leads to hanging of the chip…
void hal_sleep () {
// Not implemented

//#include <libmaple/pwr.h>
//#include <libmaple/scb.h>

//#include <RTClock.h>

Then there are several errors relating to INPUT_ANALOG:
469: error: ‘INPUT_ANALOG’ was not declared in this scope

   pinMode(PA0, INPUT_ANALOG);


Then adc_enable and adc_disable is not defined…i commented out all these calls. adc_read not declared.
I commented out #define SLEEP replaced all the mdelay() with delay()

In addition, i also modified DEVEUI to get data from the TTN console as follows:

static const u1_t DEVEUI[8]={ 0x43, 0x35, 0x42, 0x32, 0x24, 0x45, 0x23, 0x25 }; // reversed 8 bytes of DevEUI registered with ttnctl
void os_getDevEui (u1_t* buf) {
  // use chip ID:
//  memcpy(buf, &STM32_ID[1], 8);
  // Make locally registered:
//  buf[0] = buf[0] & ~0x3 | 0x1;

Otherwise, data doesn’t get forwarded to the TTN application from TTN gateway…

Now, i am looking forward to add an OLED to display various messages. to save battery, this could be woken up by a gpio pin or from upstream TTN downlink message…I will post my results later…

Starting from your ino sketch, after my modifications, it looks something like this:

This is the output on the serial monitor:

Enter do_send
Packet queued
Leave do_send
Leave setup
Enter onEvent
Leave onEvent

(Tom Vijlbrief) #36

Regarding the garbled serial output when using sleep modes.

When the chip enters sleep mode, also the serial output stops. So you should have a small delay after the last print and just before entering sleep mode, so that the output can be successfully transmitted before the sleep is entered.

Using deep sleep modes should really be the last step in your project if everything else is working fine.

(Lorawanhere) #37

@tomtor Thanks for responding. i don’t understand sleep modes properly especially so in the stm32 framework. further, it has been pointed out that in the “official” stm32duino, there is a support for HAL (http://www.stm32duino.com/viewtopic.php?f=42&t=97 )

From the experiments i carried out above with both the cores (https://github.com/stm32duino/Arduino_Core_STM32 ) and ( https://github.com/rogerclarkmelbourne/Arduino_STM32 ) have problems of similar nature. my guess is, it is related to IRQ/interrupts/timing issues as the timing of the chip is managed by stm32.

All in all, major problem for me is:

While i can send data to TTN, it is done only once. so if i have to send data periodically, for example temperature, pressure, altitude data from bmp180, what can i do to achieve that?
my guess is that the node doesn’t move to the next state. this could be because i disturbed its flow using comments…

  • I’ve somehow managed OLED to display each of the Serial.print() messages by replacing all the Serial.println & Serial.print with msgOLED() which then calls Serial.println & display.println (from Adafruit Library) The keypoint to note is that we have to reinitialise OLED everytime it has to display something, else nothing gets printed on the oled. perhaps, some GPIO’s gets messed up by the LMIC library
  • Sleep would be required, but i leave to some later time because i don’t know enough how LMIC works and how stm32/arduino works…but one thing i’m quite sure is that stm32 is “the” right choice for the sensor nodes. it is not that pricey yet has support for so many peripherals and works for quite low voltages. directly connected to a 18650 lipo, it works…with 2 oleds, a battery capacity monitor, a lora modem (aithinker ra02, based on sx1278), stm32f103 with default setup still works. i am experimenting with ni-cd 1.2x4…with solar probably should work without any human intervention
  • SPI frequency@10mhz, i think, i a bit too high especially if wiring with dupont wires and in external environment with i2c on…
  • In my opinion, both the cores exhibit similar behaviour. apart from a few libraries here and there…real problematic is calling noInterrupts in hal.cpp by the LMIC library. hal_io_check(); also seems to be problematic. but i have not investigated enough.
  • default library supplied by arduino-lmic project (https://github.com/matthijskooijman/arduino-lmic ) 1.5-arduino-2 also exhibits similar behaviour…i think they have merged your changes…so i think that should be the starting point to avoid confusion…but maybe sleep/low frequency operations@8mhz etc have not been included…

Any insights on how to get continuous data, say every 10 minutes with oled woken up by GPIO interrupts/upstream data?

and once again thanks for any help…

(Tom Vijlbrief) #38

See https://github.com/tomtor/arduino-lmic/commit/52583ab4f2cb958fa0d220a8e0fadd39f121df89

I do not disable interrrupts.

(Lorawanhere) #39

@tomtor For the “official” stm32 core, #ifdef ARDUINO_ARCH_STM32F1 doesn’t work as pointed out by fpiSTM32 http://www.stm32duino.com/viewtopic.php?f=48&t=3489&start=10#p43857
hal_io_check(); https://github.com/tomtor/arduino-lmic/commit/52583ab4f2cb958fa0d220a8e0fadd39f121df89#diff-7cce30ca97b97ac6a31ad515b700e604R206 is equally problematic…

it is defined as below in https://github.com/tomtor/arduino-lmic/blob/0859fcd71de91a54498b83df58bae382559140e5/src/hal/hal.cpp#L60 as:

static void hal_io_check() {
    uint8_t i;
    for (i = 0; i < NUM_DIO; ++i) {
        if (lmic_pins.dio[i] == LMIC_UNUSED_PIN)

        if (dio_states[i] != digitalRead(lmic_pins.dio[i])) {
            dio_states[i] = !dio_states[i];
            if (dio_states[i])

radio_irq_handler is defined in https://github.com/tomtor/arduino-lmic/blob/master/src/lmic/radio.c#L764 as below:

void radio_irq_handler (u1_t dio) {
    ostime_t now = os_getTime();
    if( (readReg(RegOpMode) & OPMODE_LORA) != 0) { // LORA modem
        u1_t flags = readReg(LORARegIrqFlags);
        lmic_printf("%lu: irq: dio: 0x%x flags: 0x%x\n", now, dio, flags);
        if( flags & IRQ_LORA_TXDONE_MASK ) {
            // save exact tx time
            LMIC.txend = now - us2osticks(43); // TXDONE FIXUP
        } else if( flags & IRQ_LORA_RXDONE_MASK ) {
            // save exact rx time
            if(getBw(LMIC.rps) == BW125) {
                now -= TABLE_GET_U2(LORA_RXDONE_FIXUP, getSf(LMIC.rps));
            LMIC.rxtime = now;
            // read the PDU and inform the MAC that we received something
            LMIC.dataLen = (readReg(LORARegModemConfig1) & SX1272_MC1_IMPLICIT_HEADER_MODE_ON) ?
                readReg(LORARegPayloadLength) : readReg(LORARegRxNbBytes);
            // set FIFO read address pointer
            writeReg(LORARegFifoAddrPtr, readReg(LORARegFifoRxCurrentAddr));
            // now read the FIFO
            readBuf(RegFifo, LMIC.frame, LMIC.dataLen);
            // read rx quality parameters
            LMIC.snr  = readReg(LORARegPktSnrValue); // SNR [dB] * 4
            LMIC.rssi = readReg(LORARegPktRssiValue) - 125 + 64; // RSSI [dBm] (-196...+63)
        } else if( flags & IRQ_LORA_RXTOUT_MASK ) {
            // indicate timeout
            LMIC.dataLen = 0;
        // mask all radio IRQs
        writeReg(LORARegIrqFlagsMask, 0xFF);
        // clear radio IRQ flags
        writeReg(LORARegIrqFlags, 0xFF);
    } else { // FSK modem
        u1_t flags1 = readReg(FSKRegIrqFlags1);
        u1_t flags2 = readReg(FSKRegIrqFlags2);
        if( flags2 & IRQ_FSK2_PACKETSENT_MASK ) {
            // save exact tx time
            LMIC.txend = now;
        } else if( flags2 & IRQ_FSK2_PAYLOADREADY_MASK ) {
            // save exact rx time
            LMIC.rxtime = now;
            // read the PDU and inform the MAC that we received something
            LMIC.dataLen = readReg(FSKRegPayloadLength);
            // now read the FIFO
            readBuf(RegFifo, LMIC.frame, LMIC.dataLen);
            // read rx quality parameters
            LMIC.snr  = 0; // determine snr
            LMIC.rssi = 0; // determine rssi
        } else if( flags1 & IRQ_FSK1_TIMEOUT_MASK ) {
            // indicate timeout
            LMIC.dataLen = 0;
        } else {
    // go from stanby to sleep
    // run os job (use preset func ptr)
    os_setCallback(&LMIC.osjob, LMIC.osjob.func);



It is better to use Serial.flush() instead of using delay() before entering sleep.

Serial.flush() waits for the transmission of outgoing serial data to complete.
delay() does not know when and if the serial transmission has completed so you have to use a safety margin for the delay value. This will cause that the delay takes longer than required.
Serial.flush() is both more reliable and more efficient. It guarantees that all serial data has transmitted and prevents unnecessary delays. Delays that use more power because they delay entering the sleep state.

Unnecessary delay is the time between completion of the serial transmission and completion of delay().