Go Down

Topic: Breaking the .7 mA barrier? (Read 4800 times) previous topic - next topic

tim7

OK, here's another power-saving solution I've been using which doesn't use the Narcoleptic library.  I hadn't actually checked the current draw until now.

My meter reads 3uA (at 5.1V), but the real current is probably lower than this.
To do timed sleeps you need to enable the WatchDog, which uses roughly another 8uA.

Here's the code.  See the comments for more details
Code: [Select]
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>

// ====== Utility macros ======
#define adc_disable()  (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off)
#define adc_enable()   (ADCSRA |=  (1<<ADEN)) // re-enable ADC
#define ac_disable()   (ACSR   |=  (1<<ACD )) // disable analogue comparator
#define ac_enable()    (ACSR   &= ~(1<<ACD )) // enable analogue comparator
#ifndef sleep_bod_disable() // not included in Arduino AVR toolset
#define sleep_bod_disable() \
do { \
  uint8_t tempreg; \
  __asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \
                       "ori %[tempreg], %[bods_bodse]" "\n\t" \
                       "out %[mcucr], %[tempreg]" "\n\t" \
                       "andi %[tempreg], %[not_bodse]" "\n\t" \
                       "out %[mcucr], %[tempreg]" \
                       : [tempreg] "=&d" (tempreg) \
                       : [mcucr] "I" _SFR_IO_ADDR(MCUCR), \
                         [bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \
                         [not_bodse] "i" (~_BV(BODSE))); \
} while (0)
#endif

// wake-up pins on port-C
#define INPUT_MASK  0x3F


void setup() {
  // low outputs on ports B & D
  PORTB=0x00;
  DDRB=0xFF;
  PORTD=0x00;
  DDRD=0xFF;

  // pull-ups on port-C
  PCICR  = 0;           // disable pin-change interrupts
  DDRC &= ~INPUT_MASK;  // inputs on DIs
  PORTC  = INPUT_MASK;  // pull-ups on DIs
  PCMSK1 = INPUT_MASK;  // interrupt on DIs
  PCICR  = (1<<PCIE1);  // enable port-C interrupts
 
  // current-consumption of pull-ups is too small to measure (<1uA for 6 pins)
}

void loop() {
  // all currrents measured with ATmega328p at 8MHz and 5V
  // minimum current draw is less than the multimeter error (~3uA)
 
  digitalWrite(13,HIGH);
  delay(100);
  digitalWrite(13,LOW);
  delay(5000);
  digitalWrite(13,HIGH);
  delay(100);
  digitalWrite(13,LOW);
 
  // power off peripherals
  ac_disable();
  adc_disable(); // ADC uses ~125uA
  power_all_disable();
  wdt_disable(); // WDT uses ~8uA

  //wdt_enable(WDTO_4S);

  // enter sleep
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_bod_disable(); // BOD uses ~18uA
  sleep_cpu();
  sleep_disable();

  // to wake CPU pull pin A0-A5 low (or wait for WDT timeout if enabled)

  wdt_disable();
  power_all_enable();
}



Nick Gammon

You should be able to get a lot less than 700 uA.

http://www.gammon.com.au/forum/?id=11497

I got consumption down to 355 nA on that page. That's about 2000 times less. (That's with no watchdog).

I think I reckoned the watchdog at 6.2 uA which roughly agrees with tim7.

As I point out on that page, most batteries have a somewhat larger self-discharge rate than that, so you aren't really achieving much by going much lower.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

terraslate

Hello

Its 2015. Is this all still relevant? The sleep_cpu method etc no longer exists as far as i can tell. We have sleep_mode as it currently stands. There is no bod disable either.  Not sure how much i can rely on the 3 year old code when the api has changed since.

Kind regards

Terra

Coding Badly

Hello Terra,

The sleep_cpu method etc no longer exists as far as i can tell.
Why do you believe that to be true?

Quote
There is no bod disable either.
Ditto.


ushills

Sorry but I cannot see the pictures of your circuit set up.

Are you switching the +ve supplies for the sensors, I have done this with my code using narcoleptic and I am too using RF for temp and pressure.

My code is below if your interested, again running off 2 AA batteries.

Code: [Select]
// library for I2C barometer
#include <Wire.h>
#include <Narcoleptic.h>

// libraries for radio transmitter
#include <RHDatagram.h>
#include <RH_ASK.h>
#include <SPI.h> // not used for needed for compile


// declare variables for the BMP180 barometer
short ac1;
short ac2;
short ac3;
unsigned short ac4;
unsigned short ac5;
unsigned short ac6;
short b1;
short b2;
short mb;
short mc;
short md;
long PressureCompensate;
const unsigned char OSS = 3;
const int BMP180_ADDRESS = 0x77;
const int BMP180PowerPin = 2;
float temperature;
float pressure;

// define the height at the current location for sea level adjustment
short height = 101; // height at current location above sea level

// declare variables for radio transmitter
const int TxPowerPin = 4;
const int TRANSMITTER_ADDRESS = 0xDD;
const int RECEIVER_ADDRESS = 0xCD;
const int HEADER_TEMPERATURE = 0x25; // id for temperature variable
const int HEADER_PRESSURE = 0x28; // id for pressure variable
byte buf[RH_ASK_MAX_MESSAGE_LEN];



// setup variables for RH manager and driver
RH_ASK driver;  // use RH ASK driver
RHDatagram manager (driver, TRANSMITTER_ADDRESS);  // use the Datagram manager

void setup()
{
  Serial.begin(9600);  // begin serial communications

  // setup the barometer
  pinMode(BMP180PowerPin, OUTPUT); // set the BMP180 Power pin to an output
  digitalWrite(BMP180PowerPin,HIGH);  // turn the power on to the BMP180
  delay(10); // small delay for BMP180 to start
  barometerInit();  // get the calibration variables for the BMP180
  pinMode(BMP180PowerPin, LOW);  // turn tho power off to the BMP180

  // setup the radio transmitter
  if (!driver.init())
    Serial.println("Radio setup failed");
  manager.setThisAddress(TRANSMITTER_ADDRESS);

}

void loop()
{

  Narcoleptic.delay(30000);  // deep sleep for 60 seconds
  Narcoleptic.delay(30000);  // deep sleep for 60 seconds

  pinMode(BMP180PowerPin, OUTPUT); // set the BMP180 Power pin to an output
  digitalWrite(BMP180PowerPin,HIGH);  // turn the power on to the BMP180
  delay(10);  // small delay for BMP180 to start
  temperature = barometerGetTemp(barometerReadUT());
  pressure = barometerGetPress(barometerReadUP());
  Serial.print(temperature/10, 1);
  Serial.println(" deg C");
  Serial.print(pressure/100, 1);
  Serial.println(" hPa");
  pinMode(BMP180PowerPin, LOW);  // turn the power off to the BMP180

  // call the temperature transmit routine
  Serial.print("Transmit temperature value: ");
  Serial.println(temperature);
  transmittemperature(temperature);

  delay (500);  // delay to give receiver time to decode previous message

  // call the pressure transmit routine
  Serial.print("Transmit pressure value: ");
  Serial.println(pressure, 0);
  transmitpressure(pressure);

}


void barometerInit(void)
{
  Wire.begin();
  ac1 = BMP180ReadInt(0xAA);
  ac2 = BMP180ReadInt(0xAC);
  ac3 = BMP180ReadInt(0xAE);
  ac4 = BMP180ReadInt(0xB0);
  ac5 = BMP180ReadInt(0xB2);
  ac6 = BMP180ReadInt(0xB4);
  b1 = BMP180ReadInt(0xB6);
  b2 = BMP180ReadInt(0xB8);
  mb = BMP180ReadInt(0xBA);
  mc = BMP180ReadInt(0xBC);
  md = BMP180ReadInt(0xBE);
}

// Read 1 byte from the BMP180 at 'address'
// Return: the read byte;
char BMP180Read(unsigned char address)
{
  //Wire.begin();
  unsigned char data;
  Wire.beginTransmission(BMP180_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();

  Wire.requestFrom(BMP180_ADDRESS, 1);
  while(!Wire.available());
  return Wire.read();
}

// Read 2 bytes from the BMP180
// First byte will be from 'address'
// Second byte will be from 'address'+1
short BMP180ReadInt(unsigned char address)
{
  unsigned char msb, lsb;
  Wire.beginTransmission(BMP180_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
  Wire.requestFrom(BMP180_ADDRESS, 2);
  while(Wire.available()<2);
  msb = Wire.read();
  lsb = Wire.read();
  return (short) msb<<8 | lsb;
}

// Read the uncompensated temperature value
unsigned short barometerReadUT()
{
  unsigned short ut;
  Wire.beginTransmission(BMP180_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x2E);
  Wire.endTransmission();
  delay(5);
  ut = BMP180ReadInt(0xF6);
  return ut;
}
// Read the uncompensated pressure value
unsigned long barometerReadUP()
{
  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;
  Wire.beginTransmission(BMP180_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x34 + (OSS<<6));
  Wire.endTransmission();
  delay(2 + (3<<OSS));

  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  msb = BMP180Read(0xF6);
  lsb = BMP180Read(0xF7);
  xlsb = BMP180Read(0xF8);
  up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
  return up;
}

void writeRegister(short deviceAddress, byte address, byte val)
{
  Wire.beginTransmission(deviceAddress); // start transmission to device
  Wire.write(address);       // send register address
  Wire.write(val);         // send value to write
  Wire.endTransmission();     // end transmission
}

short readRegister(short deviceAddress, byte address)
{
  short v;
  Wire.beginTransmission(deviceAddress);
  Wire.write(address); // register to read
  Wire.endTransmission();

  Wire.requestFrom(deviceAddress, 1); // read a byte

  while(!Wire.available()) {
    // waiting
  }

  v = Wire.read();
  return v;
}


float barometerGetTemp(unsigned short ut)
{
  long x1, x2;

  x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
  x2 = ((long)mc << 11)/(x1 + md);
  PressureCompensate = x1 + x2;

  float temp = ((PressureCompensate + 8)>>4);
  temp = temp;
  return temp;
}

float barometerGetPress(unsigned long up)
{
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;
  b6 = PressureCompensate - 4000;
  x1 = (b2 * (b6 * b6)>>12)>>11;
  x2 = (ac2 * b6)>>11;
  x3 = x1 + x2;
  b3 = (((((long)ac1)*4 + x3)<<OSS) + 2)>>2;

  // Calculate B4
  x1 = (ac3 * b6)>>13;
  x2 = (b1 * ((b6 * b6)>>12))>>16;
  x3 = ((x1 + x2) + 2)>>2;
  b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;

  b7 = ((unsigned long)(up - b3) * (50000>>OSS));
  if (b7 < 0x80000000)
    p = (b7<<1)/b4;
  else
    p = (b7/b4)<<1;

  x1 = (p>>8) * (p>>8);
  x1 = (x1 * 3038)>>16;
  x2 = (-7357 * p)>>16;
  p += (x1 + x2 + 3791)>>4;

  float pressure = (p/pow(1-(height/44330.0),5.255));
  pressure = pressure;  // convert to hPa from Pa
  return pressure;
}

void transmittemperature(int tempRF)
{
  pinMode(TxPowerPin, OUTPUT); // set the TX Power pin to an output
  digitalWrite(TxPowerPin,HIGH);  // turn the power on to the TX
  char tempdata [6];  // char size of temperature -xx.xC + NULL rounded to even
  itoa(tempRF, tempdata, 10);  // convert variable to array
  manager.setHeaderId(HEADER_TEMPERATURE);

  // Print the pressure data message
  Serial.print("This the is the Temperature data message: ");
  Serial.println(tempdata);

  Serial.println("Sending message");
  // Send a message to the server
  manager.sendto((uint8_t *)tempdata, sizeof(tempdata), RECEIVER_ADDRESS);

  // Wait until the message has been sent before returning
  manager.waitPacketSent();
  Serial.println("Message Sent");
  digitalWrite(TxPowerPin, LOW); // turn off the power to the TX

}

void transmitpressure(long pressureRF)
{
  pinMode(TxPowerPin, OUTPUT); // set the TX Power pin to an output
  digitalWrite(TxPowerPin,HIGH);  // turn the power on to the TX
  char pressuredata [8];  // size of pressure xxxxxx plus NULL rounded to even
  ltoa(pressureRF, pressuredata, 10);  // convert long variable to array
  manager.setHeaderId(HEADER_PRESSURE); // set the headerID to indicate pressure

  // Print the pressure data message
  Serial.print("This the is the Pressure data message: ");
  Serial.println(pressuredata);

  Serial.println("Sending message");
  // Send a message to the server
  manager.sendto((uint8_t *)pressuredata, sizeof(pressuredata), RECEIVER_ADDRESS);

  // Wait until the message has been sent before returning
  manager.waitPacketSent();
  Serial.println("Message Sent");
  digitalWrite(TxPowerPin, LOW); // turn off the power to the TX
}




I have a fritzing sketch and PCB as well, attached

Go Up