Close

Power Saving Code

A project log for Light Up Raccoons

A little coin cell powered raccoon with capacitive sensing to light up its eyes

savoSavo 02/01/2023 at 18:150 Comments

With the capacitive sensing and LED driving code complete, my focus shifted to the meat of this project: power saving. I wanted this system to operate off a coin cell battery so it could be put on display wherever someone wanted without needing a power connection and I didn't want people to have to change the battery more often than once a year or so.

Baseline

I first began by trying to have the system idle using the delay() function for ten seconds between checks. I hooked up my multi-meter to monitor the power supplied to the system and began to monitor. For reference, I planned to use CR2032 batteries, which provide 3V with a capacity of around 340mAh.

Current draw when using delay() to idle for 10 seconds at a time. (5V, 8MHz)
Current draw when using delay() to idle for 10 seconds at a time. (5V, 8MHz)

With the ATtiny85 running at its default 8MHz internal clock at 5V, the current draw averaged about 7.2mA. This would kill a CR2032 in just under 48 hours. Although not super relevant to the project, it was odd to see a noticeable saw-tooth pattern to to current draw as it steadily increased during the delay() call. I wonder why that was.

With this (horrible) baseline I went about doing two simple things. First I lowered my supply voltage to match the expected 3V it would have in system (instead of 5V), and then lowered the clock of the microcontroller from 8MHz to 1MHz. These corresponded to a 33% and 75% reduction in power draw, down to about 1.4mA with both in effect.

Then I began to change my code to save power. First I started by following the recommendation in the data sheet (Section 7.4.6) to drive unused pins rather then leaving them floating. If left floating they will hover around half the supplied voltage and thus constantly toggle the digital input buffers between states, drawing power. This is shown in the plot below, where the unused pins are driven low for the leftmost (oldest) section and right most, but left to hover in the middle. This saved me roughly 1mA!

The difference in current draw when unused pins are driven low (start and end) compared to them being left floating (middle).
The difference in current draw when unused pins are driven low (start and end) compared to them being left floating (middle).

Idling at about 1.4mA of draw, meant that the system would last about 10 days. Still far off the several months I needed, let alone the year I wanted. I knew the ATtiny could do better since they advertised a sleep current of only 1.8uA at 1MHz and 1.8V. So I started looking into how to properly disable peripherals and put the microcontroller into sleep modes, rather than just using delay().

Power Management Library

Using the AVR power.h library helped me disable power to peripherals that I wasn’t using to save power, by using "__power_all_disable()" and "__power_all_enable()". I also wrote to some of the power management registers myself where it seemed the the power library didn’t have an effect.

// Reduce power use by disabling peripherals
ACSR |= _BV(ACD); // Disable analog comparator
DIDR0 = _BV(AIN1D) | _BV(AIN0D); // Disable digital input buffers on analog pins (we don't need digital in)
PRR = _BV(PRTIM1) | _BV(PRTIM0) | _BV(PRUSI) | _BV(PRADC); // Timers, universal serial interface, ADC

These lines of code shaved off a several hundred uA, bringing the system down to about 800uA idle current consumption.

Sleep Library

The final frontier of power reduction was getting the microcontroller to sleep properly. In deep sleep basically everything inside the ATtiny is shutoff with the exception of the watchdog timer and some of the input systems so that the microcontroller can be brought back into action. Since I wanted the raccoon to check periodically, every couple of seconds or so for a touch event I wanted to use the watch dog timer to pull the microcontroller out of the deepest sleep.

To put the ATtiny into sleep I used the AVR sleep library. I would simply set the sleep mode in my setup with just "set_sleep_mode(SLEEP_MODE_PWR_DOWN)" so the ATtiny would go into the deepest of sleep when prompted. To put the microcontroller into sleep mode, I just needed to call "sleep_mode()", and then to disable sleep mode after being “awoken”, "sleep_disable()".

Setting up the watchdog only took a few lines, done in the required sequence outlined in the data sheet. The interrupt it triggers is empty as it only serves to pull the ATtiny out of sleep.

MCUSR &= ~_BV(WDRF); // Reset WDT

// Start timed sequence to set
WDTCR |= _BV(WDCE) | _BV(WDE);  // Set new watchdog timeout value WDTCR = _BV(WDCE) | _BV(WDP2) | _BV(WDP1) | _BV(WDP0); // 2 seconds

//  WDTCR = _BV(WDCE) | _BV(WDP3) | _BV(WDP0); // 8 seconds
//  WDTCR = _BV(WDCE) | _BV(WDP2) | _BV(WDP1); // 1 second
//  WDTCR = _BV(WDCE) | _BV(WDP2) | _BV(WDP0); // 0.5 seconds  WDTCR |= _BV(WDIE);

Bringing it all Together

With sleep and the power reduction code implemented, the idle current draw of the system was brought down to about… 4.5uA! (This value varied slightly board to board later, but all were below 5uA.) At this draw it will last about 3000 days, or roughly eight years on a single CR2032. Granted it will likely be much lower because it consumes several mA when running the capacitance check for a few milliseconds each couple of seconds, and then several mA for a few seconds when a touch is detected. Even so I don’t think it would be unreasonable to expect these to operate for over a year under normal use (lighting up a couple of times a day).

Final power draw
The current draw for the raccoon (The spikes are from checking for capacitance which only takes a few milliseconds)

For some reason I needed to manually disable the ADC using its control register, in addition to calling "__power_all_disable()" before going to sleep, otherwise it runs through sleep. Without specifically disabling it, the sleep draw would be 230uA higher. This took me quite a while to figure out. It appears that this is intentional on AVR's part to allow for ADC measurements in a low-noise read mode with the rest of the MCU asleep.

Discussions