Go Down

Topic: The case of the missing microamps... (Read 3123 times) previous topic - next topic

Drmn4ea

I just put together a first prototype of a low-power Arduino derivative using the ATmega644PA. As the first test, I'm trying to put this chip into its deepest sleep to measure/verify baseline power consumption (as low as 0.1uA according to the datasheet). But for some reason, no matter what I do there is an unexpectedly large static current draw even in full powerdown: about 100uA at 3.3V. The chip is driven by a 4MHz external crystal, which goes silent after executing sleep_cpu().

I've more or less confirmed this is being drawn by an on-chip resource (not floating pins or pullup/down resistors) - swapping the chip into an empty socket with only Vdd/GND, a 100nF bypass cap, crystal and RESET pullup has no effect. But for the life of me I can't find what it is.

Current fuse settings:
mosquino.bootloader.low_fuses=0xf7
(External crystal, not divided)

mosquino.bootloader.high_fuses=0xDC
(On-chip debugger off; JTAG off; SPI programming enabled; WDT not always on)

mosquino.bootloader.extended_fuses=0xFE
(BOD @ 1.8v when enabled)


The code so far: (just keeps growing longer as I find new things to try / fumble with)



Code: [Select]

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

/*
 Sleeptest
 Put the AVR all the way to sleep so we can measure board current consumption.

*/

int ledPin =  27;    // LED connected to digital pin

void setup()   {                
 // initialize the digital pins as outputs... no, now try inputs just to be anal...
 int i;
 for(i=0; i<32;i++)
 {
   //pinMode(i, OUTPUT);  //ensure no floating pins
   pinMode(i, INPUT);  //ensure no sneaky leakage paths I'm not seeing
   //digitalWrite(i, LOW);
 }
 
 // except for INT0, INT1 which have a pullup resistor
//  //pinMode(14, INPUT);
//  //pinMode(15, INPUT);
//  digitalWrite(14, HIGH);
//  digitalWrite(15, HIGH);
//  
//    // 25: SCL
//    //26: SDA
//
//  pinMode(27, OUTPUT);
//  digitalWrite(27, LOW);
//
//  pinMode(28, OUTPUT);
//  digitalWrite(28, LOW);
//
//  pinMode(29, OUTPUT);
//  digitalWrite(29, LOW);
//  pinMode(ledPin, OUTPUT);    
//  digitalWrite(ledPin, LOW);
//
//  pinMode(30, OUTPUT);    
//  digitalWrite(30, LOW);



//   Put various on-chip modules to sleep

 // *CRASH!* CPU hangs if this is executed...
 // Arduino does not like its timers disabled...
 //power_all_disable();

 // these "should" not matter much anyway as all clocks should stop in deep sleep anyway...
 power_adc_disable();
 power_spi_disable();
 power_twi_disable();
 power_usart0_disable();
 ////power_usart1_disable(); // not defined, wtf?
 

 /*     * In the avr/sleep.h file, the call names of these sleep modes are to be found:
      *
      * The 5 different modes are:
      *     SLEEP_MODE_IDLE         -the least power savings
      *     SLEEP_MODE_ADC
      *     SLEEP_MODE_PWR_SAVE
      *     SLEEP_MODE_STANDBY
      *     SLEEP_MODE_PWR_DOWN     -the most power savings
 */
 
 // try disabling the BOD
 // Requires an unlock sequence: Set BODS (bit 6) and BODSE (bit 5), then clear BODS within 4 cycles.
 // "Then, to set the BODS bit, BODS must be set to one and BODSE must be set to zero within four clock cycles."
 // (does this mean in the same instruction or not?)
 
 MCUCR |= B01100000;  // set BODS, BODSE
 //MCUCR &= B10111111; // clear BODS
 MCUCR &= B10011111;  // clear BODSE (BODS again too for good measure)
 
 MCUCR |= B00010000; // disable pullups - no change

 // kill WDT if running
 MCUSR = 0;
 WDTCSR |= _BV(WDCE) | _BV(WDE);
 WDTCSR = 0;



 // turn OFF analog parts
 ACSR = ACSR & 0b11110111 ; // clearing ACIE prevent analog interupts happening during next command
 ACSR = ACSR | 0b10000000 ; // set ACD bit powers off analog comparator
 ADCSRA = ADCSRA & 0b01111111 ;  // clearing ADEN turns off analog digital converter


 // Found in: http://www.mail-archive.com/tinyos-help@millennium.berkeley.edu/msg17215.html
//  cbi(ADMUX,REFS0);
//  cbi(ADMUX,(REFS1));
//  cbi(ADCSRA,ADEN);
//  cbi(ADCSR,ACIE);
//  sbi(ACSR,ACD);

 ADMUX &= B00111111;
 

 // blink to show we're still alive and ready to sleep
 digitalWrite(ledPin, HIGH);
 delay(100);
 digitalWrite(ledPin, LOW);
 delay(100);
 
 digitalWrite(ledPin, HIGH);
 delay(100);
 digitalWrite(ledPin, LOW);
 delay(100);  

 digitalWrite(ledPin, HIGH);
 delay(100);
 digitalWrite(ledPin, LOW);
 delay(100);


 digitalWrite(ledPin, HIGH);  
 PRR = B11111111 ; // Bit 6, 5 are timers...
 digitalWrite(ledPin, LOW);  

// Digital Input Disable
 DIDR1 = B00000011;
 DIDR0 = B11111111;
 

}


void loop()                    
{
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);
 sleep_enable();
 sleep_cpu();

 while(1)
 {
     digitalWrite(ledPin, HIGH);
     digitalWrite(ledPin, LOW);
 }
}


Has anyone seen this before, or know what I'm missing?

Coding Badly


http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1243213127/all

...try setting the pins to OUTPUT or INPUT w/ PULLUP enabled.

Drmn4ea

I tried that already (that code was not always commented) - all pins were either OUTPUT, or INPUT with an on-board hardware pullup. I even tried the old (if slightly dangerous) trick of touching MCU pins while dragging a toe on the carpet to see where the meter dances, but this showed nothing other than the expected few uA change when VCC and GND (or output-low pin) are touched at the same time.

RuggedCircuits

You've enabled brownout at 1.8V. That's going to contribute about 20uA.

--
Need a custom shield? Let us design and build one for you.

Drmn4ea

True, the Brown Out Detect boot fuses are set, but if I read the datasheet correctly, the sequence shown in the code (under "// try disabling the BOD") should disable it during sleep modes. I'll try killing it in the fuse settings to be certain.

Even if the BOD is not disabling where it should, this still leaves ~80uA unaccounted for. Is there anything else I'm missing?

RuggedCircuits

Did you disable the analog comparator?

Are you sure you went back to a version of the code in which you drove the I/O pins as outputs or enabled pullups?

--
The Gadget Shield: accelerometer, RGB LED, IR transmit/receive, light sensor, potentiometers, pushbuttons

Drmn4ea

Yes, all of that.

Here is my current code, which pulls a very consistent 100uA (+/-1uA) when the CPU is asleep at 3.3V. The measurement is taken after moving the chip to the 'bare' board mentioned earlier (only VCC/GND, 10k pullup on RESET\, and 4MHz crystal). The behavior on a 2nd chip with the same code is identical.

The BOD is now disabled in the boot fuses and reflashed.

Code: [Select]

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

/*
 Sleeptest
 Put the AVR all the way to sleep so we can measure board current consumption.

*/

void setup()   {                

 int i;


 for(i=0; i<32;i++)
 {
   pinMode(i, OUTPUT);  //ensure no floating pins
   digitalWrite(i, LOW);
 }

//   Put various on-chip modules to sleep


 // these "should" not matter much anyway as all clocks should stop in deep sleep anyway...
 power_adc_disable();
 power_spi_disable();
 power_twi_disable();
 power_usart0_disable();
 ////power_usart1_disable(); // not defined, wtf?
 
 
 MCUCR |= B00010000; // disable pullups - no change

 // kill WDT if running
 MCUSR = 0;
 WDTCSR |= _BV(WDCE) | _BV(WDE);
 WDTCSR = 0;



 // Shutdown ADC related stuff
 ACSR = ACSR & 0b11110111 ; // clearing ACIE prevent analog interupts happening during next command
 ACSR = B10000000; // set ACD bit powers off analog comparator; disable bandgap ref too
 ADCSRA = B00000000;  // clearing ADEN turns off analog digital converter
 ADMUX &= B00111111;  // Comparator uses AREF/GND and not internally generated references
// Digital Input Disable
 DIDR1 = B00000011;
 DIDR0 = B11111111;
 
 // Found in: http://www.mail-archive.com/tinyos-help@millennium.berkeley.edu/msg17215.html
//  cbi(ADMUX,REFS0);
//  cbi(ADMUX,(REFS1));
//  cbi(ADCSRA,ADEN);
//  cbi(ADCSR,ACIE);
//  sbi(ACSR,ACD);

 //noInterrupts();

 // Show CPU is still alive
 digitalWrite(ledPin, HIGH);
 delay(100);
 digitalWrite(ledPin, LOW);
 delay(100);
 
 digitalWrite(ledPin, HIGH);
 delay(100);
 digitalWrite(ledPin, LOW);
 delay(100);  

 digitalWrite(ledPin, HIGH);
 delay(100);
 digitalWrite(ledPin, LOW);
 delay(100);


 digitalWrite(ledPin, HIGH);  
 PRR = B11111111 ; // Bit 6, 5 are timers...
 PRR0 = B11111111 ; // probably an alias for PRR
 digitalWrite(ledPin, LOW);  


}


void loop()                    
{
 // Verified: SLEEP_MODE_PWR_DOWN in sleep.h correctly sets SM[2..0].

 set_sleep_mode(SLEEP_MODE_PWR_DOWN);
 sleep_enable();
 sleep_cpu();


 while(1)
 {
     digitalWrite(ledPin, HIGH);
     digitalWrite(ledPin, LOW);
 }
}


An (most likely unrelated) anomaly I have noticed is that using Arduino's noInterrupts() (I assume this is mostly a wrapper for the CLI instruction) causes the chip to go catatonic. In that case it never gets to the sleep part at all, and so remains at 2.x mA.

But about that 100uA during sleep... anyone, anyone? I'm going to have to start offering prizes soon...

RuggedCircuits

All those power_XXX_disable() functions mean that when you actually write to the peripheral control registers it's possible your writes are being ignored. If they really don't matter, try commenting them out.

Turning off interrupts means your delay() functions are going to hang.

--
The Quick Shield: breakout all 28 pins to quick-connect terminals

Drmn4ea

Thanks for the clarification on delay() being the culprit for interrupts; I figured it was just a counter loop... in case of a timer interrupt, the trouble I was having with noInterrupts() and/or disabling timers make perfect sense now.

I know the power_xxx_disable() functions are indeed doing nothing now that I write to the PRR directly - I figured they couldn't hurt anything :-)

But nevermind that... I have some new information!

It is definitely something in the Arduino environment - not the fuse configuration or bootloader - that is twiddling the "mystery resource(s)" that draws (or totals) 100uA. I compiled and uploaded the essentially identical code directly using avrdude to the bootloader:

Now it draws only 17uA!

Now to track down those 17uA... after more careful reading of the datasheet/forums, I was not disabling the BOD correctly: even after setting BOD disable during sleep using a magic incantation, the sleep() has to be executed within three cycles, or BOD re-enables!

After fixing this, the sleep current is now too low to measure on my meter (<1uA).

Going thru avr-gcc directly like this is not really a solution (the point of this is to be an Arduino, afterall), but it should at least rule out the fuses/bootloader and help narrow down what's going on. If anyone reading this is familiar with the bowels of the Arduino source, any suggestions to narrow the search further would be most appreciated!

Code: [Select]

#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
//#include <avr/boot.h>
#include <avr/power.h>
#include <avr/sleep.h>

#define LED_DDR  DDRC
#define LED_PORT PORTC
#define LED_PIN  PINC
#define LED      PINC2

void flash_led(uint8_t);

// fished from a newer avr/sleep.h on the internet
#define sleep_bod_disable() \
do { \
  uint8_t tempreg; \
  __asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \
                       "ori %[tempreg], %[bods_bodse]" "\n\t" \
                       "out %[mcucr], %[tempreg]" "\n\t" \
                       "andi %[tempreg], %[not_bodse]" "\n\t" \
                       "out %[mcucr], %[tempreg]" \
                       : [tempreg] "=&d" (tempreg) \
                       : [mcucr] "I" _SFR_IO_ADDR(MCUCR), \
                         [bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \
                         [not_bodse] "i" (~_BV(BODSE))); \
} while (0)

void main(void)
{
 asm volatile("nop\n\t");


   /* set LED pin as output */
 LED_DDR |= _BV(LED);


 flash_led(NUM_LED_FLASHES);


 DDRA = 0;
 DDRB = 0;
 DDRC = 0;
 DDRD = 0;

 PORTA=0;
 PORTB=0;
 PORTC=0;
 PORTD=0;

 while (ASSR & ((1<<TCN2UB)|(1<<OCR2AUB)|(1<<OCR2BUB)|(1<<TCR2AUB)|(1<<TCR2BUB))) {  }


 
 
 MCUCR |= 0b00010000; // disable pullups - no change





 // Shutdown ADC related stuff
 ACSR = ACSR & 0b11110111 ; // clearing ACIE prevent analog interupts happening during next command
 ACSR = 0b10000000; // set ACD bit powers off analog comparator; disable bandgap ref too
 ADCSRA = 0b00000000;  // clearing ADEN turns off analog digital converter
 ADMUX &= 0b00111111;  // Comparator uses AREF/GND and not internally generated references
 // Digital Input Disable
 DIDR1 = 0b00000011;
 DIDR0 = 0b11111111;


   // kill WDT if running
 MCUSR = 0;
 WDTCSR |= _BV(WDCE) | _BV(WDE);
 WDTCSR = 0;

 TCCR0B = 0b00000000; // ensure timer 0 not clocked
 TCCR1B = 0b00000000; // ensure timer 1 not clocked
 TCCR1B = 0b00000000; // ensure timer 2 not clocked
 PRR = 0b11111111 ; // Bit 6, 5 are timers...
 PRR0 = 0b11111111 ; // probably an alias for PRR

 set_sleep_mode(SLEEP_MODE_PWR_DOWN);
 sleep_enable();
 sleep_bod_disable();
 sleep_cpu();


   /* forever loop */
   for (;;)
     {
           flash_led(2);
     }
   /* end of forever loop */
}

void flash_led(uint8_t count)
{
   /* flash onboard LED three times to signal entering of bootloader */
     /* l needs to be volatile or the delay loops below might get
     optimized away if compiling with optimizations (DAM). */
   volatile uint32_t l;

   if (count == 0) {
     count = 3;
   }

     int8_t i;
   for (i = 0; i < count; ++i) {
           LED_PORT |= _BV(LED);
           for(l = 0; l < (F_CPU / 1000); ++l);
           LED_PORT &= ~_BV(LED);
           for(l = 0; l < (F_CPU / 1000); ++l);
     }

}


Coding Badly

#9
Oct 01, 2010, 09:19 am Last Edit: Oct 01, 2010, 09:20 am by bcook Reason: 1
Quote
I compiled and uploaded the essentially identical code directly using avrdude to the bootloader:

Using the Arduino init code?

Which core are you using?

Drmn4ea

Huh? Using the code that is in the post I just posted. :-) (for the 1uA case). This is compiled with avr-gcc directly, no Arduino stuff involved.

For the 100uA case, the core was arduino-0018, although I just updated it to 0020 with no change. The bootloader is a slightly modified Sanguino '644P bootloader - this same bootloader was used to upload both tests. The modifications are trivial (basically, just update the pin definitions to match my board). Both of these are available at http://tim.cexx.org/projects/mosquino/mosquino_hw_dropin.zip .

westfw

Quote
I compiled and uploaded the essentially identical code

That doesn't look "essentially identical" to me.  For starters, I don't see anything in your arduino-style sketch to turn off the timers, and your straight C isn't using the power_xxx functions (even though they're not part of the arduino environment...)

Drmn4ea

#12
Oct 02, 2010, 08:11 am Last Edit: Oct 02, 2010, 08:24 am by Drmn4ea Reason: 1
They were "essentially" identical at the time I posted the code above - the Arduino sketch posted further up is a couple days old by comparison.

But indirectly...you are right! And now the mystery is solved!!

Quote

and your straight C isn't using the power_xxx functions (even though they're not part of the arduino environment...)


The power_xxx functions are wrappers for toggling bits in the Power Reduction Register (PRR). Recalling the mention earlier that these calls were redundant (since later on I was writing to the PRR directly), I omitted them when copy-pasting the code over to the straight-C test. To prove to myself (and ultimately this thread) the code was "essentially identical", tonight I copied the C version back into the Arduino IDE (removing the last remaining Arduino-isms such as "digitalWrite(...)", but otherwise I saw no significant differences).

But lo and behold, this "identical" version now pulls <1uA!

After some trial and error (...blood...tears...) the culprit was the power_adc_disable() near the beginning of the file. Setting its PRR bit saves power by stopping the clock to the ADC. In my code this is immediately followed by some code to explicitly disable the ADC and any voltage references it may be using. In an ironic twist unbeknownst to me (and the datasheet authors, apparently  :-X), the ADC must be receiving a clock to be disabled. The solution is to delay the PRR bit change until after ADEN is cleared.

@westfw - good catch; I would not have caught this by myself in a million years!

westfw

hah!  thanks for the update.
It wasn't so much a "catch" as letting the ball bounce off my head till you showed up with the net...

Go Up