5

I fail to use PWM in power saving mode. The output behaves erratically. This should be possible, I guess. The following is a minimal working example:

/* MWE: Cannot use PWM in any power saving mode.

   A minimal working example (MWE) demonstrating my trouble in using
   PWM while saving power.

   Intention: Control one load to be on or off for longer periods of
   time (hours, that is).  The brightness of the load is set by PWM.

   Wiring:

                                              +5V
                                               |
           +------------------+                |
     +5V---|Vin               |                |
           |     Arduino      |                C
           |     Pro Mini    9|-----/\/\/----B|<  PN2222
           |     compatible   |      1k        E
           |     (ATmega328)  |                |
     GND---|GND               |                | +
           +------------------+               LED
                                               | -
                                               |
                                               >
                                               < 1k
                                               >
                                               |
                                               |
                                              GND

   Implementation:

     * Set watchdog to cause an interrupt in regular intervals (in
       this MWE: 2s).  Count down `cycles` (MWE: 2 or 3) on each
       watchdog interrupt.

     * If `cycles` is 0, toggle the output, set new `cycles` value.
       The real program uses 7200 8s cycles for 16h before the next
       toggle.

     * Go to SLEEP_MODE_PWR_DOWN.

   Expected: attached load is on for 4s, dimmed at 32/256.  Then off
             for 6s.  Then repeat.

   Observed: In most cases, there's no output in sleep mode, the LED
             only blinks before sleeping.  But sometimes it is on, so
             PWM seems to be possible while sleeping!

   The real program (from which this MWE is derived) uses adjustable
   brightness, wakes up from sleep by external interrupts, stores
   brightnes to EEPROM, ...
*/

/* Setting the following to 0 uses `digitalWrite` instead of
   `analogWrite` to demonstrate that the watchdog and interrupt logic
   do actually work. */
#define USE_PWM 1

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

#define outputPin 9    // where the PWM load is connected
#define onCycles 2     // num of watchdog cycles to stay on
#define offCycles 3    // num of watchdog cycles to stay off

int brightness = 32;  // PWM duty cycle in 1/256, if the load is on
int toggle = 1; // current on/off state of PWM load



/* Number watchdog cycles until next toggle.  This will be modified in
   the watchdog interrupt handler. */
volatile unsigned long wdt_cycles = onCycles;

/* Watchdog interrupt handler called when watchdog expires, count down
   remaining cycles.  Does not underflow. */
ISR(WDT_vect) {
    if (wdt_cycles > 0)
        wdt_cycles--;
}



void setup(void) {

    /* Setup watchdog.  See [1] p54, [2] */
    cli(); // BEGIN disable interrupts

    MCUSR &= ~(1<<WDRF); // no system reset on watchdog timeout

    WDTCSR |= 1<<WDCE | 1<<WDE; // allow watchdog configuration

    WDTCSR
        = 1<<WDIE // enable interrupts
        | 1<<WDP2 | 1<<WDP1 | 1<<WDP0 // 2.0 seconds
        ;

    wdt_reset(); // reset timer

    sei(); // END disable interrupts


    /* Setup output pins */

    pinMode(outputPin, OUTPUT);

#if USE_PWM
    analogWrite(outputPin, brightness);
#else
    digitalWrite(outputPin, toggle ? HIGH : LOW);
#endif

}



void loop(void) {

    if (wdt_cycles < 1) {
        toggle = !toggle;
        wdt_cycles = toggle ? onCycles : offCycles;
#if USE_PWM
        analogWrite(outputPin, toggle ? brightness : 0);
#else
        digitalWrite(outputPin, toggle ? HIGH : LOW);
#endif
    }

    /* Heartbeat: Short blink of builtin led.  This verifies that we
       are actually sleeping and waking up. */
    digitalWrite(LED_BUILTIN, HIGH);
    delay(50);
    digitalWrite(LED_BUILTIN, LOW);

    /* Got to sleep, see [3].  I want `SLEEP_MODE_PWR_DOWN`, but also
       none of the others works. */
    set_sleep_mode(
                   //SLEEP_MODE_IDLE
                   //SLEEP_MODE_ADC
                   //SLEEP_MODE_PWR_SAVE
                   //SLEEP_MODE_STANDBY
                   SLEEP_MODE_PWR_DOWN
                   );  
    sleep_mode();
}


/* References
   [1] https://cdn.sparkfun.com/datasheets/Kits/doc8161.pdf
   [2] https://forum.arduino.cc/index.php?topic=63651.0
   [3] https://playground.arduino.cc/Learning/ArduinoSleepCode
 */    
2

The timers are disabled during SLEEP_MODE_PWR_DOWN, so PWM doesn't work during SLEEP_MODE_PWR_DOWN. You could instead use SLEEP_MODE_IDLE, but that only saves a bit of current.

When you put the MCU to sleep, the led will keep the state it was in during PWM. So around 12.5% if the time the leds will be on, and the other times it will be off (during those 2s PWM periods).

  • Your second comment explains why I have seen light-on in sleep mode, and also why this is difficult to reproduce. – stefan Nov 25 '17 at 13:53
2

Just as a complement to Gerben's answer:

  1. You could use SLEEP_MODE_PWR_DOWN during the off period, but SLEEP_MODE_IDLE is the only way to do PWM while sleeping.
  2. When in SLEEP_MODE_IDLE, you can use the power_*_disable() function defined in <avr/power.h> in order to further reduce the power consumption.
  • Really good info: “SLEEP_MODE_IDLE is the only way to do PWM while sleeping”. I failed to find this in the ATmega docs. I'm using what you suggested, thanks! – stefan Nov 25 '17 at 13:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.