ATtiny85 deep sleep consumption on the high side

I was playing with a small battery-powered circuit that intends to periodically flash an LED. It's for amusement's sake, really, so nothing fancy.

The 'problem' (observation) I'm having is that the ATtiny85 is drawing far more current in deep sleep than anticipated. The mcu draws about 185uA; I would expect it to draw about 10uA according to the datasheet.

The hardware is DIY and I'm currently working with a prototype/test PCB that has some bug fixes:
image

The schematic of the circuit as built (including modifications) is as follows:


Some remarks:
R5 is really just a piece of wire.
SW1 is a "touch" pad which is simply a GND and a signal trace wrapped around each other on the PCB; see photo above.
Yes, LED1 has no current limiting resistor. This is intentional. No, it doesn't damage the microcontroller or the LED when run properly (i.e. brief flashes of a couple dozen ms). It's also not the cause of the surprisingly high deep sleep current draw.
Yes, I could probably shave off another 8uA by removing R8.
Yes, I could probably shave off some more by changing some resistors to higher values, but I'm well-stocked up to 1Meg and very poorly so above that...
Nothing is connected to the SPI header when taking the measurements.

Current is measured with a cheap DMM in the negative lead going to the battery. I cross-checked the measurements with a second DMM; they measure virtually the same current consumption.
The voltage and current measurements are taken when running on battery power with the mcu in deep sleep ('power down') with only the watchdog timer running (well, that's what I think, at least... )

I've made a minimum demonstrator code that produces the measurements as reported:

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

void setup() {
  ADCSRA &= ~ADEN;                     // ADC off
  MCUCR |= BODS;                      //Disable brown-out detector during sleep
  while (1) {
    system_sleep();
  }
}

void loop() {

}

//Modified from https://www.marcelpost.com/wiki/index.php/ATtiny85_WDT_sleep
void setup_watchdog(int ii) 
{
  // 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
  // 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
  uint8_t bb;
  if (ii > 9 ) ii=9;
  bb=ii & 7;
  if (ii > 7) bb|= (1<<5);
  bb|= (1<<WDCE);

  MCUSR &= ~(1<<WDRF);  //Clear WDT interrupt flag
  WDTCR |= (1<<WDCE) | (1<<WDE);  //Start timed sequence
  WDTCR = bb;         // set new watchdog timeout value
  WDTCR |= _BV(WDIE); //WDT interrupt enable
}

//Modified from https://www.marcelpost.com/wiki/index.php/ATtiny85_WDT_sleep
// system wakes up when watchdog is timed out
void system_sleep() 
{
  DIDR0 &= ~(AIN1D | AIN0D);           //Disable ADC digital inputs
  setup_watchdog(9);                   // approximately 32ms sleep
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here ; SLEEP_MODE_IDLE, SLEEP_MODE_ADC, SLEEP_MODE_PWR_DOWN
  sleep_enable();
  sei();                               // Enable the Interrupts so the wdt can wake us up
  power_all_disable();                  //Disable all peripherals
  sleep_mode();                        // System sleeps here
  sleep_disable();                     // System continues execution here when watchdog timed out 
}

// Watchdog Interrupt Service / is executed when watchdog timed out
ISR(WDT_vect)
{
  asm("NOP");
}

It's modified from the slightly larger sketch that also flashes the LED etc. In this version above the microcontroller does nothing except sleep, and then sleep some more.

I've also desoldered the ATtiny85 from the circuit to verify that the current consumption from the rest of the switching hardware (the bunch of transistors etc.) is not the culprit; indeed, it isn't; everything except the uC draws something like 75uA. The 'excess' current is really drawn by the controller itself.

In the system as built I run the uC from the internal 1MHz clock, but it doesn't matter since the clock is disabled most of the time in the sketch above. Indeed, running a faster 8MHz internal RC doesn't change a thing, as expected. I'm using ATtinyCore.

The most plausible explanation I can come up with so far is that the controller is counterfeit. I purchased this one a few years ago (2019?) from AliExpress. I've used several from the same batch and they all work fine, but this is the first time I try minimizing power use with one. I can very well imagine that these are reasonably good copies that only fall through when they're pushed to the limits - in this case in terms of dissipation.
Here's the chip btw:

The alternative explanations would be:

  • Somehow there are peripherals enabled/running (e.g. internal voltage reference?) that I'm not aware of. I've gone through the relevant bits of the datasheet, but there may be stuff happening 'under the hood' of the Arduino core that I'm overlooking.
  • The uC is drawing current through the circuit through a route I'm overlooking in my measurements. But for the life of me, I'm not seeing where this would be happening.

Any thoughts/musing/ramblings are welcome. As said, it's not a real/serious project, so I'm OK if this happens to be the end of the line for this circuit.

I mainly wanted to mess with a touch-activated circuit that doesn't rely on a uC for keeping a power assert enabled (so it can sleep with all of its GPIO's floating, and then wake up from the WDT) and at the same time use the touch pad as a user input while the circuit runs. It does all this, alright. Power draw with Q1 not conducting is below the measurement threshold, the circuit wakes up nicely when the touch pad is touched, and the 'button' input also works fine. So basically this is already a success, but it would be a nice touch if I could get the current draw down a little further.


This is the whole circuit, but running the code that includes button read and LED flashes (note the LED that's lit in the photo). It's running off an 'empty' CR2032 here, flashing the LED every 5 seconds.

Here is a simple circuit that would do the same thing for probably less in money and time.
image

No, not quite, but that's also nice in its simplicity.

1 Like

I've decided to not bother digging deeper into the issue for now and just complete the 'project'.

Schematic:


Ignore the voltage reading on PB0/MOSI and PB3; these are from the previous version and are 0V and +Vcc respectively in the version pictured above.

The circuit works as follows:
Q1 acts as a high-side main switch, with the gate pulled up high through R1 and Q3 as battery power is connected. R2 ensures Q3 conducts by default. When the 'switch' is touched, the gate of Q1 is pulled sufficiently low to start conducting, which starts a positive feedback loop through Q2, forcing Q1 fully 'on'. C1 protects against spurious signals and prevents the circuit from coming alive randomly through pickup of EMI, radio etc.

When the circuit switches on and Q1 conducts, Q4 also starts to conduct, pulling the gate of Q3 low. This decouples the gate of Q1 from the touch sensor/switch, leaving it floating again. This enables the touch sensor/switch to be used as a digital input, which is buffered by Q5. This buffer proved to be necessary as the ATtiny85 has more leakage current than anticipated (maybe this is related to the current consumption issue), although a breadboard test setup using an Atmega328P (Arduino Nano) worked OK with a 'bare' GPIO as an input. In this test setup I used the ADC to read the 'button' value and detect a minor change in voltage. The buffered version as shown here has the added advantage that the ADC isn't needed, saving a tiny bit of power. In the version as built I used a 2n7002 from the parts bin for Q5 instead of the BSS123. Either should work.

In operation, the ATtiny85 flashes the LED every 5 seconds or so by pulling PB4 HIGH for about 20ms. Despite the lack of a current limiting resistor, neither the LED nor the ATtiny suffer from this, and it also doesn't create a brownout condition or spurious resets.

When the button is pressed (touched), the controller gives a brief burst of LED flashes and then pulls pin PB0 high. This makes Q6 conduct, discharging C1 and cutting current flow through the bases of Q2 and Q4. The gate of Q1 floats back up and the circuit is again in its start, 'off' state. Current consumption in this state is effectively zero and limited to tiny leakage currents that I didn't bother to measure. They appear to be below 1uA.
Technically, Q6 is unnecessary and the junction at its drain could be directly connected to a GPIO instead. But C1 interfered with SPI programming on the pins I used, so I added Q6 as a buffer.

It's a bit of a Rube Goldberg machine, of course. I made this because I wanted to do a little experiment with using a PCB-printed touch 'button' that would do double duty as an on-switch as well as a GPIO input, and I wanted to combine this in a trinket that runs on the remaining charge of an 'empty' CR2032 taken from our kitchen scales.

The mechanics are a 3D printed CR2032 holder that I drew up in Fusion360, using 2-pin header bent into an advantageous shape as the battery contacts. The single-layer PCB was laid out in DesignSpark PCB, printed as an inkjet stencil and then exposed, etched etc. I didn't bother reflow soldering this but instead used a manual soldering iron with a small tip and a hot air rework station for the ATtiny.

image
The SPI connector is a 6-pin SMT part that I didn't mount, but just soldered some temporary wires to for the USBasp programmer to connect to. I could have left it out and soldered directly to the uC pins and ended up with a smaller PCB. However, soldering directly to uC pins is annoying as it's too easy to bridge some pins and the larger pads of the SMT header are far easier to work with. Besides, I had plenty of space available, anyway...!?

image

Dimensions of the final item are about 30x25x10mm.
image

image