token-vesting icon indicating copy to clipboard operation
token-vesting copied to clipboard

add Sleep-with-millis and power-saving library, understand & document power-saving modes

Open SpenceKonde opened this issue 4 years ago • 30 comments

This library will provide the following functionality:

  • Allow sleep where the RTC is used to keep time in standby sleep, and adjust millis() time upon wake, when using any timer except RTC as the timing source.
  • Provide a wrapper around sleep when RTC is used to keep time so that user does not have to manage re-sleeping when the ISR fires to update millis()

This will also coincide with testing to better understand sleep modes on the tinyAVR 0-series and 1-series parts.

SpenceKonde avatar Apr 02 '20 23:04 SpenceKonde

Interesting observation in #264 -

Also SLEEP_MODE_PWR_DOWN does not work (with the fore-mentioned sketch)!

Should be investigated here...

SpenceKonde avatar Nov 21 '20 13:11 SpenceKonde

Interesting observation in #264 -

Also SLEEP_MODE_PWR_DOWN does not work (with the fore-mentioned sketch)!

Should be investigated here...

Using 2.1.5, SLEEP_MODE_PWR_DOWN seems to be actually SLEEP_MODE_STANDBY from a power point of view. No sleep, Idle sleep and stanbdy sleep work as intendeed, but Power Down does not seems to achieve the targeted sleep. In my tests, SLEEP_MODE_PWR_DOWN used to be +/- 6uA on my board. But is now +/- 150uA (at 3.3v), which is what i used to have with SLEEP_MODE_STANDBY .

EDIT : looks like it's due to a parasitic capacitance on my board, not sure why it was only impacting SLEEP_MODE_PWR_DOWN.

rolland-fx avatar Dec 20 '20 16:12 rolland-fx

Thank you for your great work! While we're all eagerly waiting for the megaTinySleep library, I am working on a board design using ATtiny1616, where I want the controller to periodically wake up from sleep, do some measurements etc - and go back to sleep. But I also want to enable external wakeup if a relay input is triggered. I have found out how to do those things separately, but have not gotten my head around how to get the ATtiny161 to do both. I think what is confusing me is the ISR that seems to need different input parameters for each case, and I have not seen a way to set up two ISRs in parallel. Any hint on how this might be done? I could of course add an external RTC chip, but with such a powerful microcontroller with a RTC sitting there ... Any guidance would be highly appreciated!

ArnieO avatar Feb 07 '21 21:02 ArnieO

Do you have an issue with the PORT interrupt not working when you are using the PIT interrupt? Here is an example where I used both: https://github.com/freemovers/teddy_bear/blob/main/main.c I can provide some Arduino examples later today as well if needed.

On Feb 7, 2021, at 1:23 PM, ArnieO [email protected] wrote:

 Thank you for your great work! While we're all eagerly waiting for the megaTinySleep library, I am working on a board design using ATtiny1616, where I want the controller to periodically wake up from sleep, do some measurements etc - and go back to sleep. But I also want to enable external wakeup if a relay input is triggered. I have found out how to do those things separately, but have not gotten my head around how to get the ATtiny161 to do both. I think what is confusing me is the ISR that seems to need different input parameters for each case, and I have not seen a way to set up two ISRs in parallel. Any hint on how this might be done? I could of course add an external RTC chip, but with such a powerful microcontroller with a RTC sitting there ... Any guidance would be highly appreciated!

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

freemovers avatar Feb 07 '21 22:02 freemovers

@ArnieO - For now I think @freemovers is the guy you want to be asking about this; I have yet to have time to put one of these into sleep mode! I do think that this sounds like it just calls for a linear combination of your two pieces of code...?

SpenceKonde avatar Feb 08 '21 01:02 SpenceKonde

@freemovers : Thank you for your rapid response! This will be my first application with an ATtiny microcontroller, and I prefer to work in Arduino IDE. Compared to interrupts on other microcontrollers I have some experience with, the ISR syntax for ATtiny confuses me. I have spent a few hours searching for overview information, but have only found bits and pieces. I have not read the datasheet in detail (it is 589 pages...) - and the sections relating to interrupts seem to require basic knowledge on how this is handled.

My confusion: In Arduino-style coding several ISRs can be defined, each activated by a call to attachInterrupt(). The ISR examples I have seen for ATtiny use one ISR(<register?>) function call, and it looks like the ISR function must always have this name.

So it could well be that my issue is just lack of competence on the coding of this controller family:

  • Is it possible to define multiple ISR(xxx) functions in the same code? Your linked code indicates that the answer is YES - which is already a great indication. If you also have Arduino examples available that would surely be a bonus!
  • How is priority between those different ISRs handled?

@SpenceKonde : I thought so too until I started looking at code examples. @freemovers will surely be able to provide guidance!

ArnieO avatar Feb 08 '21 09:02 ArnieO

attachInterrupt() is an ABOMINATION It is one of the most vile pieces of code included with the Arduino IDE... and the competition in that space is very tough, let me tell you! All of the third party cores do have it. I would love to cut that cancer out of my cores, but too many people are used to it, and too many libraries depend on it.

Under the hood, all it does is store function pointers - then it's got a row of `ISR(PORTA_PORT_vect)) - well, they use a macro to generate the muiltiple near identical interrupts - but that's basically what it is- a bunch of ISR's that have a loop that runs 8 times, checking each bit of the int flags inturnm and if that is set and there is a function pointer corresponding to it, call that fnction.

You use ONE attachInterrupt. On ONE port - bam. Every single pin interrupt is now claimed by WInterrupt.c and you'll get a multiple definition error if you try to define your own ISR the normal way (with ISR(PERIPHERAL_INTNAME_vect) ...). And the overhead of it having to be able to handle a separate function for each pin slows it down, too (and you generally want an ISR to run as close to instantly as possible and get back to whatever it interrupted.

Each chip has limited number of interrupts. Somewhere I think in extras there's a .md file with a list of all the vector names used on 0/1-series Each vector can have one function assigned to it with ISR() though you can have it do more than one thing, depending on the "flags" For interrupts on a pin, you get one vector per "port" (ie, PORTA has one, PORTB has one, and so on). There are often sensible ways to group things "any of these pins should wake it and turn on backlight") to get some added efficiency - plus you leave the interrupts you're not using available for later use.

I've actually been thinking about whether there is a way that I could make it only generate the ISR for ports that get an interrupt function attached to them.... that would at least take it from apocalyptically bad to only moderately bad. The pin would have to be known at compiletime, but you generally aren't deciding what pin is connected to the interrupt source at runtime...

SpenceKonde avatar Feb 08 '21 11:02 SpenceKonde

Oh, and no priority, generally speaking, first come first served - interrupts can't interrupt other interrrupts except that one interrupts can be marked as high priority, if it is, that can interrupt. In a situation where there are multiple interrupts simultaneously wanting to fire(generally, interrupts were disabled globally, or it was in an interrupt, it is done, interrupts reenabled and all these pending interrupts want to fire - this goes in a fixed numerical priorirty, but you can set a bit to make it do round-robin (IMO if you're considering that, either, you are paranoid and don't need it , or your code is ending up in a horrible position because the interrupts run too slowly and fire too often and like, maybe that's the thing to be fixing?).

SpenceKonde avatar Feb 08 '21 11:02 SpenceKonde

attachInterrupt() is an ABOMINATION It is one of the most vile pieces of code included with the Arduino IDE... and the competition in that space is very tough, let me tell you!

😆

Thank you for this great "ATtiny interrupts for dummies" writeup, @SpenceKonde - very helpful and very much appreciated!

And I promise not to attempt using attachInterrupt() in my ATtiny project. 😉

ArnieO avatar Feb 08 '21 14:02 ArnieO

Here is some basic code that uses the Periodic Interrupt Timer (PIT) and a PORT interrupt. The timer is triggered every 4 seconds to turn off the LED while the PORT interrupt is used to turn on the LED. PIT and PORT peripherals both work in Power Down sleep mode.

#include <avr/sleep.h>

void RTC_init(void)
{
  RTC.CLKSEL = RTC_CLKSEL_INT1K_gc;         // 1kHz Internal Crystal Oscillator (INT1K)
  while (RTC.STATUS > 0 || RTC.PITSTATUS);  // Wait for all register to be synchronized
  RTC.PITINTCTRL = RTC_PI_bm;               // Periodic Interrupt: enabled
  RTC.PITCTRLA = RTC_PERIOD_CYC4096_gc      // 1024 / 4096 = 1/4Hz, 4 sec
  | RTC_PITEN_bm;                           // Enable: enabled PIT
}

// Wake up routines
ISR(RTC_PIT_vect)
{
  RTC.PITINTFLAGS = RTC_PI_bm;        // Clear flag
  digitalWrite(2, LOW);
}

ISR(PORTA_PORT_vect)
{
  PORTA.INTFLAGS = PIN2_bm;       // clear interrupt flag  
  digitalWrite(2, HIGH);
}

void setup() {
  RTC_init();
  pinMode(2, OUTPUT);
  PORTA.PIN2CTRL = PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc;  // digital pin #5 on 412
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);   // Set sleep mode to POWER DOWN mode
  sleep_enable();
  interrupts();
}

void loop() {
  // put your main code here, to run repeatedly:
  sleep_cpu();
}

freemovers avatar Feb 08 '21 16:02 freemovers

Thanks a lot @freemovers, this is very helpful! Your code works - and I also managed to move the interrupt to an other pin after having read up a bit on the basics of port manipulation.

ArnieO avatar Feb 09 '21 19:02 ArnieO

@freemovers

Your example here above use RTC_PIT_vect for timed sleep. So the RTC must be enabled in Arduino IDE submenu. The consequence seems to be that micros() is not available -- which I need in my design for controlling a neopixel.

Do you see a way out; can timed sleep be done with an other counter?

ArnieO avatar Feb 22 '21 16:02 ArnieO

I have not used the RTC for millis() before (the option in the Arduino Menu), but I just use the interrupt above. You can still use the default timer for millis() and micros(). The RTC is enabled in the example above as follows (not from the Arduino menu):

  RTC.PITCTRLA = RTC_PERIOD_CYC4096_gc      // 1024 / 4096 = 1/4Hz, 4 sec
  | RTC_PITEN_bm;                           // Enable: enabled PIT

Keep in mind that you cannot use the power down sleep mode since that will turn off the timer (TCA or TCD) that controls the millis() and micros(). These timers will only work in Idle Sleep mode:

set_sleep_mode(SLEEP_MODE_IDLE);

I'm not sure how often you change the neopixel, but you could update the neopixel while the MCU is running, and put it back in sleep mode after the neopixel is updated.

freemovers avatar Feb 22 '21 17:02 freemovers

They work in standby sleep mode too if you set runstandby. Standby gets down t almost powerdown levels of powerconsumption and is the "good" sleep mode


Spence Konde Azzy’S Electronics

New products! Check them out at tindie.com/stores/DrAzzy GitHub: github.com/SpenceKonde ATTinyCore: Arduino support for almost every ATTiny microcontroller Contact: [email protected]

On Mon, Feb 22, 2021, 12:02 Sander van de Bor [email protected] wrote:

I have not used the RTC for millis() before (the option in the Arduino Menu), but I just use the interrupt above. You can still use the default timer for millis() and micros(). The RTC is enabled in the example above as follows (not from the Arduino menu):

RTC.PITCTRLA = RTC_PERIOD_CYC4096_gc // 1024 / 4096 = 1/4Hz, 4 sec | RTC_PITEN_bm; // Enable: enabled PIT

Keep in mind that you cannot use the power down sleep mode since that will turn off the timer (TCA or TCD) that controls the millis() and micros(). These timers will only work in Idle Sleep mode:

set_sleep_mode(SLEEP_MODE_IDLE);

I'm not sure how often you change the neopixel, but you could update the neopixel while the MCU is running, and put it back in sleep mode after the neopixel is updated.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/SpenceKonde/megaTinyCore/issues/158#issuecomment-783521645, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABTXEWYP22DFWTFYBBESUUDTAKE23ANCNFSM4L3C6ANQ .

SpenceKonde avatar Feb 22 '21 17:02 SpenceKonde

Thank you both for pointing me in the right direction.

And indeed - I got it working now!

ArnieO avatar Feb 22 '21 18:02 ArnieO

@SpenceKonde, most peripherals can run in standby mode, but looks like the TCA and TCD are limited to idle sleep: image

freemovers avatar Feb 22 '21 18:02 freemovers

Ooo, I see what you meant.

Right. But why the hell do you need micros for neopixels?! o_o

SpenceKonde avatar Feb 22 '21 18:02 SpenceKonde

But why the hell do you need micros for neopixels?! o_o I have not found yet a neopixel library that compiles without having micros() available, including this one: https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/tinyNeoPixel.md 😉 Error message if I disable millis()/micros(): ...\2.2.6\libraries\tinyNeoPixel_Static/tinyNeoPixel_Static.h:297: undefined reference to micros'` It compiles after having enabled millis()/micros().

An other point: It looks like millis() stops running while in SLEEP_MODE_STANDBY. Just an observation, not an issue for my current application. My current settings are: image

EDIT I think @freemovers answered this above. Default timer is TCD, which does not run in Standby mode.

EDIT 2 Nope, I tried with other clock sources (TCA, TCB0, TCB1, TCD) in the submenu millis()/micros(). In all cases, millis() seems to stop during Standby sleep.

ArnieO avatar Feb 22 '21 20:02 ArnieO

Only the TCB you should be able to run in Standby, but you have to enable that option: image But you don't really need micros() running all the time for neopixels to work. Here is an example of the simple sketch from the TinyNeoPixel Static example. Timers are running when it updates the output to the neopixels, but instead of using a delay, the MCU simple goes to sleep:

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

#define PIN            15
#define NUMPIXELS      30

void RTC_init(void)
{
  RTC.CLKSEL = RTC_CLKSEL_INT1K_gc;         // 1kHz Internal Crystal Oscillator (INT1K)
  while (RTC.STATUS > 0 || RTC.PITSTATUS);  // Wait for all register to be synchronized
  RTC.PITINTCTRL = RTC_PI_bm;               // Periodic Interrupt: enabled
  RTC.PITCTRLA = RTC_PERIOD_CYC512_gc      // 1024 / 512 = 2Hz, 500 msec
  | RTC_PITEN_bm;                           // Enable: enabled PIT
}

// Wake up routines
ISR(RTC_PIT_vect)
{
  RTC.PITINTFLAGS = RTC_PI_bm;        // Clear flag
}

byte pixels[NUMPIXELS * 3];

tinyNeoPixel leds = tinyNeoPixel(NUMPIXELS, PIN, NEO_GRB, pixels);

// int delayval = 500; // delay for half a second

void setup() {
  RTC_init();
  pinMode(PIN, OUTPUT);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);   // Set sleep mode to POWER DOWN mode
  sleep_enable();
  interrupts();
}

void loop() {

  for (int i = 0; i < NUMPIXELS; i++) {
    leds.setPixelColor(i, leds.Color(0, 150, 0)); // Moderately bright green color.
    leds.show(); // This sends the updated pixel color to the hardware.
    // delay(delayval); // Delay for a period of time (in milliseconds).
    sleep_cpu();
  }
  for (int i = 0; i < (NUMPIXELS * 3); i++) {
    pixels[i] = 150; //set byte i of array (this is channel (i%3) of led (i/3) (respectively, i%4 and i/4 for RGBW leds)
    leds.show(); //show
    // delay(delayval); //delay for a period of time
    sleep_cpu();
    pixels[i] = 0; //turn off the above pixel
  }
}

freemovers avatar Feb 23 '21 05:02 freemovers

Oh, I see, to ensure that it doesn;t get called so frequently the pixels never latch.... I will correct the dependence on micros in next release, and instead issue a #warning in that situation that they MUST ensure at least 50 us (in practice, apparently only 20 is needed!) between calls to show() - with the tighter constraint imposed by real-world neopixels, (datasheet says 50, but in reality? they latch after 20!), I think it's unlikely to be tripped over, except for very tight loops with very few pixels. I have seen neopixels used to reduce the pincount requirement for an RGB led when flash was abundant and pins scarce...

SpenceKonde avatar Feb 23 '21 05:02 SpenceKonde

I had even checked for it in the DISABLE_MILLIS case! But not RTC millis case. Might have predated RTC millis

SpenceKonde avatar Feb 23 '21 05:02 SpenceKonde

TinyNeoPixel dependency on micros() Great @SpenceKonde , thank you for looking into that! Neopixel is a great solution for implementing a multi-colour signaling LED using only one GPIO (the case in my current project). A library implementation (with some minor limitations) that is not dependent on micros() will be a nice improvement.

millis() not running during SLEEP_MODE_STANDBY

Only the TCB you should be able to run in Standby, but you have to enable that option: image

Indeed, it seems like TCB should be used if needing to have a counter running during Standby. TDC (which seems to be the default on 1-series) can not be kept running: image

EDIT OK, so I added this line to setup(): TCB1.CTRLA |= TCB_RUNSTDBY_bm; And recompiled with image Alas, the counter still does not seem to run during Standby, at least it is not seen by millis(). Any ideas?

ArnieO avatar Feb 23 '21 09:02 ArnieO

Yeah, non-micros-dependent version is checked in now - drop-in replacement. If you want it. I'd recommend grabbing it from the repo, not planning to do any releases for a while now because I gotta get some stuff sorted out on ATTinyCore.

I'm not sure I follow your question? Why do you want an oscillator running, unless it's being used by something? And if it is being requested by something that runs in standby sleep, then it wouldn't turn off... What do you mean by "it will no longer work if the code is recompiled with a different clock source setting"? which setting are you referring to?

On Tue, Feb 23, 2021 at 4:09 AM ArnieO [email protected] wrote:

TinyNeoPixel dependency on micros() Great @SpenceKonde https://github.com/SpenceKonde , thank you for looking into that! Neopixel is a great solution for implementing a multi-colour signaling LED using only one GPIO (the case in my current project). A library implementation (with some minor limitations) that is not dependent on micros() will be a nice improvement.

millis() not running during SLEEP_MODE_STANDBY

Only the TCB you should be able to run in Standby, but you have to enable that option: [image: image] https://user-images.githubusercontent.com/10295178/108818073-24b25a80-75b9-11eb-89dc-aaeeeb12ac6f.png

OK, but this seems to be more generic than only TCB, running the clock source during Standby can be enabled for all clock sources: [image: image] https://user-images.githubusercontent.com/10295178/108818448-b0c48200-75b9-11eb-9cbc-ecbc63ac1eb3.png @SpenceKonde https://github.com/SpenceKonde : Do you see a generic way to request the selected clock source to keep running during sleep, like a function or macro that could be called right before sleep_cpu()? If this is done in the code "by writing a '1' to the Run Standby bit (RUNSTDBY) in the respective oscillator's Control A register", it will no longer work if the code is recompiled with a different clock source setting.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/SpenceKonde/megaTinyCore/issues/158#issuecomment-784022447, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABTXEW44XP7HYQ6MX6VLUG3TANWFRANCNFSM4L3C6ANQ .

--


Spence Konde Azzy’S Electronics

New products! Check them out at tindie.com/stores/DrAzzy GitHub: github.com/SpenceKonde ATTinyCore https://github.com/SpenceKonde/ATTinyCore: Arduino support for all pre-2016 tinyAVR with >2k flash! megaTinyCore https://github.com/SpenceKonde/megaTinyCore: Arduino support for all post-2016 tinyAVR parts! DxCore https://github.com/SpenceKonde/DxCore: Arduino support for the AVR Dx-series parts, the latest and greatest from Microchip! Contact: [email protected]

SpenceKonde avatar Feb 23 '21 17:02 SpenceKonde

Why do you want an oscillator running, unless it's being used by something?

For the moment I am only trying to use it for verifying the sleep duration, while learning how to master these devices. So not a blocking point for my ongoing project.

What do you mean by "it will no longer work if the code is recompiled with a different clock source setting"? which setting are you referring to?

If the port manipulation I attempted in my previous post had worked, it would no longer have worked if I rebuilt the code while in the Arduino IDE submenu selecting another clock source, for instance TCB0, because the port manipulation is done on TCB1 in the code.

ArnieO avatar Feb 23 '21 19:02 ArnieO

Okay, now I see the context. Yes, you most certainly do have to configure the same timer/counter. Keeping the oscillator running would be beside the point though - if you had the oscillator running, but you hadn't set the currently selected timer to run in standby, doing that wouldn't make the the timer (which wasn't set to run in standby) run in standby.

Huh, I'm surprised that that didn't do something! What I would have expected is that it when you did that, it would sleep until the next millis overflow, and then the TCB overflow interrupt would fire and you would no longer be asleep....

You can't reasonably use the high speed timers we normally use for microsecond resolution timing while also sleeping - the overflows come often enough that you're waking up constantly. you can get like... 6.4 ms between interrupts with 20 MHz system clock while sleeping. And they're power hogs! I would be inclined to set up a second one to time it with - as in. have it drop a pin (with digitalWriteFast or VPORT write - those are both single cycle, vs what, 50 clocks for digitalWrite? It may even be more than that. digitalWrite is godawful slow for what it's doing - it's all to permit people to specify pins by Arduino pin numbers at runtime where the pins are mapped arbitrarily onto actual ports/bits). On the other AVR you could use pulseIn() if you're lazy and don't need extreme accuracy, or use a TCB for input capture if one of those isn't true. If you used an AVR DB-series part, or AVR DA-series or tinyAVR 2-series, you could put a pair of TCBs into CASCADE mode to do 32-bit input capture, and measure something 3 minutes long with a granularity of... 24ths of a microsecond? I'd probably clock the Dx at 16 MHz or 32 MHz (they work fine at 32 at room temp; I suspect they wouldn't at 85C) usingthe TCA prescaler prescaling /16 as it's clock source, that way I'd get 1 us granularity in a measurement of an event... oh... 71 minutes long (2^32 * (PRESCALE / F_CPU) / 1000 us/ms / 1000 ms/s / 60min/s = 71 minutes and change. Sometimes 16-bit input capture just doesn't feel like enough. But 32-bit always feels like way more than necessary.

terminology: "register" manipulation, not "port" manipulation ("port manipulation" specifically refers to doing that to the PORT registers, which control the I/O pins, generally to get faster digitalRead/etc, occasionally to save flash (I did that once because the hundred-some-odd bytes of flash you get when you get rid of the last instances of each of the three digital I/O functions, and the last instance of any of them (for the tables they use), well, we had code that needed to go there...). Here, on DxCore and I think MegaCoreX as well, digitalReadFast, digitalWriteFast and now openDrainFast make direct port manipulation much less necessary now. digitalWriteFast(PIN_PA0,HIGH); compiles to the same instruction as VPORTA.OUT |= 1<<0; namely sbi 1,0;

SpenceKonde avatar Feb 23 '21 21:02 SpenceKonde

Huh, I'm surprised that that didn't do something! What I would have expected is that it when you did that, it would sleep until the next millis overflow, and then the TCB overflow interrupt would fire and you would no longer be asleep....

Apparently I have misunderstood something, as I don't understand why TCB1.CTRLA |= TCB_RUNSTDBY_bm; should wake it from sleep?

Below is my (pseudo) code. I have three ISRs, each sets a flag that is acted upon in loop(). It works as expected:

  • Wakes from sleep each 4 sec and makes a green blink
  • Red or blue blink whenever I actuate one of the interrupt pins

But timer value written to OLED display is 13 or 14 ms also with the TCB1 register manipulation at the end of setup(). I expected that line to cause TCB1 to run during the sleep period, so that 4014 ms was measured. (And thank you for correcting my terminology!)

void setup()
{
    /* other stuff */
    
    // Port interrupt setup
    RTC_init(4);    // Number of seconds per sleep period
    PORTB.PIN2CTRL = PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc; //PB2
    PORTC.PIN1CTRL = PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc; //PC1
    interrupts();                                            // Enable interrupts; equivalent to sei();

    sleep_enable();
    set_sleep_mode(SLEEP_MODE_STANDBY);
    TCB1.CTRLA |= TCB_RUNSTDBY_bm;    //Should cause TCB1 to run during SLEEP_MODE_STANDBY so that millis() keeps running.
}

void loop()
{
    unsigned long timer = millis();
    power_all_disable();
    sleep_cpu(); //Sleeps CPU for the number of seconds set by argument to RTC_init(n) in setup()
    
    if (interruptTimer)
    {
        interruptTimer = false;
        /* Green blink */
        /* Write to OLED display: millis() - timer */
    }
    
    if (interruptMagnet)
    {
        interruptMagnet = false;
        /* Red blink */
    }
    if (interruptRelay)
    {
        interruptRelay = false;
        /* Blue blink */
    }
    
}

ArnieO avatar Feb 24 '21 11:02 ArnieO

Deferred to 2.4.0. Sorry, blame microchip for releasing new parts.

SpenceKonde avatar Apr 03 '21 13:04 SpenceKonde

Sorry again, 2.5.0. Need to get work on other cores done and 2.4.0 has taken almost a month longer than expected :-(

SpenceKonde avatar Jul 19 '21 20:07 SpenceKonde

I have to use Serial and millis() and micros() for an application. BOD Mode is Disabled

what I'd be doing is:

#include <avr/sleep.h>

void setup(){
    Serial.begin(115200);
    // Initialise a button pin for wake up interrupt. 
    // Then initialise all unused pin to INPUT_PULLUP using Direct port manipulation methods. 

    //--- Sleep mode enablers ---//
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable();
}

void loop(){
    // when system is awake
    // read if there is something in serial. 
    // do something with that data
    // -- These can be, in future, be more optimised ...  
   
    Serial.flush();                    // flush everything before going to sleep
    delay(1);
    sleep_cpu();
}

I have many other stuff in there. But basically:

  1. If Serial is being used by the application, then the current consumption, even in sleep mode is very high (in milli Amps). ppk-20210823T143246

  2. If Serial methods are removed the current consumption reduces drastically. (in fraction of micro Amps) ppk-20210823T143406

Any pointers on that? I have to use Serial and it would need to run on a small Low Capacity LiPo ... 🤕

Guesses: You're winding up with a bunch of stuff in the buffer and then flush is taking larger than you realize. A serial interrupt is waking the device after you go to sleep (someting like a transmit compltet)

dattasaurabh82 avatar Aug 23 '21 14:08 dattasaurabh82

Thank you @SpenceKonde for all your great work and sharing of your know-how. To @freemovers, great sketch above which worked right away for me, too. Thanks for sharing.

I am using Core 2.6.10 with a Nordic PPK2 at 3V0 powering an ATtiny402-SSN at 1MHz internal, no BOD, no WDT, with RTC on, with just 2x 100nF capacitors and an actice-low LED on PA2, trigger on PA6. Alas, to really come down to 600nA during sleep, I had to follow best practice advice and add input disables (edit: sorry, saw this link only after posting, maybe the image below can be seen as a visualization now...):

  PORTA.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc;
  ...
  PORTA.PIN7CTRL = PORT_ISC_INPUT_DISABLE_gc;
  • SLEEP_MODE_IDLE: 600µA
  • SLEEP_MODE_STANDBY: 600nA
  • SLEEP_MODE_PWR_DOWN: 600nA (0.6µA, see screenshot below)
    • RTC is still running here, I guess, otherwise we should be at 100nA with "all peripherals are stopped"
  • No sleep / active loop: 770µA

I find these measurements meet what's stated in the data sheet (table 33.4) and more than good enough for my intended app which polls and de-bounces some reed switch input and sends data only upon Serial request. And when Serial is on, which will be most of the time, there's plenty of power from the requesting line. A small battery will be backup only.

Screenshot 2023-12-25 170533

m9aertner avatar Dec 25 '23 16:12 m9aertner