Failing to stay in sleep mode or over optimistic about unbranded batteries?

I am working with a promini and have been following some of the info provided by Nick Gammon: Gammon Forum : Electronics : Microprocessors : Power saving techniques for microprocessors, with the intention of putting my MCU to sleep and waking it hourly using an inturrupt from the nrf24l01. I am using an unbranded version of the NCR18650B battery that boasts a 3400mAh capacity. I have played with turning off the different power registers, but at best, the battery lasts ~2 weeks. I am sharing a simplified version of the code where I intend to sleep the MCU.

While I have not removed the voltage regulator or the on board LED, I would hope to have achieved a lower current draw than the 19mA by sleeping the CPU.

The code includes a function to read an SDI-12 sensor. The datasheet:
http://www.ictinternational.com/pdf/?product_id=255
I removed the sensor and saw marginal improvement of battery life.

Am I missing something or did I goof up by buying cheap batteries?
Thanks for taking the time to read.

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <avr/sleep.h> // this is for sleep mode
#include <avr/power.h>// power reduction register (prr) can be used to turn off parts of the arduino we arent using such as adc or SPI interface or uart

#define CE_PIN   7//pins for nrf24l01
#define CSN_PIN 8

float Ea = 0.00;
float temp = 0.00f;
float batteryVoltage;
int ID;
int channel;

#define vcc  A0 //analog pin to read battery level

byte NRFIrq = 3; //inturrupt pin used by NRF

const byte thisSlaveAddress[5] = {'R', 'x', 'A', 'A', 'A'};
//{'R','x','A','A','A'} {'R','x','A','A','B'} or {'R','x','A','A','C'}

RF24 radio(CE_PIN, CSN_PIN);

char dataReceived[10]; // this must match dataToSend in the TX
float ackData[3] = {Ea, temp, batteryVoltage}; // the three values to be sent to the master
bool newData = false;

//5tm stuff. a moisture sensor. Datasheet: http://www.ictinternational.com/pdf/?product_id=255
#include <SDI12.h>
#define POWER_PIN -1       // The sensor power pin (or -1 if not switching power) 
#define DATAPIN 2        // change to the proper pin for sdi-12 data pin,
#define SENSOR_ADDRESS 1
SDI12 mySDI12(DATAPIN);
String data;//this is the string that will get sent with the rf transmitter redundant?
String myCommand = "";  //this is where the command is sent consisting of 3 parts
String sdiResponse = ""; //this is where the data will be held
//these parts are the sensor address, the command and ! which ends the command

void do_sleep(void);//call function

//==============

void setup() {


  power_adc_disable(); // disable the clock on the adc module.

  Serial.begin(9600);

  mySDI12.begin();
  Serial.println("for sampling multiple SDI12 sensors");
  delay(100);

  radio.begin();
  radio.setDataRate( RF24_250KBPS );
  radio.openReadingPipe(1, thisSlaveAddress);

  radio.enableAckPayload();
  radio.enableDynamicPayloads();
  radio.startListening();

  radio.writeAckPayload(1, &ackData, sizeof(ackData)); // pre-load data
  attachInterrupt(digitalPinToInterrupt(NRFIrq), myISR, LOW); //when signal is recieved myISR function is ran when pin three is pulled low
}

//================================================================================================

void loop() {
  //all that should be in the loop is the sleep command. the measurement commands will go in the interrupt service routine
  //do_sleep();
  getData();
  showData();

  delay(100);
  do_sleep();

}


//======================================================
void do_sleep(void)
{
  Serial.println("sleep");
  sleep_enable();
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
  power_adc_disable();
  //noInterrupts ();
  attachInterrupt(digitalPinToInterrupt(NRFIrq), myISR, LOW);
  delay (50);
  sleep_cpu ();
  delay (100);
  Serial.println("awake");
}
//========================================================
void myISR() {
  sleep_disable(); //interrupt service routine this is what happens when the wakeup signal is recieved from the logger
  //ADCSRA = old_ADCSRA; //enable the adc by setting the register to 1
  noInterrupts ();
}
//========================================================
//added function for debugging to read battery power
float readVcc() {

  power_adc_enable(); // enable the clock on the adc module.
  float batteryVoltage = ((analogRead(vcc) / 511.5) * 3.3);
  //ADCSRA = 0; //disable the adc by setting the register to 0
  return batteryVoltage;
  power_adc_disable(); // disable the clock on the adc module.

}



//============
void getData() {
  if ( radio.available() ) {
    radio.read( &dataReceived, sizeof(dataReceived) );
    delay (50);
    updateReplyData();
    delay(100);
    newData = true;
  }

}

//================

void showData() {
  if (newData == true) {
    Serial.print("Data received ");
    Serial.println(dataReceived);
    Serial.print(" ackPayload sent ");
    Serial.print(ackData[0]);
    Serial.print(", ");
    Serial.print(ackData[1]);
    Serial.print(", ");
    // Serial.println(ackData[3]);
    //  Serial.print(", ");
    Serial.println(ackData[2]);


    newData = false;
  }
}

//================

void updateReplyData() {
  measure(); // polls the sensor for a string 'o data
  batteryVoltage = readVcc();

  //sent data are up dated
  ackData[0] = Ea;
  ackData[1] = temp;
  ackData[2] = batteryVoltage;
  //ackData[3] = batteryVoltage;
  radio.writeAckPayload(1, &ackData, sizeof(ackData)); // load the payload for the next time
}
//===================
//5tm read function
void measure () {
  myCommand = String(SENSOR_ADDRESS) + "M!";
  // Serial.println(myCommand);     // echo command to terminal
  mySDI12.sendCommand(myCommand);
  delay(30);                  // wait a while for a response
  while (mySDI12.available()) {  // build response string
    char c = mySDI12.read();
    if ((c != '\n') && (c != '\r')) {
      sdiResponse += c;
      delay(5);
    }
  }
  if (sdiResponse.length() > 1); //Serial.println(sdiResponse); //write the response to the screen
  data = sdiResponse;
  // Serial.println (data);
  mySDI12.clearBuffer();


  delay(1000);                 // delay between taking reading and requesting data
  sdiResponse = "";           // clear the response string

  // next command to request data from last measurement
  myCommand = String(SENSOR_ADDRESS) + "D0!";
  //Serial.println(myCommand);  // echo command to terminal

  mySDI12.sendCommand(myCommand);
  delay(30);                     // wait a while for a response

  while (mySDI12.available()) {  // build string from response
    channel = mySDI12.parseInt();
    ID = mySDI12.parseFloat(3);
    Ea = mySDI12.parseFloat();
    temp = mySDI12.parseFloat();
    // Serial.print("Sensor: ");
    // Serial.print(channel);
    Serial.print("Sensor ID: ");
    Serial.print(ID);

    Serial.print(" Ea=");
    Serial.print(Ea);
    Serial.print("temp= ");
    Serial.print(temp);
    char c = mySDI12.read();
    Serial.println(c);
    //
    if ((c != '\n') && (c != '\r')) {
      sdiResponse += c;
      //Serial.println(sdiResponse);
      delay(5);

    }
  }
}

You realize the regulator and LED can draw most of the 19mA. Also the lower the voltage supplied to the regulator the lower the power consumption of the board. A linear regulator is 50% efficient at best. Have Fun! There are a lot of posts on how to do what you want and gives the pit falls as well on some of them.

Use a hot solder pencil tip to swipe off the voltage regulator and the power LED. Takes 10 seconds.

Then, power the Pro Mini via the "5V" output.

If you follow Gammon's tutorial carefully, the battery will last months to a year or more, depending on the battery self discharge rate and the time spent not sleeping. Of course the radio must be put to sleep as well.

jremington:
Of course the radio must be put to sleep as well.

Really? that could be a large oversight on my part.
How does the radio receive input to provide an interrupt if it's asleep?

You have to reconfigure the application so the battery powered unit wakes itself up including the radio, transmits something then goes back to sleep for a set period.
Hopefully, it is talking to something which is mains powered so is available continuously to receive data from the battery powered unit.
Use a multimeter to check the power consumption to estimate the battery life.
The watchdog timer can be used to wake the mcu.

Take a look at this other Gammon power tutorial, in which a radio is used.

morr8362:
Really? that could be a large oversight on my part.
How does the radio receive input to provide an interrupt if it's asleep?

It does not receive anything if its 'asleep'.

To receive stuff the radio must be powered on and that can consume 10-20mA.

at best, the battery lasts ~2 weeks.

(two weeks) x 10 mA = 3360 mAh, so your batteries are fine.

Great advice. I will reverse engineer my system :wink: and knock off the extra bits

6v6gt:
You have to reconfigure the application so the battery powered unit wakes itself up including the radio, transmits something then goes back to sleep for a set period.
Hopefully, it is talking to something which is mains powered so is available continuously to receive data from the battery powered unit.

Can the listening device (a data logger) be listening for data coming in from multiple sensors? From my understanding the nodes are all communicating with the data logger using the same pipe, and an overlap in communication can result in lost data.

Or am I confused? Can a device listen via multiple pipes? or is there a simple way to stagger the transmissions? I remember seeing a post about using a random number generator to shift the transmit timing ever so slightly.

Either way. If you have short, infrequent, staggered transmissions on the same channel pr pipe, the chance of collision is small. Each packet needs a device ID and a packet sequence number (to detect reception failures).

Alternatively , you can transmit something, say using method write(), and test the return code to see if the receiver acknowledged the success receipt of your data. You can also configure automatic retries of any unacknowledged transmission.

morr8362:
While I have not removed the voltage regulator or the on board LED, I would hope to have achieved a lower current draw than the 19mA by sleeping the CPU.

If you don't sleep the radio, that's not going to happen. The power LED takes a few mA, the regulator less (quiescent current isn't that much but becomes significant for achieving really long battery life).

Am I missing something or did I goof up by buying cheap batteries?

That's always a risk :slight_smile:

gilshultz:
A linear regulator is 50% efficient at best.

That is just utter nonsense.

I would like to update this post with the solution I arrived to thanks to the recommendations of the community.

The problem:
the node arduinos were sleeping but the RF module was always awake and listening to provide the interrupt which would wake up the MCU. This extra power consumption was the main cause of my short battery life.

The solution:
I had my communication protocol slightly backwards. I had to reconfigure the application so the battery powered unit wakes itself up (including the radio) transmits data and returns to sleep for a set period.

I am still waiting to see how long it takes for the battery to die on one unaltered node and one node where I have removed the voltage regulator and LED.

Thanks!

Well done, no doubt the new way will make your battery last a LOT longer!