Sleep Modes on ATTiny85
When using microcontroller in battery-based applications we really need to look at current consumption. This will affect the power consumption and hence the lifetime of the device.
This post shows some tests I performed to try and reduce the current consumption of an ATtiny85 AVR microcontroller using sleep modes and switching off various aspects of the IC when they are not required.
The test results and code for the Arudino IDE is shown here.
Test circuit
I needed a test circuit to check both the correct functioning of the different inputs and outputs and also for comparison of the current consumed when using different power modes.
My test circuit comprised of:
- Software serial output every 1 second on pin 2 (Arduino 3)
- Fast PWM 32kHz output at 50% duty on pin 5 (Arduino 0)
- On/Off 0.5s output on pin 6 (Arduino 1)
- Analog read every 1 second on pin 7 (Arduino A1)
- FET output being driven at 32kHz (Fast PWM) at 50% duty (but NO load) on pin 3 (Arduino 4)
I wanted all of this to function at all times. I built this circuit and rigged up a Fluke multi-meter to accurately measure the current.
I used a test input voltage of 16.0V DC from a bench power supply. This is similar to the maximum voltage from a 12V lead acid battery.
Using this test circuit with no power saving I found that the current consumption was on average 16-17mA:
Interestingly, when I did not have a serial output lead attached, the current consumption dropped to 10-13mA. This means that driving a serial output requires an extra 5-7mA. I am unsure why, apart from current required on the output Tx pin? Any ideas anyone?
While not a huge current draw this would still be a high current draw on a small lead-acid battery. This is designed for a renewable energy powered system and hence every uA matters.
Power reduction
There are a few ways we can save power. These include reducing the operating frequency, reducing the operating voltage, put the unit into sleep mode when not required, switch off any internal microcontroller modules when not in use.
I did not want to reduce the operating frequency as this would affect the PWM frequency output and the serial output. I did not want to reduce the operating voltage as this would require a re-design of the FET driver. So I have the option of putting the IC into sleep mode and switching off any modules which are not used.
ATTiny Sleep Modes
From the ATtiny25/45/85 data sheet we can look at section 7 which refers to Power Management and Sleep Modes.
There are also a couple of interesting videos here and here from InsideGadgets. And its always worth reading up from the Arduino website about putting the ‘normal’ arduino (ATmega328) to sleep here.
Another article on testing the Arduino for power modes here.
There are three sleep modes on the ATTiny25/45/85 as shown in the data and copied here:
ATTiny power management
There are also a number of ‘modules’ which can also be switched off to save even more power. These are also mentioned in the data sheet and include:
- Analog to digital converter – I switch this off before going to sleep.
- Analog comparator – In Power_Down sleep mode this is disabled automatically.
- Brown-out detector – I use this to check if voltage has dropped. This is set with fuses internal to the microcontroller
- Internal voltage reference – This is required for the Brown-out detection.
- Watchdog timer- This is used to wake from sleep so cannot switch off.
- The port pins – These are all set to input before sleeping to help save power
Arduino and ATTiny in sleep
In order to put the ATTiny to sleep we need to include two libraries: avr/sleep.h and avr/interrupt.h. One is required to give us access to the sleep functions and the the other is required to control the interrupts to awake from sleep.
To start with I just want to do a power down and see what happens and the current reduction.
Using the following code I set the ATtiny to sleep for 4 seconds and then do all the normal functions (ADC read, Fast PWM output, write to the serial port, light the LED for 0.5 seconds), then go back to sleep.
Putting the IC to sleep massively changed the power consumption. It now consumed around 200uA (0.2mA) when in sleep, with an average of around 2mA, including the LED ON time.
Yey! In sleep much less current consumption (the reading is in uA…)
Averaging over 30ish seconds we get an average of 2.85mA. This depends upon what is switched on when the unit is not asleep, but is much better than our previous value of 12mA.
Arduino code for ATTiny85
Here is the sample code including the sleep function. There are a few things to note here: We use the watchdog timer to wake up from sleep, so no external interrupt is required. When we do wake up due to the watchdog timer the watchdog flag is set, which allows our main code to run and then go back to sleep.
Putting the output pins into input before sleeping mode saves LOTs of current. When I did not do this the current consumption was still around 4mA when in sleep mode.
/* ATtiny85 sleep mode test Overview: This code reads in analog voltage, outputs a Fast PWM and switches ON/OFF an LED. I want to investigate sleep modes to see power consumption and savings. This is to reduce the energy consumed so it can be attached to a lead acid battery. This code is now fully powered down and only wakes up with the watchdog timer This code is designed to run on the ATTiny 25/45/85 The serial output only works with the larger ATTiny85 IC The connections to the ATTiny are as follows: ATTiny Arduino Info Pin 1 - 5 RESET / Rx (Not receiving any data) Pin 2 - 3 Tx for serial conenction Pin 3 - 4 FET driver (PWM) Pin 4 - GND Pin 5 - 0 RED LED (PWM) Pin 6 - 1 GREEN LED Pin 7 - 2 / A1 Vsensor (Analog) Pin 8 - +Vcc See www.re-innovation.co.uk for more details including flow code 14/11/13 by Matt Little (matt@re-innovation.co.uk/www.re-innovation.co.uk) Added code from (http://www.insidegadgets.com/2011/02/05/reduce-attiny-power-consumption-by-sleeping-with-the-watchdog-timer/). Thanks to: * KHM 2008 / Lab3/ Martin Nawrath nawrath@khm.de * Kunsthochschule fuer Medien Koeln * Academy of Media Arts Cologne * * Modified on 5 Feb 2011 by InsideGadgets (www.insidegadgets.com) * to suit the ATtiny85 and removed the cbi( MCUCR,SE ) section * in setup() to match the Atmel datasheet recommendations Updated: 14/11/13 - Added Power_Down Sleep mode - Matt Little This example code is in the public domain. */ #define F_CPU 8000000 // This is used by delay.h library #include <stdlib.h> #include <EEPROM.h> #include <avr/io.h> // Adds useful constants #include <util/delay.h> // Adds delay_ms and delay_us functions #include <avr/sleep.h> #include <avr/interrupt.h> // Routines to set and claer bits (used in the sleep code) #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif // *********** Define I/O Pins ********************** // LED output pins: const int redled = 0; // Red LED attached to here (0, IC pin 5) const int buzzLedSw = 1; // Green LED/buzzer/Switch attached to here (1, IC pin 6) // MOSFET Driver output const int FETdriver = 4; // Analog sensing pin const int VsensePin = A1; // Reads in the analogue number of voltage // Only use Serial if using ATTiny85 // Serial output connections: #include <SoftwareSerial.h> #define rxPin 5 // We use a non-existant pin as we are not interested in receiving data #define txPin 3 SoftwareSerial serial(rxPin, txPin); #define INTERNAL2V56NC (6) // We use the internal voltage reference //************ USER PARAMETERS*********************** //**********MODE************************************* const int deviceType = 85; // 45 = ATTiny45, NO serial output, 85 = AtTiny85, with serial output // Variables unsigned long int averageSensor = 0; // This holds the average value of the sensor // Varibales for EEPROM int hiByte; // These are used to store longer variables into EEPROM int loByte; // Varibles for the calibration factor int calibrationFactor = 0; // This holds the Vref value in millivolts // Variables for the Sleep/power down modes: volatile boolean f_wdt = 1; // the setup routine runs once when you press reset: void setup() { // Set up FAST PWM TCCR0A = 2<<COM0A0 | 2<<COM0B0 | 3<<WGM00; // Set control register A for Timer 0 TCCR0B = 0<<WGM02 | 1<<CS00; // Set control register B for Timer 0 TCCR1 = 0<<PWM1A | 0<<COM1A0 | 1<<CS10; // Set control register for Timer 1 GTCCR = 1<<PWM1B | 2<<COM1B0; // General control register for Timer 1 // Set up IO pins pinMode(rxPin, INPUT); pinMode(txPin, OUTPUT); pinMode(redled, OUTPUT); pinMode(buzzLedSw, OUTPUT); // First want to read the switch pinMode(FETdriver, OUTPUT); digitalWrite(FETdriver, LOW); // Switch the FET OFF if(deviceType==85) { // Start the serial output string - Only for ATTiny85 Version serial.begin(4800); serial.println("SLEEP ATTiny85"); serial.println("14/11/13 Matt Little"); } analogReference(INTERNAL2V56NC); // This sets the internal ref to be 2.56V (or close to this) delay(100); // Wait a while for this to stabilise. // Read in the Calibration Factor hiByte = EEPROM.read(124); loByte = EEPROM.read(125); calibrationFactor = (hiByte << 8)+loByte; // Get the sensor calibrate value serial.print("Calibration Factor: "); // TESTING serial.println(calibrationFactor); // TESTING // Set the Fast PWM output for the Red LED analogWrite(redled, 127); // Set it to 50% running at 31.2kHz analogWrite(FETdriver, 127); // Set it to 50% running at 31.2kHz setup_watchdog(8); // approximately 0.5 seconds sleep } // the loop routine runs over and over again forever: void loop() { if (f_wdt==1) { // wait for timed out watchdog / flag is set when a watchdog timeout occurs f_wdt=0; // reset flag digitalWrite(buzzLedSw, HIGH); // GREEN LED ON _delay_ms(500); // Switch the LED on for 0.5 Seconds averageSensor = analogRead(VsensePin); averageSensor = (averageSensor*calibrationFactor)/1024; // This is the int of the real voltage serial.print("Analog Voltage reading: "); serial.println(averageSensor); digitalWrite(buzzLedSw, LOW); // Green LED OFF // Set the ports to be inputs - saves more power pinMode(txPin, INPUT); pinMode(redled, INPUT); pinMode(buzzLedSw, INPUT); // First want to read the switch pinMode(FETdriver, INPUT); system_sleep(); // Send the unit to sleep // Set the ports to be output again pinMode(rxPin, INPUT); pinMode(txPin, OUTPUT); pinMode(redled, OUTPUT); pinMode(buzzLedSw, OUTPUT); // First want to read the switch pinMode(FETdriver, OUTPUT); } } // set system into the sleep state // system wakes up when wtchdog is timed out void system_sleep() { cbi(ADCSRA,ADEN); // switch Analog to Digitalconverter OFF set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here sleep_enable(); sleep_mode(); // System actually sleeps here sleep_disable(); // System continues execution here when watchdog timed out sbi(ADCSRA,ADEN); // switch Analog to Digitalconverter ON } // 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms // 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec void setup_watchdog(int ii) { byte bb; int ww; if (ii > 9 ) ii=9; bb=ii & 7; if (ii > 7) bb|= (1<<5); bb|= (1<<WDCE); ww=bb; MCUSR &= ~(1<<WDRF); // start timed sequence WDTCR |= (1<<WDCE) | (1<<WDE); // set new watchdog timeout value WDTCR = bb; WDTCR |= _BV(WDIE); } // Watchdog Interrupt Service / is executed when watchdog timed out ISR(WDT_vect) { f_wdt=1; // set global flag }
This allows me to reduce the power consumption of my project (the open source charge controller project).
Now I need to write a program flow which will put the unit into sleep mode as often as possible.
Edit 28/1/2014:
Here is a very good tutorial on sleep modes for the Arduino:
http://donalmorrissey.blogspot.co.uk/2010/04/putting-arduino-diecimila-to-sleep-part.html
Edit 20/11/2020:
After 6 years, I’ve managed to do a bit of an update. Got the code as a github repository. I’ll be updating this a bit more over the next few weeks. This should have examples and some circuit diagrams for some of this work:
https://github.com/curiouselectric/SolarSpinner
Good post mate… It help me a lot.
Thanks for doing this. I would like to reuse some of this code for my own LED lighthouse simulator which I originally did in assembly using the Atmel Studio IDE on Win XP. I am trying to get your code to compile but it keeps telling me its run out of program memory (2k). I am using Arduino 1.8.2 on Max OS and setting the board description to ATTiny25/45/85. It seems to think this code is for a 25. Is there something I add to the code to tell the IDE that it’s for an 85? ( I also tried Adafruit Gemma Tiny85 but had an issue with A0).
Two typos above, A1 not A0 and Trinket not Gemma.
I changed A1 to 7 for the analog input and it compiled using Adafruit Trinket.
According to datasheet (see fig. 22-12), an ATtiny in deep sleep, with Watchdog enabled, need less than 10µA.
The remaining 180µA has to be consumed by the board (LDO, Pull-up/down resistor etc.).
Ps : Another typo : “Arudino 3”.
Very useful information and code, thanks. With a logic level P-channel mosfet controlled by the ATtiny to switch power to the servo in my project, I have now achieved a sleep-mode current of 6.5 microamps.
Would love to see a revised version, for 85 family, but also the new 88s… Thank you for taking time to help others, and they have no doubt helped you. -pat 🙂
Hi Pat,
I’ve just added some code examples to the post and done a bit of a re-write/update.
I’ll be working on it a bit more, as I’m using an ATTiny85 in a project at the moment.
For the ATTiny88, I think the programming side of things should be quite easy, using the ATTiny x8 core:
https://github.com/SpenceKonde/ATTinyCore
The registers might be different and will need checking on the ATTiny88 datasheet.
I’ve not got a smaple of that IC so hard for me to develop this.
Let us know if you find anything out!
Regards,
Matt
Just a silly question but if you go to sleep multiple times in the loop do you need to reset the f_wdt=0 each sleep times as well ?
Hi,
Not a silly question at all!
When you sleep then at wake up you will carry one your code from where the unit had the sleep command.
The f_wdt is a flag to deal with the watch dog timer.
In the example I’m using the WDT to wake up the unit. So the WDT runs the WDT_ISR when it gets to it’s timeout.
This sets the flag and then the unit can deal with whatever needs to happen in the main loop.
You can go to sleep without setting the f_wdt and it will still sleep and then start again at wakeup from the point at which it sleeps.
The main reason to service the f_wdt and reset it is so that you always get the next WDT interrupt and so timing is accurate.
I generally use flags set on Interrupt service routines which tell me what has woken up the unit and the service that as appropriate.
Hmmm… Not sure if I have answered your question…
Have a go and see what happens!! Thats usually what I do.
Cheers,
Matt
Strangely, I do not get anywhere near the power savings stated. Mine is around 11mA in sleep.
Tried everything to get that number down.
What is in your circuit?
It could be a voltage regulator (these can take quite a few mA). I’ve just taken these from my ESP32 units and gone from 6mA constant current down to <0.5mA...
Also potential dividers and LEDs all consume a little bit which can add up. Check potneital divider resistor values - can then be higher (use 10k rather than 1k etc)? This will reduce current consumption.
Can you isolate just the ATTiny and measure current to it?
What are you measuring current with?
I'm using a Fluke multimeter which reads pretty accurately to relatively low currents but I would not trust it for real readings less than 1mA.
Can you remove all other processes the ATTiny is doing in code and just put it to sleep, then maybe wake up for a second then back to sleep - you should then see the current change if the IC is properly going to sleep.
Is the unit going to sleep then being woken up instantly? Maybe by an external interrupt?
Hope you get it working,
Regards,
Matt
Hi i tried the same code to read my solar pin but unfortunately it is constantly reading 0V.
#include
#include // Adds useful constants
const int VsensePin = A1;
#define INTERNAL2V56NC (6)
unsigned long int averageSensor = 0; // This holds the average value of the sensor
// Varibales for EEPROM
int hiByte; // These are used to store longer variables into EEPROM
int loByte;
// Varibles for the calibration factor
int calibrationFactor = 0; // This holds the Vref value in millivolts
volatile boolean f_wdt = 1;
const int redled = 0;
void setup() {
Serial.begin(9600);
pinMode(redled, OUTPUT);
analogReference(INTERNAL2V56NC);
delay(100);
setup_watchdog(8); // approximately 0.5 seconds sleep
}
void loop() {
// put your main code here, to run repeatedly:
if (f_wdt==1) { // wait for timed out watchdog / flag is set when a watchdog timeout occurs
f_wdt=0; // reset flag
averageSensor = analogRead(VsensePin);
averageSensor = (averageSensor*calibrationFactor)/1024; // This is the int of the real voltage
Serial.print(“Analog Voltage reading: “);
Serial.println(averageSensor);
}
}
void setup_watchdog(int ii) {
byte bb;
int ww;
if (ii > 9 ) ii=9;
bb=ii & 7;
if (ii > 7) bb|= (1<<5);
bb|= (1<<WDCE);
ww=bb;
MCUSR &= ~(1<<WDRF);
// start timed sequence
WDTCSR |= (1<<WDCE) | (1<<WDE);
// set new watchdog timeout value
WDTCSR = bb;
WDTCSR |= _BV(WDIE);
}
// Watchdog Interrupt Service / is executed when watchdog timed out
ISR(WDT_vect) {
f_wdt=1; // set global flag
}
Hi,
A quick scan of your code:
You have set calibrationFactor = 0. Then you perform the following calculation: averageSensor = (averageSensor*calibrationFactor)/1024;
So averageSensor will always be 0.
In my code I stored the calibration factor into EEPROM. Then I read it back into the calibrationFactor on start-up.
I’d suggest trying:
int calibrationFactor = 2560; (2.56V in milli-volts)
This will give you a voltage reading, but it may not be totally accurate (as the 2.56V internal ref is not that accurate).
Matt