See also hardware for a document discussing hardware issues and power draw calculations and measurements. This document is based on the Pyboard V1.0 and V1.1. The other platforms based on the STM chips may have different hardware functionality. This applies to the Pyboard Lite which supports only a subset.
This module provides access to features of the Pyboard which are useful in low
power applications but not supported in firmware at the time of writing: check
for official support for any specific feature before using. The modules
alarm.py
and ttest.py
provide simple demonstrations of its use. Access
to the following processor features is provided:
- 4KB of backup RAM (optionally battery-backed) - accessible as words or bytes.
- 20 general purpose 32-bit registers also battery backed.
- Wakeup fom standby by means of two Pyboard pins.
- Wakeup by means of two independent real time clock (RTC) alarms.
- Access to board voltages and CPU temperature without the drawbacks of
ADCAll
.
Utility functions provide a way to determine the reason for a wakeup and offer a
low power alternative to the official delay()
function.
All code is released under the MIT license
When a Pyboard goes into standby its consumption drops to about 6uA. When it is
woken, program execution begins as if the board had been initially powered up:
boot.py
then main.py
are executed. Unless main.py
imports your
application, a REPL prompt will result. Assuming your application is re-run,
there are ways to retain some program state and to determine the cause of the
wakeup. Mastering these enables practical applications to be developed. The
following is one of the demo programs alarm.py
which uses the two timers to
wake the Pyboard alternately, each once per minute.
import stm, pyb, upower, machine
red, green, yellow, blue = (pyb.LED(x) for x in range(1, 5))
rtc = pyb.RTC()
# If we have a backup battery clear setting from a previously running program
rtc.wakeup(None)
reason = machine.reset_cause() # Why have we woken?
if reason == machine.PWRON_RESET or reason == machine.HARD_RESET: # first boot
rtc.datetime((2015, 8, 6, 4, 13, 0, 0, 0)) # Code to run on 1st boot only
aa = upower.Alarm('a')
aa.timeset(second = 39)
ab = upower.Alarm('b')
ab.timeset(second = 9)
red.on()
elif reason == machine.DEEPSLEEP_RESET:
reason = upower.why()
if reason == 'ALARM_A':
green.on()
elif reason == 'ALARM_B':
yellow.on()
upower.lpdelay(1000) # Let LED's be seen!
pyb.standby()
The module requires a firmware build dated 1st October 2016 or later: on import an OSError will be raised if this condition is not met. Note that the module uses the topmost three 32 bit words of the backup RAM (1021-1923 inclusive).
Note on objects in this module. Once rtc.wakeup()
is issued, methods other
than enable()
should be avoided as some employ the RTC. Issue rtc.wakeup()
shortly before `pyb.standby``.
The module provides a single global variable:
usb_connected
A boolean, True
if REPL via USB is enabled and a physical
USB connection is in place.
The module provides the following functions:
lpdelay
A low power alternative topyb.delay()
.lp_elapsed_ms
An alternative topyb.elapsed_millis
which works duringlpdelay
calls.now
Returns RTC time in millisecs since the start of year 2000.savetime
Store current RTC time in backup RAM. Optional argaddr
default 1021 (uses 2 words).ms_left
Enables a timed sleep or standby to be resumed after a tamper or WKUP interrupt. Requiressavetime
to have been called before commencing the sleep/standby. Argumentsdelta
the delay period in ms,addr
the address where the time was saved (default 1021).cprint
Same usage asprint
but does nothing if USB is connected.v33
No args. Returns Vdd. If Vin > 3.3V Vdd should read approximately 3.3V. Lower values indicate a Vin which has dropped below 3.3V typically due to a failing battery.vref
Returns the reference voltage.vbat
Returns the backup battery voltage (if fitted).temperature
Returns the chip temperature in deg C. Note that the chip datasheet points out that the absolute accuracy of this is poor, varies greatly from one chip to another, and is best suited for monitoring changes in temperature. It produces spectacularly poor results if the 3.3V supply drops out of spec.why
No args. Returns the reason for a wakeup event.bkpram_ok
No args. Detection of valid data in backup RAM after a power up event. ReturnsTrue
if RAM has retained data (i.e. it was battery backed during outage).
Items 8-11 avoid the drawbacks of the pyb.ADCAll
class. Instantiating this
turns all available pins into ADC inputs. Further its read_core_vbat()
method returns incorrect results if the 3.3V supply is low for example due to a
failing battery.
The module provides the following classes:
Alarm
Provides access to the two RTC alarms.BkpRAM
Provides access to the backup RAM.RTC_Regs
Provides access to the backup registers.Tamper
Enables wakeup from the Tamper pin X18.wakeup_X1
Enables wakeup from a positive edge on pin X1.
This function accepts one argument: a delay in ms and is a low power replacement
for pyb.delay()
. The function normally uses pyb.stop
to reduce power
consumption from 20mA to 500uA. If USB is connected it reverts to pyb.delay
to avoid killing the USB connection. There is a subtle issue when using this
function: pyb
loses all sense of time when the Pyboard is stopped.
Consequently you can't use functions such as pyb.elapsed_millis
to keep
track of time in a loop. The simplest solution is to use the provided
lp_elapsed_ms
function.
Accepts one argument, a start time in ms from the now
function. Typical code
to implement a one second timeout might be along these lines:
start = upower.now()
while upower.lp_elapsed_ms(start) < 1000:
print(upower.lp_elapsed_ms(start)) # do something
upower.lpdelay(100)
Returns RTC time in milliseconds since the start of year 2000. The function is mainly intended for use in implementing sleep or standby delays which can be resumed after an interrupt from tamper or WKUP. Millisecond precision is meaningless in standby periods where wakeups are slow, but is relevant to sleep. Precision is limited to about 4ms owing to the RTC hardware.
Store current RTC time in backup RAM. Optional argument addr
default 1021.
This uses two words to store the milliseconds value produced by now()
This produces a value of delay for presenting to wakeup()
and enables a
timed sleep or standby to be resumed after a tamper or WKUP interrupt. To use
it, execute savetime
before commencing the sleep/standby. Arguments delta
normally the original delay period in ms, addr
the address where the time
was saved (default 1021). The function can raise an exception in response to a
number of errors such as the case where a time was not saved or the RTC was
adjusted after saving. The defensive coder will trap these!
If the time has expired it will return zero (i.e. it will never return negative values).
The test program ttest.py
illustrates its use.
This enhances machine.reset_cause
by providing more detail on the reason
why the Pyboard has emerged from deep sleep. machine.reset_cause
should
first be called to detect the conditions PWRON_RESET
or HARD_RESET
.
If it returns DEEPSLEEP_RESET
then why()
may be called. It will
return one of the following values:
- 'TAMPER' Woken by the Tamper pin (X18).
- 'WAKEUP' Woken by RTC.wakeup().
- 'ALARM_A' Woken by RTC alarm A.
- 'ALARM_B' Woken by RTC alarm B.
- 'X1' Woken by the WKUP pin (X1).
None
Reason unknown.
The RTC supports two alarms 'A' and 'B' each of which can wake the Pyboard at programmed intervals.
Constructor: an alarm is instantiated with a single mandatory argument, 'A' or
'B'.
Method timeset()
Assuming at least one kw only argument is passed, this will
start the timer and cause periodic interrupts to be generated. In the absence of
arguments the timer will be disabled. Arguments default to None
.
Arguments (kwonly args):
day_of_month
1..31 If present, alarm will occur only on that dayweekday
1 (Monday) - 7 (Sunday) If present, alarm will occur only on that day of the weekhour
0..23minute
0..59second
0..59
Usage examples:
mytimer.timeset(weekday = 1, hour = 17) # Wake at 17:00 every Monday
mytimer.timeset(hour = 5) # Wake at 5am every day
mytimer.timeset(minute = 10, second = 30) # Wake up every hour at 10 mins, 30
secs after the hour
mytimer.timeset(second = 30) # Wake up each time RTC seconds reads 30 i.e. once
per minute
This class enables the on-chip 4KB of battery backed RAM to be accessed as an
array of integers or as a bytearray. The latter facilitates creating persistent
arbitrary objects using JSON or pickle.
Its initial contents after power up are arbitrary unless an RTC backup battery
is used. Note that savetime()
uses two 32 bit words at 1021 and 1022 by
default and startup detection uses 1023 so these top three locations should
normally be avoided.
from upower import BkpRAM
bkpram = BkpRAM()
bkpram[0] = 22 # use as integer array
bkpram.ba[4] = 0 # or as a bytearray
The following code fragment illustrates the use of pickle to save an arbitrary Python object to backup RAM and restore it on a subsequent wakeup. The pickle module is available in the MicroPython library.
import pickle, upower
bkpram = upower.BkpRAM()
a = {'rats':77, 'dogs':99,'elephants':9, 'zoo':100}
z = pickle.dumps(a).encode('utf8')
bkpram[0] = len(z)
bkpram.ba[4: 4+len(z)] = z # Copy into backup RAM
# Resumption after standby
import pickle, upower
bkpram = upower.BkpRAM()
# retrieve dictionary
a = pickle.loads(bytes(bkpram.ba[4:4+bkpram[0]]).decode('utf-8'))
The RTC has a set of 20 32-bit backup registers. These are initialised to zero on boot, and are also cleared down after a Tamper event. Registers may be accessed as follows:
from upower import RTCRegs
rtcregs = RTCRegs()
rtcregs[3] = 42
This is a flexible way to interrupt a standby condition, providing for edge or
level detection, the latter with hardware switch debouncing. Level detection
operates as follows. The pin is normally high impedance. At intervals a pullup
resistor is connected and the pin state sampled. After a given number of such
intervals, if the pin continues to be in the active state, the Pyboard is woken.
The active state, polling frequency and number of samples may be configured
using tamper.setup()
.
Note that in edge triggered mode the pin behaves as a normal input with no pullup. If driving from a switch, you must provide a pullup (to 3V3) or pulldown as appropriate.
In use first instatiate the tamper object:
from upower import Tamper
tamper = Tamper()
The class supports the following methods and properties.
setup()
method accepts the following arguments:
level
Mandatory: valid options 0 or 1. In level triggered mode, determines the active level. In edge triggered mode, 0 indicates rising edge trigger, 1 falling edge. Optional kwonly args:freq
Valid options 1, 2, 4, 8, 16, 32, 64, 128: polling frequency in Hz. Default 16.samples
Valid options 2, 4, 8: number of consecutive samples before wakeup occurs. Default 2.edge
Boolean. If True, the pin is edge triggered.freq
andsamples
are ignored. Default False.
enable()
method enables the tamper interrupt. Call just before issuing
pyb.standby()
and after the use of any other methods as it reconfigures the
pin.
tamper.wait_inactive()
method returns when pin X18 has returned to its
inactive state. In level triggered mode this may be called before issuing the
enable()
method to avoid recurring interrupts. In edge triggered mode where
the signal is from a switch it might be used to debounce the trailing edge of
the contact period.
disable()
method disables the interrupt. Not normally required as the
interrupt is disabled by the constructor.
pinvalue
property returning the value of the signal on the pin: 0 is 0V
regardless of level
.
See ttest.py
for an example of its usage.
Enabling this converts pin X1 into an input with a pulldown resistor enabled
even in standby mode. A low to high transition will wake the Pyboard from
standby. The following code fragment illustrates its use. A complete example is
in ttest.py
.
from upower import wakeup_X1
wkup = wakeup_X1()
# code omitted
wkup.enable()
if not upower.usb_connected:
pyb.standby()
The wakeup_X1
class has the following methods and properties.
enable()
enables the wkup interrupt. Call just before issuing pyb.standby()
and after the use of any other wkup methods as it reconfigures the pin.
wait_inactive()
This method returns when pin X1 has returned low. This might
be used to debounce the trailing edge of the contact period: call lpdelay(50)
after the function returns and before entering standby to ensure that contact
bounce is over.
disable()
disables the interrupt. Not normally required as the interrupt is
disabled by the constructor.
pinvalue
Property returning the value of the signal on the pin: 0 is low, 1
high.
Demonstrates various ways to wake up from standby and how to differentiate between them.
To run this, edit your main.py
to include import ttest
. Power the
Pyboard from a source other than USB. It will flash the red, yellow and green
LEDs after boot and the green and yellow ones every ten seconds in response to a
timer wakeup. If pin X1 is pulled to 3V3 red and green will flash. If pin X18 is
pulled low red will flash. If a backup battery is in use and power to the board
is cycled, power up events subsequent to the first will cause the yellow LED to
flash.
If a UART is initialised for REPL in boot.py the time of each event will be output.
If an RTC backup battery is used and the Pyboard power is removed while a wakeup delay is pending it will behave as follows. If power is re-applied before the delay times out, it will time out at the correct time. If power is applied after the time has passed two wakeups will occur soon after power up: the first caused by the power up, and the second by the deferred wakeup.
Demonstrates the RTC alarms. Runs both alarms concurrently, waking every 30
seconds and flashing LED's to indicate which timer has caused the wakeup. To run
this, edit your main.py
to include import alarm
.
Using USB for the REPL makes this impractical because stop()
and standby()
break the connection. A solution is to redirect the REPL to a UART and use a
terminal application via a USB to serial adaptor. If your code uses standby()
a delay may be necessary prior to the call to ensure sufficient time elapses for
the data to be transmitted before the chip shuts down.
On resumption from standby the Pyboard will execute boot.py
and main.py
,
so unless main.py
restarts your program, you will be returned to the REPL.
Other points to note when debugging code which uses standby mode. If using a
backup battery the RTC will remember settings even if the Pyboard is powered
down. So if you run rtc.wakeup(value)
, power down the board, then power it
up to run another program, it will continue to wake from standby at the interval
specified. Issue rtc.wakeup(None)
if this is not the desired outcome. The
same applies to alarms: to clear down an alarm instantiate it and issue its
timeset()
method with no arguments.
Another potential source of confusion arises if you use rshell
to access the
Pyboard. Helpfully it automatically sets the RTC from the connected computer.
However it can result in unexpected timings if the RTC is adjusted when delays
or alarms are pending.
When coding for minimum power consumption there are various options. One is to
reduce the CPU clock speed: its current draw in normal running mode is roughly
proportional to clock speed. However in computationally intensive tasks the
total charge drawn from a battery may not be reduced since processing time will
double if the clock rate is halved. This is a consequence of the way CMOS logic
works: gates use a fixed amount of charge per transition. If your code spends
time waiting on pyb.delay()
reducing clock rate will help, but if using
upower.lpdelay()
gains may be negligible.