Saving power with AVR-Ada
As I got my INA219 sensor working, the next logical step was to actually find out how you can save some power with Arduinos.
Sparkfun provides a nice article about the subject and atmega328p datasheet is also an useful information source.
To save power, I basically did following things:
- Turned off all unnecessary peripherals.
- Turned off brown-out-detection (BOD, either via software or via FUSE bits).
- Slow down CPU frequency
- Put the processor in power save mode.
Turn off unnecessary peripherals
Turning off unnecessary things is pretty easy, you just flip some bits:
-- Disable ADC ADCSRA_Bits (ADEN_Bit) := False; ACSR_Bits (ACD_Bit) := True; PRR_Bits (PRADC_Bit) := True; -- Disable SPI PRR_Bits (PRSPI_Bit) := True; -- Disable TWI PRR_Bits (PRTWI_Bit) := True; -- Disable USART0 PRR_Bits (PRUSART0_Bit) := True; -- Disable timers 0 and 1 PRR_Bits (PRTIM0_Bit) := True; PRR_Bits (PRTIM1_Bit) := True;
Brown-out-detection
Disabling the brown out detection is somewhat trickier. You can do it in software, but you need to flip the bits within certain time and if I interpreted the generated code correctly, gcc generates too big/slow code from AVR-Ada code. Anyway, here is how it would be done:
procedure Disable_BOD is use AVR.MCU; begin MCUCR_Bits (BODS_Bit) := True; MCUCR_Bits (BODSE_Bit) := True; MCUCR_Bits (BODS_Bit) := True; MCUCR_Bits (BODSE_Bit) := False; end Disable_BOD; pragma Inline_Always(Disable_BOD);
Generated code for this is:
in r24,0x35 ori r24,lo8(64) out 0x35,r24 in r18,0x35 ori r18,lo8(32) out 0x35,r18 in r19,0x35 ori r19,lo8(64) out 0x35,r19 in r20,0x35 andi r20,lo8(-33) out 0x35,r20
And in avr-libc same is done as inline assembly (much shorter):
#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)
Because of this, I also created a small dosleep.c helper (in C) to do the bod disabling, but in the end I didn't find it to have a huge difference in power usage.
Slowing down the CPU frequency
CPU frequency control happens via CLKPR register. You are expected to write value 16#80# first into the register and after that the scaling value into the same register.
For this, I created two helper procedures (Slow_CPU and Fast_CPU):
procedure Slow_CPU is CPU_125khz : constant := 16#07#; Status : Interfaces.Unsigned_8; begin Status := Interrupts.Save_And_Disable; AVR.MCU.CLKPR := 16#80#; AVR.MCU.CLKPR := CPU_125khz; Interrupts.Restore (Status); end Slow_CPU; procedure Fast_CPU is CPU_16MHz : constant := 16#00#; Status : Interfaces.Unsigned_8; begin Status := Interrupts.Save_And_Disable; AVR.MCU.CLKPR := 16#80#; AVR.MCU.CLKPR := CPU_16MHz; Interrupts.Restore (Status); end Fast_CPU;
The constants names as CPU_125khz and CPU_16MHz make only sense if your AVR processor is clocked by 16MHz crystal.
Also, an important note: Once your processor is running at 125khz, avrdude might have problems programming it.
With Olimexino-328 board I didn't have any programming problems (programming via serial console and normal avrdude parameters worked fine), but Arduino UNO and Arduino Duemilanove boards required programming with USBTinyISP and with "-B 32" avrdude parameter.
Power saving mode
AVR processor have many power saving modes. With atmega328p, I used one called "POWER SAVE", which pretty much turns off everything except Timer 2 and watchdog.
If you don't need timer 2, you can also use "POWER DOWN" mode, but then you cannot use timer 2 to wake up the processor.
I wanted timer 2 to wake the processor every 2 seconds, so I did following initialization:
Elapsed_Seconds : aliased Interfaces.Unsigned_16; pragma Volatile (Elapsed_Seconds); procedure Tick; pragma Machine_Attribute (Entity => Tick, Attribute_Name => "signal"); pragma Export (C, Tick, Timer2.Signal_Compare); procedure Tick is use type Interfaces.Unsigned_16; Adjustment : constant := 2; begin Elapsed_Seconds := Elapsed_Seconds + Adjustment; end Tick; ... Timer2.Init_CTC (Timer2.Scale_By_1024, Overflow => 249);
At 125kHz with scale of 1024 and overflow at 250, timer 2 is triggered about every 2 seconds (1/125000*1024*250 = 2.048).
Putting everything together
To test the setup, I created a simple main procedure:
Sleeper.Init; loop Sleeper.Sleep (Seconds => 10); MCU.PortB_Bits (5) := Flag; Flag := not Flag; end loop;
The main procedure changes the status of the default LED (at Arduino digital pin 13) every 10 seconds. This way I know that the processor is still alive and I can also verify that the timing is mostly correct.
The Sleep procedure ended up like this:
procedure Sleep_Instr; pragma Inline_Always (Sleep_Instr); pragma Import (Intrinsic, Sleep_Instr, "__builtin_avr_sleep"); procedure Sleep (Seconds : Interfaces.Unsigned_16) is procedure Do_Sleep; pragma Import (C, Do_Sleep, "dosleep"); begin Slow_CPU; Elapsed_Seconds := 0; -- interrupt increases this loop exit when Elapsed_Seconds >= Seconds; if True then AVR.Sleep.Set_Mode (AVR.Sleep.Power_Save); MCU.MCUCR_Bits (AVR.MCU.SE_Bit) := True; Disable_BOD; Sleep_Instr; MCU.MCUCR_Bits (AVR.MCU.SE_Bit) := False; else Do_Sleep; -- C version end if; end loop; Fast_CPU; end Sleep;
Different Arduinos and Arduino clones compared
Since I have a lot of different Arduino devices around, I was interested in knowing which draws the least amount of current.
The tested devices were:
- Official Arduino Duemilanove - older version of Arduino
- Official Arduino UNOr3 - the latest version of 8-bit Arduino
- Olimexino-328 - Arduino clone from Olimex
- Arduino DIY PTH Kit from Sparkfun (older obsolete version)
- Diavolino from Evil Mad Scientist
For measurement I used INA219 sensor from Adafruit. I have no idea was the sensor calibrated correctly, so you should not trust the values to be absolute/correct. However, for relative measurements between above devices, the sensor should be fine. As the power source, I used 9v battery run through 5V regulator, but probably any other power source would do also.
The setup was same as before.
The program running on the sleeping Arduino can be found from here (sleeper-atmega328p.hex) in ihex format.
The measurement results were:
Device | Current |
---|---|
Duemilanove | 7.50mA |
UNOr3 | 18.10mA |
Olimexino-328 | 4.00mA |
Sparkfun PTH Kit | 7.60mA |
Diavolino | 0.50mA |
The biggest surprise was UNOr3 which was drawing 18.10mA current in the power saving mode. I made the measurements three times to make sure the result was correct. Unfortunately, I didn't have another UNOr3, so I could not check if the device was broken.
The low value of Diavolino is because that board has no other components than atmega328p, crystal, and a few resistors. Other boards have regulators, USB-UART, etc components.
Olimex advertises Olimexino-328 as low power Arduino clone and based on my results, it really is. Well, that wasn't unexpected since in general Olimex tends to make high quality products, like the Olinuxino ARM boards.
Summary
When you need to a lower power Arduino, Diavolino or Olimexino-328 looks like the best bet.
If my measurements are correct, you can run Diavolino on 400mAh battery for one month or even more, if you run the board at 3.3V instead of 5V.
Also, I didn't even try all the available power saving tricks, so it should be possible to go even lower. (See the Sparkfun tutorial for details.)
The complete code is available from my arduino-blog repository, under examples/sleeper directory.