SpenceKonde / megaTinyCore

Arduino core for the tinyAVR 0/1/2-series - Ones's digit 2,4,5,7 (pincount, 8,14,20,24), tens digit 0, 1, or 2 (featureset), preceded by flash in kb. Library maintainers: porting help available!
Other
545 stars 141 forks source link

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

Open SpenceKonde opened 4 years ago

SpenceKonde commented 4 years ago

This library will provide the following functionality:

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

SpenceKonde commented 3 years ago

Interesting observation in #264 -

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

Should be investigated here...

rolland-fx commented 3 years ago

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.

ArnieO commented 3 years ago

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!

freemovers commented 3 years ago

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 notifications@github.com 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.

SpenceKonde commented 3 years ago

@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...?

ArnieO commented 3 years ago

@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:

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

SpenceKonde commented 3 years ago

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 commented 3 years ago

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?).

ArnieO commented 3 years ago

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. 😉

freemovers commented 3 years ago

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();
}
ArnieO commented 3 years ago

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 commented 3 years ago

@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?

freemovers commented 3 years ago

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.

SpenceKonde commented 3 years ago

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: spencekonde@gmail.com

On Mon, Feb 22, 2021, 12:02 Sander van de Bor notifications@github.com 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 .

ArnieO commented 3 years ago

Thank you both for pointing me in the right direction.

And indeed - I got it working now!

freemovers commented 3 years ago

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

SpenceKonde commented 3 years ago

Ooo, I see what you meant.

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

ArnieO commented 3 years ago

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 tomicros'` 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.

freemovers commented 3 years ago

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
  }
}
SpenceKonde commented 3 years ago

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 commented 3 years ago

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

ArnieO commented 3 years ago

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?

SpenceKonde commented 3 years ago

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 notifications@github.com 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: spencekonde@gmail.com

ArnieO commented 3 years ago

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.

SpenceKonde commented 3 years ago

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;

ArnieO commented 3 years ago

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:

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 */
    }

}
SpenceKonde commented 3 years ago

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

SpenceKonde commented 2 years ago

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 :-(

dattasaurabh82 commented 2 years ago

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)

m9aertner commented 6 months ago

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;

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