ATTiny85v and Sleep Functions

I will be programming an ATTiny85v using an Arduino as the programmer. My end project will be the ATTiny with 2xAA's (~3.2V), one analog temperature sensor, and one IR LED for output. The program will basically take 50 (or whatever) readings, throw out the lowest 10 and highest 10, then average the remaining readings to get a solid temperature reading. It will then blink the IR LED slowly for 2 seconds if the temperature is below a range, leave it off if within the acceptable range, and blink fast for 2 seconds if above that range. Then it will wait for 30 seconds and do it all again.

I have this running well on the Arduino itself, and will transfer it to the ATTiny85v.

But I need some help for figuring out how to maximize battery life. This will be a constant ON device (to monitor temperatures in babies' rooms and show me via the night vision cameras if it is too cold or hot). I found the code to use the power down sleep mode, and I found somebody who modified it to work on the ATTiny85, so I should be able to get that to work. I am wondering if I can use that to put it to sleep/wake up during the light blinking process. Basically, would there be any issues using the system_sleep() function in place of ALL delay() calls? The examples set the watchdog timer duration during the setup, so it would have to use the same sleep time each time it is called, but it would be nicer if a different time could be used (for example: half second in between LED blinks, and 8 seconds inside of a loop 5 times for a 30 second delay). Is that possible?

I was also wondering about any/all other tips for maximizing battery life. I see I can call power_adc_disable(), power_timer0_disable(), power_timer1_disable(), and power_usi_disable(). I can disable ADC during sleep and re-enable after. What about the timers and the USI? Can I disable the USI completely in the setup, or would that prevent me from flashing another program onto it??? I'm worried about that! And do I need timer0 and timer1? It is unclear to me exactly what those are needed for. If I disable them before a sleep call, would it turn off the watchdog timer and not wakeup?

So many questions, but I am learning quickly. Thank you all for your help!

jrburke99:
Basically, would there be any issues using the system_sleep() function in place of ALL delay() calls?

No.

jrburke99:
The examples set the watchdog timer duration during the setup, so it would have to use the same sleep time each time it is called, but it would be nicer if a different time could be used (for example: half second in between LED blinks, and 8 seconds inside of a loop 5 times for a 30 second delay). Is that possible?

The watchdog time can be changed as often as you like.

jrburke99:
I was also wondering about any/all other tips for maximizing battery life. I see I can call power_adc_disable(), power_timer0_disable(), power_timer1_disable(), and power_usi_disable(). I can disable ADC during sleep and re-enable after. What about the timers and the USI? Can I disable the USI completely in the setup, or would that prevent me from flashing another program onto it??? I'm worried about that! And do I need timer0 and timer1? It is unclear to me exactly what those are needed for. If I disable them before a sleep call, would it turn off the watchdog timer and not wakeup?

Timer0 generates the interrupt which increments millis(), you might need that. Timer1 normally isn't needed (it's used for PWM generation if you do analogWrite()).

Disabling USI won't affect programming (the first thing a programmer does is RESET the chip).

The power consumption of all those modules is listed in the datasheet (in "Power Management" and "Typical Characteristics" sections). The worst offenders are Timer1 and the ADC.

jrburke99:
So many questions, but I am learning quickly. Thank you all for your help!

This is quite advanced stuff... :slight_smile:

I suggest you program the fuses to use the 128kHz internal oscillator instead of the 8MHz one, and program the clock prescaler register to run the processor at the lowest clock frequency that you can manage with. This way, you can get the current consumption of the chip down to a fraction of a millamp while it is awake.

If you do this, then I doubt that it's worth having the device sleep while blinking the IR LED, since the ATtiny current consumption will be tiny compared to the IR LED.

Timer0 generates the interrupt which increments millis(),

Are you sure ?

From the core_build_options.h

/*
For various reasons, Timer 1 is a better choice for the millis timer on the
'85 processor.
*/
#define TIMER_TO_USE_FOR_MILLIS 1

Erni:

Timer0 generates the interrupt which increments millis(),

Are you sure ?

From the core_build_options.h

/*
For various reasons, Timer 1 is a better choice for the millis timer on the
'85 processor.
*/
#define TIMER_TO_USE_FOR_MILLIS 1

You must have a different core than me. Mine uses timer0.

If you're worried about power, Timer1 uses 10 times as much power as Timer0. :slight_smile:

There are a lot of things you can do to save power:

Using the watchdog timer to sleep is a reasonable way of allowing time to elapse. Clearly, very exact timing it not important.

If I may suggest, some other blink pattern to indicate the battery power is low? There are ways of self-detecting battery power. I'm not sure about the Attiny85, but a quick look at the datasheet would appear to indicate it does that too.

I am wondering if I can use that to put it to sleep/wake up during the light blinking process.

Yes you can, although as dc42 says, there may not be much point.

What about the timers and the USI?

If you use the watchdog, you won't care about the timers.

Just out of curiosity (in case I want to make one of these) how are you planning to read the temperature accurately, in a way that is resistant to fluctuating supply (battery) voltage?

fungus:
Timer0 generates the interrupt which increments millis(), you might need that. Timer1 normally isn't needed (it's used for PWM generation if you do analogWrite()).

Wow! Great info from all! I am not using the millis() function, so I should be good there. I just wanted to make sure the watchdog timer will still work. I use the ADC to read the temperature sensor, and I use the INTERNAL 1.1v reference voltage for that reading as well. But I should be able to turn that all off during sleep and then back on without any serious issues. The datasheet mentions a brief wait time for the internal reference voltage to start back up, so I might add a tiny delay(10) before it begins taking readings. And right now, I have it taking 100 readings and throwing out the highest 20 and lowest 20 readings before averaging. By doing that and using the internal voltage reference,I get pretty steady temperature readings.

dc42:
I suggest you program the fuses to use the 128kHz internal oscillator instead of the 8MHz one, and program the clock prescaler register to run the processor at the lowest clock frequency that you can manage with. This way, you can get the current consumption of the chip down to a fraction of a millamp while it is awake.

If you do this, then I doubt that it's worth having the device sleep while blinking the IR LED, since the ATtiny current consumption will be tiny compared to the IR LED.

Nice. I have seen some about programming the fuses with avrdude commands. Can anyone provide the correct command to make these recommended settings changes?

How will running it at 128kHz affect my timing? For example, if in my test program I am using a delay(1000) on the Arduino itself to wait 1 second, what does that get translated to on the ATTiny85v running at 128kHz?

Will that change the watchdog timing as well? If I have the watchdog set to wait 8 seconds, will it still be 8 seconds at the slower frequency? Thanks!

jrburke99:
Nice. I have seen some about programming the fuses with avrdude commands. Can anyone provide the correct command to make these recommended settings changes?

See AVR® Fuse Calculator – The Engbedded Blog to get the correct fuse values and the avrdude docs for the corresponding commands.

jrburke99:
How will running it at 128kHz affect my timing? For example, if in my test program I am using a delay(1000) on the Arduino itself to wait 1 second, what does that get translated to on the ATTiny85v running at 128kHz?

Depending on the core you are using, if you set F_CPU to 128000 in your board config when compiling, delay() may still give the correct timings.

jrburke99:
Will that change the watchdog timing as well? If I have the watchdog set to wait 8 seconds, will it still be 8 seconds at the slower frequency? Thanks!

The watchdog timer is always clocked at 128kHz, so it won't be affected.

I use analogReference(INTERNAL); in the setup. The ATTiny85 also has a 1.1V internal reference voltage, so this should work there. Works very well on the Arduino! Before using this, I was getting lots of stray readings (even though I was running off a steady 5v USB supply), and as soon as I switched it, I am now getting very steady readings. I hope those steady readings will translate well to the use of batteries.

Which brings up another point. What do I need to do about low batteries? I saw something that said the ATTiny can be damaged if the batteries get low, and you can use the built-in BOD, but I never saw a good example on exactly how to implement it. DO I really need to worry about that?

If I get enough life out of the 2 AA's, I would be interested in moving to maybe 2 CR2032's in parallel. It would make a smaller package.

To use the BOD, all you need to do is program the fuses.

dc42:

jrburke99:
Nice. I have seen some about programming the fuses with avrdude commands. Can anyone provide the correct command to make these recommended settings changes?

See AVR® Fuse Calculator – The Engbedded Blog to get the correct fuse values and the avrdude docs for the corresponding commands.

I am on there now, and I see 3 options for 128kHz, and not really sure what they all mean. Which options should I pick???

Page 29 of the ATtiny25/45/85 datasheet lists the recommended usage for each of the 3 options.

I saw something that said the ATTiny can be damaged if the batteries get low

Where?

jrburke99:
Nice. I have seen some about programming the fuses with avrdude commands. Can anyone provide the correct command to make these recommended settings changes?

It's much easier to edit your "boards.txt" and create a new option in there called "ATiny85, 128kHz clock" (or whatever). The entries for each board have fuse settings.

Then you just select it and do "burn bootloader" from the IDE.

Whilst using a slower clock speed can save power consumption, the downside is that when it is awake it takes longer to do things. Much longer if you compare 128 KHz to 8 MHz.

The brown-out detection itself uses quite a lot of power, I measured it here:

I'm not sure how useful the BOD is. If you fall below the threshold the processor will reset. If it keeps resetting then it will effectively do nothing, making it hard or impossible to flash a warning LED.

Another battery test idea might be to have a button or pad that you touch with your finger on the side of the device. If touched it would flash the LED (or a different LED) as a "I am alive" confirmation. Since presumably with babies you check them from time to time, that could be part of the daily routine, to check that the temperature monitor is alive.

There's another active thread right now about battery discharging and detecting when you have "two days to go". The problem is the flat discharge curve, so a minor change to voltage can mean the difference between being quite fresh and almost dead.

I've been having a play with this project. I made up a similar thing to what was described. A thermistor, an LED to flash, and some upper and lower temperature limits. Code here:

// Temperature monitoring system

// Author: Nick Gammon
// Date:   16 March 2013

// Thermistor calculation adapted from: http://learn.adafruit.com/thermistor/using-a-thermistor

#include <avr/sleep.h>
#include <avr/wdt.h>

// Flash LED if temperature outside this range:
const float lowTemp = 22.0;      // degrees C
const float highTemp = 26.0;     // degrees C

// the bigger this is, the less power we consume
const int timeBetweenReadings = 30;  // seconds

const byte warningLED = 8;    // which LED to flash

#define DEBUGGING false

// Wiring:   Gnd <----> 5K Thermistor <----> |   <----->>   4.7K resistor  <-----> AREF
//                                           |
//                                           v
//                                           A0

// which analog pin to connect
const byte THERMISTORPIN = A0;

// temp. for nominal resistance (almost always 25 C) 
const int TEMPERATURENOMINAL = 25;

// resistance at TEMPERATURENOMINAL (above)
const int THERMISTORNOMINAL = 5000;

// how many samples to take and average, more takes longer but is more 'smooth'
const int NUMSAMPLES = 5;

// The beta coefficient of the thermistor (usually 3000-4000) 
const int BCOEFFICIENT = 3960;

// the value of the 'other' resistor (measure to make sure)
const int SERIESRESISTOR = 4640;

// how many Kelvin 0 degrees Celsius is
const float KELVIN = 273.15;

// what was our last reading
float lastReading;  // degrees

// how many seconds till we take another reading (updated as we enter sleep mode)
float nextReadingTime = 0;  // seconds

// watchdog intervals
// sleep bit patterns for WDTCSR
enum 
 {
  wdt_16_mS  =  0b000000,
  wdt_32_mS  =  0b000001,
  wdt_64_mS  =  0b000010,
  wdt_128_mS =  0b000011,
  wdt_256_mS =  0b000100,
  wdt_512_mS =  0b000101,
  wdt_1_sec  =  0b000110,
  wdt_2_sec  =  0b000111,
  wdt_4_sec  =  0b100000,
  wdt_8_sec  =  0b100001,
 };
 

void setup (void) 
  { 
    
#if DEBUGGING
  Serial.begin(115200); 
#endif // DEBUGGING

  pinMode (warningLED, OUTPUT);
  digitalWrite (warningLED, LOW);
  lastReading = getTemperature ();
  }  // end of setup
  
// watchdog interrupt
ISR (WDT_vect) 
{
  wdt_disable();  // disable watchdog
}

void myWatchdogEnable (const byte interval) 
  {
  // clear various "reset" flags
  MCUSR = 0;     
  // allow changes, disable reset
  WDTCSR = _BV (WDCE) | _BV (WDE);
  // set interrupt mode and an interval 
  WDTCSR = _BV (WDIE) | interval;    // set WDIE, and requested delay
  wdt_reset();  // pat the dog

  // disable ADC
  byte old_ADCSRA = ADCSRA;
  ADCSRA = 0;  
  
  // turn off various modules
  byte old_PRR = PRR;
  PRR = 0xFF; 

  // timed sequence coming up
  noInterrupts ();
  
  // ready to sleep
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
  sleep_enable();

  // turn off brown-out enable in software
  MCUCR = _BV (BODS) | _BV (BODSE);
  MCUCR = _BV (BODS); 
  interrupts ();
  sleep_cpu ();  

  // cancel sleep as a precaution
  sleep_disable();
  PRR = old_PRR;
  ADCSRA = old_ADCSRA;
  
  } // end of myWatchdogEnable

 
float getTemperature ()
  {
  byte i;
  float average = 0.0;
  analogReference (INTERNAL);
  
  // take N samples in a rowy 
  for (i = 0; i < NUMSAMPLES; i++) 
    average += analogRead (THERMISTORPIN);

  average /= NUMSAMPLES; 

  // convert the value to resistance 
  average = 1023 / average - 1; 
  average = SERIESRESISTOR / average; 

  float steinhart = average / THERMISTORNOMINAL;
  steinhart = log (steinhart); 
  steinhart /= BCOEFFICIENT; 
  steinhart += 1.0 / (TEMPERATURENOMINAL + KELVIN); 
  steinhart = 1.0 / steinhart; 
  steinhart -= KELVIN;   // back to celsius
  return steinhart;
  }  // end of getTemperature
  
void takeReading ()
  {
  lastReading = getTemperature ();
#if DEBUGGING
  Serial.print ("Temperature = "); 
  Serial.print (lastReading);
  Serial.println (" *C");
#endif // DEBUGGING

  if ((lastReading < lowTemp) || (lastReading > highTemp))
    nextReadingTime = 5;    // if out of range, read again in 5 seconds
  else
    nextReadingTime = timeBetweenReadings;    // when to take another reading
  }  // end of takeReading
  
  
void loop (void) 
  { 
  if (nextReadingTime <= 0)
    takeReading ();
    
  byte waitTime;
  
  if (lastReading < lowTemp)
    {
    waitTime = wdt_512_mS;
    nextReadingTime -= 0.5;
    digitalWrite (warningLED, ! digitalRead (warningLED));
#if DEBUGGING
    Serial.println ("Too low!");
#endif // DEBUGGING
    }
  else if (lastReading > highTemp)
    {
    waitTime = wdt_256_mS;
    nextReadingTime -= 0.25;
    digitalWrite (warningLED, ! digitalRead (warningLED));
#if DEBUGGING
    Serial.println ("Too HIGH!");
#endif // DEBUGGING
    }
  else
    {
    // temperature OK - sleep for 8 seconds
    waitTime = wdt_8_sec;
    nextReadingTime -= 8; 
    digitalWrite (warningLED, LOW);
    }
    
#if DEBUGGING
  // force serial data out, wait for UART to empty
  Serial.flush ();
#endif // DEBUGGING

  myWatchdogEnable (waitTime);
  }  // end of loop

I am setting the flash rates for the LED by simply choosing different watchdog timer intervals. It checks the temperature every 32 seconds or so (3 x 8-second watchdog intervals). If OK, it sleeps for 8 seconds. If not, it sleeps for 0.25 or 0.5 seconds to give a slow flash (cold) or a fast flash (hot).

This is written for the Atmega328P chip! I haven't tried to convert to the Attiny85.

I was interested in the power consumption. Whilst asleep (most of the time) it uses about 4.2 uA when running at 2.6V using the 8 MHz internal oscillator. This can almost entirely be accounted for by the documented overhead of the watchdog timer itself, so I doubt it can go any lower.

The figure of 4.2 uA is also substantially less than what I believe is the self-discharge rate of AA batteries anyway, so your major issue is not what the circuit is using, but that the battery will self-discharge anyway.

A bit of measuring shows that the time taken to wake up, and go back to sleep is around 150 uS. The time to take a reading (it is taking 5 temperature measurements) is 2.2 mS, during which it consumes (I think) around 2 mA.

By my calculations that averages out at another 4.4 uA.

To take a reading over 2.2e-3 seconds means you can take 454 readings (1/2.2e-3 = 454)
Effectively rather than using 2 mA per second you are using 2/454 mA, that is 4.4 uA (2/454 = 0.0044)

I attempted to self-calculate the Vcc voltage but found that it wasn't a success with the thermistor there. Not sure if that is a programming issue or a hardware one.

What you could do for under-voltage tests is to do something like what Dave Jones describes here

For this device he has a switch that turns on a "voltage test". An LED lights if the battery voltage is OK. So you could have a push-button that brings a similar device into circuit, it tests the voltage against a reference point you choose, and if OK, the LED lights.

So what you might do is, when installing the device (or every night) push the "test" button, and confirm the LED lights up.