ATTiny85v and Sleep Functions

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.

While that's certainly true, if most of the awake time is spent waiting for things to happen (e.g. waiting for an analogRead to complete), then the slower clock is definitely worthwhile - as long as you program the ADC clock prescaler so as to maintain a reasonably high ADC conversion speed.

What you could do if you were concerned about that would be to start an ADC conversion, go to sleep, and let the ADC completion wake you. I have an example of doing that right here:

Scroll down slightly to "Sleep during ADC conversion". That would let you drop to the lower power consumption during the conversion and would also have the beneficial side-effect of reducing ADC noise.

In any case, as I suggested further up this thread, once you get consumption down to a certain low level, your main worry is battery self-discharge. Of course any power consumption on top of that is extra, but it may only be a few percent extra, and who can be certain exactly how much capacity a battery has anyway?

Here's an example:

// Temperature monitoring system

// Author: Nick Gammon
// Date:   16 March 2013
// Version: 1.1 - uses sleep during ADC conversion

// 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 = 19.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 = 0;

// 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,
 };  // end of WDT intervals enum

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

  pinMode (warningLED, OUTPUT);
  digitalWrite (warningLED, LOW);
  
  // stop timer 0
  TCCR0A = 0;
  TCCR0B = 0;
  
  // set the analog reference (high two bits of ADMUX) and select the
  // channel (low 4 bits).  this also sets ADLAR (left-adjust result)
  // to 0 (the default).
  ADMUX = _BV (REFS0) | _BV (REFS1) | (THERMISTORPIN & 0x07);  // Internal 1.1V reference
  
  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

// when ADC completed, take an interrupt 
EMPTY_INTERRUPT (ADC_vect);
  
float getTemperature ()
  {
  byte i;
  float average = 0.0;
  
  // take N samples in a rowy 
  for (i = 0; i < NUMSAMPLES; i++) 
    {
    // ensure not interrupted before we sleep
    noInterrupts ();
    
    // start the conversion
    ADCSRA |= _BV (ADSC) | _BV (ADIE);
    set_sleep_mode (SLEEP_MODE_ADC);    // sleep during sample
    interrupts ();
    sleep_mode (); 

    // reading should be done, but better make sure

    // ADSC is cleared when the conversion finishes
    while (bit_is_set (ADCSRA, ADSC))
      { }
    
    // get ADC data
    byte low, high;
    
    // we have to read ADCL first; doing so locks both ADCL
    // and ADCH until ADCH is read.  reading ADCL second would
    // cause the results of each conversion to be discarded,
    // as ADCL and ADCH would be locked when it completed.
    low = ADCL;
    high = ADCH;
  
    int adcReading = (high << 8) | low;
  
    average += adcReading;
    }  // end of for each of NUMSAMPLES
    
  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

That sleeps during the ADC conversion which should reduce noise.

This rather interesting image shows the power consumption during waking:

I measured the voltage before and after a 1K resistor in series, and took the maths function (CH1 - CH2) to see the current consumption.

Since we have 2.68V drop over 1K, that is 2.68 mA current at the maximum points (less during the ADC conversions).

The 5 x ADC conversion points are clearly visible, followed by a delay in calculating the temperature. The time interval is about 2.5 mS.

My earlier post didn't take into account that this is only done every 32 seconds, so we are consuming 2.68 mA over 2.5 mS, and only doing that once every 32 seconds. So the overall power consumption is low by my reckoning (0.2 uA on average).

The individual conversions are taking 220 uS each which sounds about right as normally an ADC conversion takes 104 uS, but running at half the clock speed (8 MHz) the conversions would now take 208 uS, plus a bit extra for adding to the average.

Just as a follow-up, I've been tweaking this design a bit, documented here:

(That's part of my "power saving" web page: Gammon Forum : Electronics : Microprocessors : Power saving techniques for microprocessors ).

I modified it a bit in the end to have two LEDs - a blue "too cold" one, and a red "too hot" one.

I reduced the "on" time for the LEDs down to 16 mS, to reduce power consumed by them when flashing. They are still perfectly visible. I'm not sure how well that will work with a closed-circuit TV, the on time might be too short to be captured, but you could experiment with that.

The same general concept could be used for things like a wine cellar, or even a fridge monitor. Set the temperature range to that it warns you if the fridge is too hot or too cold. Maybe add in a piezo speaker so you can hear a beeping sound.

I've got it sitting here hooked up to 3 x AA NiMh batteries. The battery voltage is being monitored, and so far hasn't changed at all.

fungus:
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.

Did this. Created a new entry for 128kHz in the boards.txt file. Changed the Low Fuse from 0x62 to 0xe4 to set it to 128kHz. Changed f_cpu to 128000L. I selected that, and chose Burn Bootloader, and it seemed to work, but I didn't look at the output in detail. I tried to upload a blink sketch using the new board entry, and it fails. If I try to select the old 1MHz entry as I was using previously, it still fails. So obviously it chnaged the fuses, but that either destroyed the chip or I am simply unable to now access it. What are the avrdude commands I should use to check the fuses and can anybody tell me EXACTLY how I should have done this? I want to be able to run at 128kHz, or change back to the default 1MHz (8MHz with Divide by 8 turned on). Thanks!