Power saving techniques on a stock Mega 2560

I have a project that is required to run on batteries and also needs an Arduino Mega 2560 to drive HTTP communications over a 3G shield. I thought that I would capture my power measurements here in case they benefit someone else.

I didn’t want to build a bare bones board at this point in time, so I was trying to do the most possible with a stock standard unit.

Most of these techniques exist in various posts around the web, but I thought it would be handy to capture them all in one place and specific to the Mega.

Note that each test is cumulative i.e. it includes all code and hardware mods from previous tests.

I am measuring current with a multimeter connected in line between the power supply and the Mega.

  1. My first test was a baseline, with a stock standard Mega copy connected to 7.2V through the barrel jack basically doing nothing. This test consumed 82mA.
void setup() {
}

void loop() {
}
  1. The second test was to set pin 13 as an output pin and write it low. This turns off the on board LED located near pin 13. This reduced power consumption to 79mA.
void setup() {
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
}
  1. The third test was to set all analog pins as digital and write them low. This reduced power consumption to 74mA.
void setup() {
  // Previous steps code omitted for brevity

  pinMode(A0, OUTPUT);
  // pins 1 to 14 omitted for brevity
  pinMode(A15, OUTPUT);

  digitalWrite(A0, LOW);
  // pins 1 to 14 omitted for brevity
  digitalWrite(A15, LOW);
}
  1. The fourth test was to set all of the digital pins as output and write them low. This reduced power consumption to 65mA.
void setup() {
  // Previous steps code omitted for brevity

  for (int i = 0; i <= 53; i++) {
    pinMode(i, OUTPUT);
    digitalWrite(i, LOW);
  }
}
  1. The fifth test was to include the power library and disable all of the components that you wouldn’t commonly require in a production build. On my project, I need USART1 to drive the 3G board so I haven’t disabled that. Depending on your project’s requirements, you may be able to disable more or less. Note that for things like the Analog Digital Converter (ADC) you can enable it only when you need it. This reduced power consumption to 60mA.
#include <avr/power.h>

void setup() {
  // Previous steps code omitted for brevity

  power_adc_disable();
  power_spi_disable();
  power_usart0_disable();
  power_usart2_disable();
  power_timer1_disable();
  power_timer2_disable();
  power_timer3_disable();
  power_timer4_disable();
  power_timer5_disable();
  power_twi_disable();
}
  1. The sixth test was to put the Arduino to sleep using the power down sleep mode for the longest interval possible of 8 seconds. This reduced power consumption while sleeping to 32mA.
void loop() {
  delay(8000);
  goToSleep();
}

void goToSleep() {
  /*** Setup the Watch Dog Timer ***/
  /* Clear the reset flag. */
  MCUSR &= ~(1<<WDRF);

  /* set new watchdog timeout prescaler value */
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = 1<<WDP0 | 0<<WDP1 | 0<<WDP2 | 1<<WDP3;
  WDTCSR |= _BV(WDIE);

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_mode();
  sleep_disable();
}

ISR(WDT_vect) {
  // Dummy watchdog timer handler to prevent reset
}
  1. The seventh test was to bypass the on board regulator and power the Arduino from an external regulated 5V supply. I did this by feeding 5V directly into the 5V pin of the Arduino and connecting the negative from the power supply to the Arduino ground pin. This reduced power consumption while awake to 58mA and while asleep to 31mA. This doesn’t seem like much of a saving in terms of milliamps, but if you consider the voltage and amperage and look at consumption in watts (V * A = W), then in sleep mode it is consuming 0.16W at 5V compared with 0.23W at 7V.

  2. The eigth test was to disconnect the on board regulator by cutting the ground pin with a pair of side cutters. The regulator is next to the barrel jack and the ground pin is the one closest to the barrel jack. You need to use a multimeter to make sure the pin is disconnected from the board. This reduced power consumption to 55mA while awake and 28mA while asleep.

  3. The ninth test was to jumper the reset and ground pins of the USB ICSP header together to put the 16U2 USB chip into reset mode which consumes less power than programming mode as suggested here. The reset and ground pins are the two pins on the USB ICSP header located closest to the USB connector. You can see a picture for an Uno here but they are in the same spot on a Mega. This reduced power consumption to 40mA while awake and 11.3mA while asleep. Note that if you want to use the USB cable to communicate with your Arduino, you will need to remove these jumpers.

  4. The tenth test was to remove the power LED by cutting it with a pair of side cutters. This reduced power consumption while awake to 37mA and 8.4mA while asleep.

Summary

Change Voltage Awake Asleep Awake Asleep
1. Baseline 7.2V 82mA - 0.59W -
2. Disable pin 13 LED 7.2V 79mA - 0.57W -
3. Analog pins as digital and LOW 7.2V 74mA - 0.53W -
4. Digital pins as OUTPUT and LOW 7.2V 65mA - 0.47W -
5. Disable on board components 7.2V 60mA - 0.43W -
6. Sleep mode 7.2V 63mA 32mA 0.45W 0.23W
7. Bypass on board regulator 5V 58mA 31mA 0.29W 0.16W
8. Disconnect on board regulator 5V 55mA 28mA 0.28W 0.14W
9. 16U2 USB reset mode 5V 40mA 11.3mA 0.20W 0.06W
10. Remove power LED 5V 37mA 8.4mA 0.19W 0.04W

Conclusion

With some basic programming an some fairly rudimentary hardware modifications, you can reduce the power consumption of the Mega quite significantly. From the starting point of 82mA (0.59W) you can reduce power consumption while awake to 37mA (0.19W) and while asleep to 8.4mA (0.04W).

For my project, I think I will use a step down regulator something like this with a 7.2V battery pack, which should be more efficient than the on board regulator.

For a more in depth look at power consumption I would recommend Nick Gammon’s excellent post here. While for an Uno, many of the techniques can still be applied to other boards.

For a summary of the macros in the power library look here.

If you have any other suggestions, please feel free to post them. Please bear in mind that my original goal was not to perform radical surgery on the stock standard board :slight_smile:

Thanks. I have bookmarked this, although I have not yet had time to study it.

...R

Update

  1. The eleventh test was to halve the clock speed of the Arduino from 16MHz to 8MHz using the clock prescaler. This reduced power consumption while awake to 28mA and still 8.4mA while asleep. Note that halving the clock speed also halves any time related functions like delay() and millis() and I believe it also halves the serial baud rate. It also halves the watchdog timer, so now our sketch sleeps for 16 seconds and then stays awake for 16 seconds, so you need to make corresponding allowances in your code for this.
void setup() {
  clock_prescale_set(clock_div_2);
}
  1. The twelfth test was to reduce the input voltage to 3.3V since we are now running at a lower clock speed, this voltage is within the tolerance of the processor running at 8MHz. This reduces the power consumption while awake to 13.8mA and 3.9mA while asleep. Note that this will require all your connected peripherals to be 3.3V tolerant, as there is now no longer 5V available on the board.

Summary

Change Voltage Awake Asleep Awake Asleep
1. Baseline 7.2V 82mA - 0.59W -
2. Disable pin 13 LED 7.2V 79mA - 0.57W -
3. Analog pins as digital and LOW 7.2V 74mA - 0.53W -
4. Digital pins as OUTPUT and LOW 7.2V 65mA - 0.47W -
5. Disable on board components 7.2V 60mA - 0.43W -
6. Sleep mode 7.2V 63mA 32mA 0.45W 0.23W
7. Bypass on board regulator 5V 58mA 31mA 0.29W 0.16W
8. Disconnect on board regulator 5V 55mA 28mA 0.28W 0.14W
9. 16U2 USB reset mode 5V 40mA 11.3mA 0.20W 0.06W
10. Remove power LED 5V 37mA 8.4mA 0.19W 0.04W
11. Reduce clock speed to 8MHz 5V 28mA 8.4mA 0.14W 0.04W
11. Change input to 3.3V 3.3V 13.8mA 3.9mA 0.046W 0.013W

davidrh:
Note that halving the clock speed also halves any time related functions like delay() and millis() and I believe it also halves the serial baud rate.

If the Arduino IDE is told that the clock rate is 8MHz it compensates so the timing is unaffected.

I routinely use breadboard Atmega 328s with their internal 8MHz clock.

...R

Is that done by editing the boards.txt file and creating a new copy of the existing board and changing the mega.build.f_cpu=16000000L to 8000000L if you have a CPU prescaler of 2?

Halving the clock speed means the processor takes twice as long to do whatever it has to do. There is no net saving.

It depends on what your code is doing and how CPU intensive it is. If you have some waiting around for things to happen then there will be savings. It also allows you to run at 3.3V as 16MHz is outside of the specifications for 3.3V.

If you have some waiting around for things to happen

delay() requires as much power as any other function or operation.

What do you think “cpu intensive” means on an Arduino? There is sleep, and there is “instruction processing mode”.

If you are waiting for something to happen via. delay() or perhaps polling the serial buffer to see if something is there, then isn't it going to use less power if you use less CPU cycles? That is what I was trying to test between scenario #10 and #11. The delay(8000) uses 37mA at 16MHz and 28mA at 8MHz with no other code change. As expected, sleep mode uses the same amount of power regardless of the CPU clock speed as the CPU is inactive.

In my application, I am communicating with the 3G board via serial to interact with a web service. I don't want to sleep or delay while I wait for a response, as the serial buffer may fill up unless I make the delay really short or the serial buffer really big. I am hoping (haven't tested this yet) that 8MHz will still be adequate to keep up with the serial communications (i.e. it will take the same amount of time at the same baud rate but use half the number of CPU cycles), but use less power while I am waiting for something to appear in the serial buffer.

I also assumed (perhaps naively) that as the clock_prescale_set function was in the power library it would save power in some situations.

then isn't it going to use less power if you use less CPU cycles?

You are always using CPU cycles, unless the processor is sleeping. Each cycle consumes the same amount of power, regardless of whether the instruction is NOP or ADC.

You could use the serial interrupt to wake the processor and deal with the incoming byte.

davidrh:
Is that done by editing the boards.txt file and creating a new copy of the existing board and changing the mega.build.f_cpu=16000000L to 8000000L if you have a CPU prescaler of 2?

Something like that - but the devil is in the detail and I have not done it for the Mega. Neither have I done it when using the prescaler.

...R

jremington:
Each cycle consumes the same amount of power, regardless of whether the instruction is NOP or ADC.

Agree. The point being if you have less cycles per second you will use less power over the same period of time, as evidenced by test 11 and other observations. It's not linear but it is less. If you are doing ADC, I agree it is more efficient to do it faster and move on to the next thing. But if you are just waiting for something to happen (like a button press) and you have nothing else to do until that happens, then a lower clock speed could be an option for using less power. You could also use sleep modes and interrupts to save even more power. Horses for courses.

use 2Mhz as clock and set all the pins to pull down internally and set as input before going into sleep mode keep the voltage between 2-3 V. This way you can conserve more power.

I know this is an older post, but I found this very useful if you only need to use the Arduino once a minute, once and hour or once a day. Then there is no power for the Arduino except when needed. Just the power for a rtc which should last a couple of years in a coin battery

I tried to post this on Gammon's site, where I first read about power savings, but alas, it's impossible to contact him, so here it is.

To disable the USB elegantly, without any hardware scratching/soldering/bridging, we are using

with an unmolested regular Mega since August 2018, and it works really well!

Software is so much cleaner and easier.

This is for a very remote top of a hill weather station for hang- and paragliders, and we updated our program there per USB only once.
After that one time (getting there, opening everything, holding laptop, hooking USB cable, removing reset bridge, etc pp) we enabled remote updates (Ariadne).

Like with most such projects, USB is not/barely used after deployment anymore, a total waste of energy.

Nico was kind enough and added the deep sleep functionality to his very amazing idea/code.

So when no USB is detected within a short time, the USB chip goes to deep sleep/hybernation.
The Rx/Tx LED's are then turned off also.
If you use USB, the Mega functions as normal.

Here is the code to reduce 16U2s power draw to 20uA.
This consumes even less power than the “USB reset mode”, and doesn’t need any soldering.

// By Nico Hood
// In conjunction with 
// https://github.com/NicoHood/HoodLoader2
// upload code below to the 16U2 of the Mega/UNO etc 
// see https://github.com/NicoHood/HoodLoader2/wiki/Uploading-programs
//
// 10 seconds after USB cable is pulled, 16U2 
// enters sleep mode (consuming only 20uA).
// Must power cycle 16U2 to regain full USB functionality


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

// Refine the reset pin to reset the destination MCU.
// This definition by default is equal to the HoodLoader2 used reset pin.
// But you still can use another pin for your layout.
const uint8_t resetPin = IO_MCU_RESET_PIN;

#define SLEEP_TIMEOUT 10000

void setup() {
  // Set main MCU by default active
  digitalWrite(resetPin, HIGH);
  pinMode(resetPin, OUTPUT);

  // Start USB Serial
  Serial.begin(0);
}

void loop() {
  // If the USB cable is connected, do not got to sleep
  static uint32_t lasttime = 0;
  uint32_t currenttime = millis();
  if (USBDevice.configured()) {
    lasttime = currenttime;
  }

  // Check for sleep timeout if no usb cable is connected  
  if (currenttime - lasttime > SLEEP_TIMEOUT)
  {
    // No cable plugged in within 10 seconds. Go to sleep now.
       // 16U2 pins, PB0-PB7, terminate pins to prevent floating inputs
    pinMode       (0,  OUTPUT);     // PB0 NC
    digitalWrite  (0,  LOW)   ;
    pinMode       (1,  INPUT) ;     // PB1 SCK2 make input with pullup
    digitalWrite  (1,  HIGH)  ;     // internal pullup
    pinMode       (2,  INPUT) ;     // PB2 MOSI2 make input with pullup
    digitalWrite  (2,  HIGH)  ;     // internal pullup
    pinMode       (3,  INPUT) ;     // PB3 MISO2 make input with pullup
    digitalWrite  (3,  HIGH)  ;     // internal pullup
    pinMode       (4,  OUTPUT);     // PB4 PCINT4, at JP5 Header
    digitalWrite  (4,  LOW)   ;
    pinMode       (5,  OUTPUT);     // PB5 PCINT5, at JP5 Header, 
    digitalWrite  (5,  LOW)   ;     //   next to 5V of ISCP
    pinMode       (6,  OUTPUT);     // PB6 PCINT6, at JP5 Header
    digitalWrite  (6,  LOW)   ;
    pinMode       (7,  OUTPUT);     // PB7 PCINT7/TIMER1C/PWM, at JP5 Header,
    digitalWrite  (7,  LOW);        //   next to MOSI of ISCP


    // 16U2 pins, PC7-PC2, terminate pins to prevent floating inputs
    pinMode       (8,  OUTPUT);     // PC7 NC 
    digitalWrite  (8,  LOW);
    pinMode       (9,  OUTPUT);     // PC6 NC 
    digitalWrite  (9,  LOW);
    pinMode       (10, OUTPUT);     // PC5 NC 
    digitalWrite  (10, LOW);
    pinMode       (11, OUTPUT);     // PC4 NC 
    digitalWrite  (11, LOW);
    // PC3 doesn't exist 
    pinMode       (12, OUTPUT);     // PC2 NC 
    digitalWrite  (12, LOW);
    // PC1 SPI Reset, pulled up by 10K 
    
    // 16U2 pins, PD0-PD7, terminate pins to prevent floating inputs
    pinMode       (13, OUTPUT);     // PD0 NC
    digitalWrite  (13, LOW);
    pinMode       (14, OUTPUT);     // PD1 NC
    digitalWrite  (14, LOW);
    pinMode       (15, INPUT);      // PD2 M8TXD connected to Mega via 1K
    pinMode       (16, INPUT);      // PD3 M8RXD connected to Mega via 1K
    pinMode       (17, OUTPUT);     // PD4 RX-LED,
    digitalWrite  (17, HIGH);       //   make high so LED is off
    pinMode       (18, OUTPUT);     // PD5 TX-LED, 
    digitalWrite  (18, HIGH);       //   make high so LED is off
    pinMode       (19, OUTPUT);     // PD6 NC
    digitalWrite  (19, LOW);
    pinMode       (20, INPUT);      // PD7 pulldown from reset

   
   // USBCON |= _BV(FRZCLK);          // Disable USB clock
   // USBCON &= ~_BV(USBE);           // Disable USB
    USBCON =  B00100000;            // Bit 7 – USBE: ZERO disables USB controller & buffers
                                    // Bit 5 – FRZCLK: ONE freezes clock, reducing power consumption
    PLLCSR &= ~_BV(PLLE);           // Disable USB PLL, Bit 1: ZERO 

    cli();
    ACSR = B10001000;               // 7: Analog Comparator power switched off , 4: ACI is cleared by logic one
                                    // 3: ACIE zero, the interrupt is disabled
                                    // 2: ACIC zero, no connection between the Analog Comparator and input capture
 
    PRR0 = B00101100;               // Power Reduction Register 0
    PRR1 = B10000001;               // Power Reduction Register 1


    cli();
    DIDR1 = 0xFF;                   // DIDR1 – Digital Input Disable Register 
                                    // Bit 1, 0 – AIN1D, AIN0D: AIN1, AIN0 Digital Input Disable
                                    // logic one disables input buffer on the AINx pin
                                    
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable();
    sleep_cpu();                    // CPU to sleep
    sleep_mode();                   // finally, go to sleep
    while (true);
  }

  // Do normal USB to Serial translation as the 16u2 is used to do
  USBtoSerial();
}

void USBtoSerial() {
  // Check if the USB virtual serial wants a new baud rate
  static uint32_t baud = 0;
  if (Serial.baud() != baud) {
    baud = Serial.baud();
    Serial1.end();
    if (baud)
      Serial1.begin(baud);
  }

  // reset the main mcu if DTR goes HIGH
  if (Serial.dtr())
    digitalWrite(resetPin, LOW);
  else
    digitalWrite(resetPin, HIGH);

  // Serial -> USB

  // Check if any data has arrived on the USART serial port
  // and USB is ready for the next bytes
  int readAvailable = Serial1.available();
  if (readAvailable > 0) {

    int writeAvailable = Serial.availableForWrite();
    if (writeAvailable > 0) {

      // Write maximum one EP_SIZE to not block with Zero Length packets
      uint8_t buff[USB_EP_SIZE - 1];
      if (readAvailable > sizeof(buff))
        readAvailable = sizeof(buff);

      // Dont write more than USB can take to not block
      if (readAvailable > writeAvailable)
        readAvailable = writeAvailable;

      // Read data from the hardware serial port

      int n = Serial1.readBytes((char *)buff, readAvailable);

      // Write it to the USB port
      Serial.write(buff, n);
    }
  }


  // USB -> Serial

  // Check if any data has arrived on the USB virtual serial port
  // and USART is ready for the next bytes
  readAvailable = Serial.available();
  if (readAvailable > 0) {

    int writeAvailable = Serial1.availableForWrite();
    if (writeAvailable > 0) {

      // Write maximum one EP_SIZE to not block with Zero Length packets
      if (readAvailable > (USB_EP_SIZE - 1))
        readAvailable = (USB_EP_SIZE - 1);

      // Dont write more than USART buffer is free to not block
      if (readAvailable > writeAvailable)
        readAvailable = writeAvailable;

      // Read data from the USB serial port
      uint8_t buff[SERIAL_TX_BUFFER_SIZE];
      int n = Serial.readBytes((char *)buff, readAvailable);

      // Write it to the USART port
      Serial1.write(buff, n);
    }
  }
}

PowerHood-20200304.zip (2.43 KB)