ArduinoCore-API
ArduinoCore-API copied to clipboard
Low Power Shared API
As per ongoing discussion with .org about libraries, here is a proposal for MCU (and companion MCU) standby API
https://github.com/rocketscream/Low-Power is a good starting point, AVR API is too granular so we should avoid it. But the SAMD API is quite good.
Function calls would simply be
-
idle()
-
sleep()
-
deepSleep()
-> might callsleep()
if there is only one sleep mode
which in modern MCU is like WFI with peripherals enabled and WFI with peripherals disabled. STM32 processors also have a mode called WFE which I would avoid exposing. Other MCU (nRF chips for example) might have multiple sleep levels; I'd select two/three of them to be exposed.
To register a wakeup source I propose overloading attachInterrupt
API with a couple of versions:
-
attachInterrupt(pin, function, MODE, wakeup)
would become the main function; -
attachInterrupt(pin, function, MODE, false)
will be called for classic API -
attachInterruptWakeup(pin, function, MODE)
will callattachInterrupt(pin, function, MODE, true)
If function is NULL, wakeup-enable bit should be set and no function called
if pin
in NOT_A_PIN
, extra code can handle the additional wakeup sources.
I suggest using macros like RTC_WAKEUP
or ADC_WAKEUP
with high values (0xFF, 0FE etc)
@cmaglie @sandeepmistry @tigoe any thoughts about that?
Random thought... something like sleep(MODE) seems like it would make it easier to expand the number of sleep modes / add additional options in the future. If for no other reason than there'd only be documentation of a single function to update if / when things change.
On Thu, Oct 27, 2016 at 7:45 AM, Martino Facchin [email protected] wrote:
As per ongoing discussion with .org about libraries, here is a proposal for MCU (and companion MCU) standby API
https://github.com/rocketscream/Low-Power is a good starting point, AVR API is too granular so we should avoid it. But the SAMD API is quite good.
Function calls would simply be
- idle()
- sleep()
- deepSleep() -> might call sleep() if there is only one sleep mode
which in modern MCU is like WFI with peripherals enabled and WFI with peripherals enabled. STM32 processors also have a mode called WFE which I would avoid exposing. Other MCU (nRF chips for example) might have multiple sleep levels; I'd select two/three of them to be exposed.
To register a wakeup source I propose overloading attachInterrupt API with a couple of versions:
- attachInterrupt(pin, function, MODE, wakeup) would become the main function;
- attachInterrupt(pin, function, MODE, false) will be called for classic API
- attachInterruptWakeup(pin, function, MODE) will call attachInterrupt(pin, function, MODE, true)
If function is NULL, wakeup-enable bit should be set and no function called
if pin in NOT_A_PIN, extra code can handle the additional wakeup sources. I suggest using macros like RTC_WAKEUP or ADC_WAKEUP with high values (0xFF, 0FE etc)
@cmaglie https://github.com/cmaglie @sandeepmistry https://github.com/sandeepmistry @tigoe https://github.com/tigoe any thoughts about that?
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/arduino/ArduinoCore-API/issues/13, or mute the thread https://github.com/notifications/unsubscribe-auth/AAYuQpcMgFWMI0ltT5VCAsOpZsChzD5Cks5q4Lj0gaJpZM4Kib7g .
While this is true, sleep(MODE) is also very likely to fill the Arduino community with tons of different MODE defines which aren't compatible with other boards. Imagine a beginner finds a code sample on some website and tries to run it on a MKR1000. It fails to compile because it's got a MODE constant that's only present on a STM32 chip. Then they find another sample, which also doesn't compile, because of a Teensy-specific MODE constant. And then yet another with ESP8266 constants, and so on....
I like the idea of having 3 simple sleep functions, and using attachInterrupt to define wakeup sources.
And I think having a handful of defines for some fairly common interrupt sources (ADC, UART, I2C) is the way to go, with the exception that we might want to handle timer-based wakeup sources by having overloaded versions of the sleep()
methods, i.e. sleep(1000)
, rather than having a TIMER...
define that must be passed to attachInterrupt
.
I like idle(), sleep() and deepSleep(). Would idle() be something like a non-blocking delay()?
I have a similar question as Tom's about idle()
, does it wait for an interrupt?
I like @eriknyquist's suggestion to include an optional number parameter to sleep
and deepSleep
for the number of milliseconds to sleep for.
@facchinm would all these functions be in the global scope (like attachInterrupt
)?
If function is NULL, wakeup-enable bit should be set and no function called
if pin in NOT_A_PIN, extra code can handle the additional wakeup sources. I suggest using macros like RTC_WAKEUP or ADC_WAKEUP with high values (0xFF, 0FE etc)
I'm concerned about the NOT_A_PIN
, is the pin
argument more like a wake up source?
What do you think about registering all attached interrupts as wake sources? Is there a downsize to doing this?
Each semiconductor company uses slightly different terms, and sometimes inconsistently among different product lines from the same company.
But generally speaking, "idle" usually means everything but the CPU keeps running. Serial communication, for example, keeps flowing. All normal interrupts will wake the CPU. Arduino features like millis() tend to work, keeping track of elapsed time correctly. Wakeup is very quick. Timers and PWM usually keep running, so this mode is very easy to use. Usually power is reduced by 50% to 80%.
Usually "sleep" means much more of the chip is shut down. Clocked peripherals like timers usually stop running. Pin interrupts and other external signals tend to be able to wake the chip. Wakeup might have latency. Usually these modes make a very dramatic reduction is power usage, but they're much more disruptive than idle. Generally speaking, the processor is able to resume executing the program where it left off.
Usually "deep sleep" means nearly all of the chip is shut down. Wakeup options are limited. Many chips must reboot when waking from these extreme sleep modes, or require other special effort to resume after waking up. Most chips with these modes draw only microamps of current, which is for all practical purposed "off". The effective self-discharge of AAA batteries at room temperature is more than the current consumed by most deep sleep modes.
Of course, there's quite a bit of variation. Many modern chips have about a dozen different modes, but the trend is usually several different flavors of "deep sleep".
idle()
usually keeps all peripherals active and halts the core (which makes me think that, in the Arduino world, it is quite useless since millis()
timer is always active and would kick the MCU out of this state every millisecond).
sleep()
should be like: " all off unless selected IRQ sources "
Paul's explanation is supercomplete and makes me think that supporting a common deepSleep
feature would need a lot of extra effort beside the API scheme.
@sandeepmistry I also like @eriknyquist proposal for overloaded versions with millis as sleep duration.
I'd love to use the global scope but maybe not in the first revision of the API.
The pin
argument would be in fact a wakeup source, with the actual pins representing a special case.
About always enabling all the wakeup sources, there should be no side effect on normal Arduino sketches, but it would make it harder for 3rd party cores people / advanced users to override this and only enable what they need.
Here https://github.com/facchinm/ArduinoLowPower there is a stub implementation of a possible ArduinoLowPower library. The ARC32 support is still broken due to multiple causes (see https://github.com/facchinm/ArduinoLowPower/commit/e99fcefe8bd76fd5749965ea1accba5adec5d05f)
The SAMD port needs RTCZero patched with https://github.com/arduino-libraries/RTCZero/pull/18
I like the look of that stub. The examples are very clear and useful.
@facchinm looks good. You should sync up with @bigdinotech, he's working on the same thing.
One thing that comes to mind; it looks like you are setting up and enabling interrupts in attachWakeupSource()
. I was thinking there might be a problem with this, since the interrupt could trigger before we've actually gone to sleep (given that it should be valid to attach mutiple wakeup sources, so user may do it a few of them in a loop. Or they might call attachWakeupSource()
long before calling one of the sleep functions, for whatever reason).
So attachWakeupSource()
really just stores the info in a table somewhere, then the sleep
functions will set up the interrupts just before going into a sleep state. I already have that table mostly done with some helper functions, just on a local machine at the mo but I can push it up to my fork if you want to see it.
@facchinm If you want to take a look at what I have so far: https://github.com/bigdinotech/Arduino101Power/
I am able to use the RTC as a wakeup source but it still missing the implementation of saving the context so it appears that the sketch restarts to the beginning of setup upon waking up.
I also have an implementation of doze which simply uses the RTC clock as a system clock. Which actually saves quite a bit of power. Using a tinyTILE with the led removed I measured 4.44mW vs 118mW normal operation.
@bigdinotech wow, that was exactly what I was looking for :smile: From my tests with the 101 (without removing the LEDs so the results have a huge bias)
-
doze
(which could maps 1-to-1 toidle
-> 60mA to 32 mA, wakeup works correctly with RTC) :+1: -
sleep
anddeepSleep
-> consumption drops to 17mA but no wakeup is possible (nor RTC neither SOFT_RESET, after changing the x86 fw not to trigger a reset of course)
At the moment I'm not sure if we'd better make ArduinoLowPower a stub library (with virtual methods only) or keeping it monolithic... @tigoe @cmaglie any preference?
@eriknyquist , I agree with your POW, the wakeup irq should only be called after a full sleep/wakeup cycle has been completed. The only feasible way (considering all the existing core infrastructure) is registering the irq when requested but enabling it just before going to sleep. I can try setting up something for a latency comparison :slightly_smiling_face:
I'd like to propose using a static pre-defined C++ class, like is done for Serial, Wire, SPI, etc. I know it's fairly late, but hopefully this can still be considered?
There's 2 reasons.
- Less likely to conflict with use of these common English works in existing code.
- Some chips have special initialization requirements, especially for their watchdog timers. Some allow the watchdog to be configured only once. Some have a timer which locks out changes a certain amount of time after the chip resets. A C++ class would allow implementations for those chips to define a constructor to do this sort of initialization. Semiconductor manufacturers are increasingly designing their watchdog timers to remain unchangeable at runtime, so long term we can expect this to be needed most newer hardware.
What would you call that class, Paul? I can see some sense in the idea, and I'm curious whether the name should reflect processor type or something else. For example, in serial we have Serial, Serial1, Serial2, etc. when there are multiple UARTs. Would this class be ARM.xxx, ARC.xxx, etc, or would it be WatchDog.xxx? Or something else?
It could be called LowPower, or PowerManagement, or PowerControl, or EnergySaver, anything else which intuitively tells ordinary people it controls how much power their board consumes and lets them access special energy saving features. I'm probably not the best person to say what names are intuitive to beginners.
The name should definitely not be chip or board specific. When someone like Sparkfun publishes a tutorial on their website where a sketch has "PowerManagement.sleep(15);" using Arduino Micro, you definitely want that same code to compile and at least do something reasonable on Arduino Uno, Arduino Zero, MRK1000, Arduino 101, and every other board.
@facchinm that might work for GPIOs, but there will be other wakeup sources that require a different procedure to set up that just calling attachInterrupt.
Additionally (unrelated), what happens when you call sleep but (for example) SPI, or UART, is halfway through an IRQ transfer? Calling 'something.flush()' isn't quite good enough, since you don't even know if that particular bit has been initialised with 'begin()' or not.
We could, instead, call it out as the user's responsibility to call Serial.flush() or whatever before sleeping. We could also track these things and do it for them. I'm not really sure which one I prefer yet, but I did start doing this https://github.com/eriknyquist/corelibs-arduino101/commit/8387650245eed99e39e1733fb5ed7d10bce7d7f5 in the event that we want to track everything.
That clears things up, thanks. PowerManager is pretty good as a name, I could go with that.
I'd like to propose using a static pre-defined C++ class, like is done for Serial, Wire, SPI, etc.
@PaulStoffregen are you thinking there would be another core specific library bundled in the same place as Wire and SPI?
How it's distributed (a core-specific lib, or builtin lib which some cores override, or included in the core library) isn't really a concern for me.
My only hope is for an API which is as compatible as possible across boards. Serial, Wire, SPI (using SPISettings), tone(), attachInterrupt() are all very good APIs which allow novices to pretty easily share code across different boards. Ideally, this power management stuff would look like those. Hopefully it doesn't end up with hardware-specific stuff, as is commonly done today for access to hardware timers or non-blocking analog input.
Another advantage to the static class is that it makes for clearer differentiation between Processor sleep and peripheral sleep, for example the wifi sleep on the MKR1000.
In fact, it might be worth calling the static class Processor. or CPU. for that reason. Though that opens a whole can of worms as to other things you could or should do with a class by that name...