├── current.png ├── hardware.JPG ├── epd_vddonly.fzz ├── simple_schem.jpg ├── epd_vddonly_pcb.jpg ├── epd_vddonly_schem.jpg ├── LICENSE ├── alarm.py ├── README.md ├── micropower.py ├── ds_test.py ├── ttest.py ├── upower.py ├── HARDWARE.md └── UPOWER.md /current.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-micropower/HEAD/current.png -------------------------------------------------------------------------------- /hardware.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-micropower/HEAD/hardware.JPG -------------------------------------------------------------------------------- /epd_vddonly.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-micropower/HEAD/epd_vddonly.fzz -------------------------------------------------------------------------------- /simple_schem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-micropower/HEAD/simple_schem.jpg -------------------------------------------------------------------------------- /epd_vddonly_pcb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-micropower/HEAD/epd_vddonly_pcb.jpg -------------------------------------------------------------------------------- /epd_vddonly_schem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-micropower/HEAD/epd_vddonly_schem.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Peter Hinch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /alarm.py: -------------------------------------------------------------------------------- 1 | # alarm.py Demonstrate using RTC alarms to exit pyb.standby() 2 | 3 | # Copyright Peter Hinch 4 | # V0.4 10th October 2016 Now uses machine.reset_cause() 5 | # Flashes leds at 30 second intervals courtesy of two concurrent timers 6 | # (each times out at one minute intervals). 7 | # Note that the Pyboard flashes the green LED briefly on waking from standby. 8 | import stm, pyb, upower, machine 9 | 10 | red, green, yellow = (pyb.LED(x) for x in range(1, 4)) # LED(3) is blue, not yellow, on D series 11 | rtc = pyb.RTC() 12 | rtc.wakeup(None) # If we have a backup battery clear down any setting from a previously running program 13 | reason = machine.reset_cause() # Why have we woken? 14 | if reason == machine.PWRON_RESET or reason == machine.HARD_RESET: # first boot 15 | rtc.datetime((2020, 8, 6, 4, 13, 0, 0, 0)) # Code to run on 1st boot only 16 | aa = upower.Alarm('a') 17 | aa.timeset(second = 39) 18 | ab = upower.Alarm('b') 19 | ab.timeset(second = 9) 20 | red.on() 21 | elif reason == machine.DEEPSLEEP_RESET: 22 | reason = upower.why() 23 | if reason == 'ALARM_A': 24 | green.on() 25 | elif reason == 'ALARM_B': 26 | yellow.on() 27 | 28 | upower.lpdelay(1000) # Let LED's be seen! 29 | pyb.standby() 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reducing Pyboard system power consumption 2 | 3 | This repository is specific to STM systems. It primarily supports Pyboard 1.x 4 | and Pyboard D. An alternative micropower architecture is to use ESPNow with 5 | ESP8266 or ESP32. [This document](https://github.com/peterhinch/micropython-mqtt/tree/master/mqtt_as/esp32_gateway) 6 | describes an MQTT client suitable for long term battery operation. 7 | 8 | The Pyboard can be used to build systems with extremely low power consumption, 9 | and it is possible to build systems capable of running for over a year on 10 | standard batteries such as AA cells or even coin cells. With the Pyboard 1.x 11 | series, achieving the lowest possible power consumption where peripheral 12 | devices are attached requires a little additional hardware. The Pyboard D 13 | overcomes this requirement by virtue of a software switchable 3.3V regulator 14 | which also powers the I2C pullups. 15 | 16 | Broadly there are two approaches, depending on the acceptable level of power 17 | consumption. If an idle current in the range of 500uA to 1mA is acceptable, 18 | code can be largely conventional with `pyb.stop()` being employed to reduce 19 | power consumption during periods when the Pyboard is idle. 20 | 21 | For the lowest possible power consumption `pyb.standby()` must be used, cutting 22 | consumption to around 6μA. With external hardware on the Pyboard 1.x or via the 23 | switchable regulator on the D series the peripherals may be turned off ensuring 24 | that the entire system uses only this amount. The drawback of `standby` is that 25 | on waking the Pyboard restarts as if from a reboot. This introduces potential 26 | difficulties regarding determining the source of the wakeup and the storage of 27 | program state while sleeping. 28 | 29 | Two sets of resources are provided to assist with the development of low power 30 | solutions. 31 | 32 | ### Hardware 33 | 34 | [hardware](./HARDWARE.md) A discussion of techniques to minimise power 35 | consumption including ways to shut down attached devices, calculation of 36 | battery usage and measurements of results. A schematic for a typical system is 37 | provided with accompanying PCB layout. 38 | 39 | ### Software 40 | 41 | [upower](./UPOWER.md) This documents `upower.py`, a module providing access to 42 | features of the Pyboard SOC which are currently unsupported in the official 43 | firmware. Some of these features may be of wider use, such as using the battery 44 | backed RAM to store arbitrary Python objects and accessing the RTC registers. 45 | 46 | All code is issued under the [MIT license](./LICENSE) 47 | -------------------------------------------------------------------------------- /micropower.py: -------------------------------------------------------------------------------- 1 | # micropower.py Support for hardware capable of switching off the power 2 | # for Pyboard peripherals 3 | # 28th Aug 2015 4 | # This code is released under the MIT licence 5 | # version 0.45 6 | 7 | # Copyright 2015 Peter Hinch 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License") 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at: 12 | # 13 | # http:#www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, 16 | # software distributed under the License is distributed on an 17 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 18 | # express or implied. See the License for the specific language 19 | # governing permissions and limitations under the License. 20 | import pyb 21 | 22 | class PowerController(object): 23 | def __init__(self, pin_active_high, pin_active_low): 24 | self.upcount = 0 25 | if pin_active_low is not None: # Start with power down 26 | self.al = pyb.Pin(pin_active_low, mode=pyb.Pin.OUT_PP) 27 | self.al.high() 28 | else: 29 | self.al = None 30 | if pin_active_high is not None: # and pullups disabled 31 | self.ah = pyb.Pin(pin_active_high, mode=pyb.Pin.OUT_PP) 32 | self.ah.low() 33 | else: 34 | self.ah = None 35 | 36 | def __enter__(self): # use as context manager 37 | self.power_up() 38 | return self 39 | 40 | def __exit__(self, *_): 41 | self.power_down() 42 | 43 | def power_up(self): 44 | self.upcount += 1 # Cope with nested calls 45 | if self.upcount == 1: 46 | if self.ah is not None: 47 | self.ah.high() # Enable I2C pullups 48 | if self.al is not None: 49 | self.al.low() # Power up 50 | pyb.delay(10) # time for device to settle 51 | 52 | def power_down(self): 53 | if self.upcount > 1: 54 | self.upcount -= 1 55 | elif self.upcount == 1: 56 | self.upcount = 0 57 | if self.al is not None: 58 | self.al.high() # Power off 59 | pyb.delay(10) # Avoid glitches on switched 60 | if self.ah is not None: # I2C bus while power decays 61 | self.ah.low() # Disable I2C pullups 62 | for bus in (pyb.SPI(1), pyb.SPI(2), pyb.I2C(1), pyb.I2C(2)): 63 | bus.deinit() # I2C drivers seem to need this 64 | 65 | @property 66 | def single_ended(self): 67 | return (self.ah is not None) and (self.al is not None) 68 | 69 | -------------------------------------------------------------------------------- /ds_test.py: -------------------------------------------------------------------------------- 1 | # ds_test.py Demonstrate ways to exit from pyb.standby(): Pyboard D specific 2 | 3 | # Copyright 2020 Peter Hinch 4 | # This code is released under the MIT licence 5 | 6 | # V0.43 Oct 2020 7 | # Pyboard firmware briefly flashes the green LED on all wakeups. 8 | 9 | import pyb 10 | import upower 11 | import machine 12 | rtc = pyb.RTC() 13 | leds = tuple(pyb.LED(x) for x in range(1, 4)) # rgb 14 | # Light LEDs, extinguishing any not listed 15 | def light(*these): 16 | any(leds[x].on() if x in these else leds[x].off() for x in range(3)) 17 | 18 | # Falling edge: need a pullup to a permanently available supply (not the switched 3V3) 19 | # Rising edge: pull down to Gnd. 20 | # Wakeup pins are 5V tolerant. 21 | # pin_names: list of any of 'X1', 'X3' 'C1', 'C13' 22 | def test(*pin_names, rising=True): 23 | wups = [upower.WakeupPin(pyb.Pin(name), rising=rising) for name in pin_names] 24 | reason = machine.reset_cause() # Why have we woken? 25 | if reason in (machine.PWRON_RESET, machine.HARD_RESET, machine.SOFT_RESET): # first boot 26 | rtc.datetime((2020, 8, 6, 4, 13, 0, 0, 0)) # Code to run on 1st boot only 27 | upower.savetime() 28 | if upower.bkpram_ok(): # backup RAM holds valid data: battery backed 29 | light(2) # Blue only 30 | else: # Boot with no backup battery, data is garbage 31 | light(0, 1, 2) # White 32 | elif reason == machine.DEEPSLEEP_RESET: 33 | reason = upower.why() 34 | if reason == 'WAKEUP': 35 | light(0, 1, 2) # White on timer wakeup 36 | upower.savetime() 37 | elif reason == 'X3': # red 38 | light(0) 39 | elif reason == 'X1': # green 40 | light(1) 41 | elif reason == 'C1': 42 | light(2) # Blue 43 | elif reason == 'C13': 44 | light(0, 1) # Red and green 45 | else: 46 | upower.cprint('Unknown: reset?') # Prints if a UART is configured 47 | 48 | upower.lpdelay(500) # ensure LEDs visible before standby turns it off 49 | while any((p.state() for p in wups)): 50 | upower.lpdelay(50) # Wait for wakeup signals to go away 51 | upower.lpdelay(50) # Wait out any contact bounce 52 | 53 | # demo of not resetting the wakeup timer after a pin interrupt 54 | try: # ms_left can fail in response to various coding errors 55 | timeleft = upower.ms_left(10000) 56 | except upower.RTCError: 57 | timeleft = 10000 # Coding error: uninitialised - just restart the timer 58 | # Set a minimum sleep duration: too short and it uses less power to stay awake 59 | # In real apps this might be longer or you might deal with it in other ways. 60 | timeleft = max(timeleft, 1000) 61 | rtc.wakeup(timeleft) 62 | # These calls reconfigure hardware and should be done last, shortly before standby() 63 | for p in wups: 64 | p.enable() 65 | if not upower.usb_connected: 66 | pyb.standby() # Set pins hi-z and turn LEDs off 67 | else: 68 | light(1) # Green LED: debugging session. 69 | -------------------------------------------------------------------------------- /ttest.py: -------------------------------------------------------------------------------- 1 | # ttest.py Demonstrate ways to exit from pyb.standby() 2 | 3 | # Copyright 2016-2020 Peter Hinch 4 | # This code is released under the MIT licence 5 | 6 | # V0.43 Oct 2020 Use new version of upower.py. Tested on Pyboard D and 1.1. 7 | # V0.4 10th October 2016 Now uses machine.reset_cause() 8 | 9 | # draws 32uA between LED flashes runing from flash. 10 | # Doesn't do much running from USB because low power modes would kill the USB interface. 11 | # Pyboard 1.x: 12 | # the blue LED is best avoided in these applications as it currently uses PWM: this can 13 | # lead to some odd effects in time dependent applications. 14 | # Pyboard D: 15 | # LED[3] described as yellow is actually blue. 16 | # You must supply a pull-down for the X1 pin. 17 | # Currently a special firmware build is required for X1 to work. 18 | # All boards: 19 | # Pyboard firmware briefly flashes the green LED on all wakeups. 20 | 21 | import pyb 22 | import upower 23 | import machine 24 | tamper = upower.Tamper() 25 | wkup = upower.WakeupPin( 26 | pyb.Pin('X1')) if upower.d_series else upower.wakeup_X1() 27 | # A level of zero triggers 28 | tamper.setup(level=0, freq=16, samples=2, edge=False) 29 | # upower.tamper.setup(level = 1, edge = True) # Falling edge trigger. You must supply a pullup resistor. 30 | rtc = pyb.RTC() 31 | 32 | leds = tuple(pyb.LED(x) for x in range(1, 4)) # rgy on V1.x, rgb on D series 33 | for led in leds: 34 | led.off() 35 | 36 | reason = machine.reset_cause() # Why have we woken? 37 | if reason in (machine.PWRON_RESET, machine.HARD_RESET, machine.SOFT_RESET): # first boot 38 | rtc.datetime((2020, 8, 6, 4, 13, 0, 0, 0)) # Code to run on 1st boot only 39 | upower.savetime() 40 | if upower.bkpram_ok(): # backup RAM holds valid data 41 | leds[2].on() # Y only 42 | else: # No backup battery, data is garbage 43 | for led in leds: 44 | led.on() # RGY == 1st boot 45 | elif reason == machine.DEEPSLEEP_RESET: 46 | reason = upower.why() 47 | if reason == 'WAKEUP': # green and yellow on timer wakeup 48 | leds[1].on() 49 | leds[2].on() 50 | upower.savetime() 51 | elif reason == 'TAMPER': # red 52 | leds[0].on() 53 | elif reason == 'X1': # red and green on X1 rising edge 54 | leds[0].on() 55 | leds[1].on() 56 | else: 57 | # Prints if a UART is configured 58 | upower.cprint('Unknown: reset?') 59 | 60 | t = rtc.datetime()[4:7] 61 | upower.cprint('{:02d}.{:02d}.{:02d}'.format(t[0], t[1], t[2])) 62 | 63 | # ensure LED visible before standby turns it off 64 | upower.lpdelay(500) 65 | 66 | # Wait for tamper signal to go away 67 | tamper.wait_inactive() 68 | wkup.wait_inactive() 69 | upower.lpdelay(50) # Wait out any contact bounce 70 | # demo of not resetting the wakeup timer after a pin interrupt 71 | try: # ms_left can fail in response to various coding errors 72 | timeleft = upower.ms_left(10000) 73 | except upower.RTCError: 74 | # Coding error: uninitialised - just restart the timer 75 | timeleft = 10000 76 | # Set a minimum sleep duration: too short and it uses less power to stay awake 77 | timeleft = max(timeleft, 1000) 78 | # In real apps this might be longer or you might deal with it in other ways. 79 | rtc.wakeup(timeleft) 80 | # These calls reconfigure hardware and should be done last, shortly before standby() 81 | tamper.enable() 82 | wkup.enable() 83 | if not upower.usb_connected: 84 | # This will set pins hi-z and turn LEDs off 85 | pyb.standby() 86 | else: 87 | for led in leds: 88 | led.off() 89 | leds[1].on() # Green LED: debugging session. 90 | -------------------------------------------------------------------------------- /upower.py: -------------------------------------------------------------------------------- 1 | # upower.py Enables access to functions useful in low power Pyboard projects 2 | # Copyright 2016-2020 Peter Hinch 3 | # This code is released under the MIT licence 4 | 5 | # V0.43 Sep 2020 Further Pyboard D fixes. 6 | # V0.42 15th June 2020 Fix Tamper for Pyboard D. Ref 7 | # https://forum.micropython.org/viewtopic.php?f=20&t=8518&p=48337 8 | 9 | 10 | # http://www.st.com/web/en/resource/technical/document/application_note/DM00025071.pdf 11 | import pyb, stm, os, utime, uctypes, machine 12 | 13 | # CODE RUNS ON IMPORT **** START **** 14 | 15 | d_series = os.uname().machine.split(' ')[0][:4] == 'PYBD' 16 | 17 | # https://forum.micropython.org/viewtopic.php?f=20&t=6222&p=35497 18 | usb_connected = False 19 | if pyb.usb_mode() is not None: # User has enabled VCP in boot.py 20 | if d_series: 21 | # Detect an active debugging session 22 | usb_connected = pyb.USB_VCP().isconnected() 23 | else: 24 | usb_connected = pyb.Pin.board.USB_VBUS.value() == 1 25 | if not usb_connected: 26 | pyb.usb_mode(None) # Save power 27 | 28 | # CODE RUNS ON IMPORT **** END **** 29 | 30 | def bounds(val, minval, maxval, msg): # Bounds check 31 | if not (val >= minval and val <= maxval): 32 | raise ValueError(msg) 33 | 34 | def singleton(cls): 35 | instances = {} 36 | def getinstance(): 37 | if cls not in instances: 38 | instances[cls] = cls() 39 | return instances[cls] 40 | return getinstance 41 | 42 | class RTCError(OSError): 43 | pass 44 | 45 | def cprint(*args, **kwargs): # Conditional print: USB fails if low power modes are used 46 | global usb_connected 47 | if not usb_connected: # assume a UART has been specified in boot.py 48 | print(*args, **kwargs) 49 | 50 | # Count the trailing zeros in an integer 51 | def ctz(n): 52 | if not n: 53 | return 32 54 | count = 0 55 | while not n & 1: 56 | n >>= 1 57 | count += 1 58 | return count 59 | 60 | # ***** BACKUP RAM SUPPORT ***** 61 | 62 | @singleton 63 | class BkpRAM: 64 | 65 | BKPSRAM = 0x40024000 66 | def __init__(self): 67 | stm.mem32[stm.RCC + stm.RCC_APB1ENR] |= 0x10000000 # PWREN bit 68 | if d_series: 69 | stm.mem32[stm.PWR + stm.PWR_CR1] |= 0x100 # Set the DBP bit in the PWR power control register 70 | stm.mem32[stm.RCC + stm.RCC_AHB1ENR] |= 0x40000 # enable BKPSRAMEN 71 | stm.mem32[stm.PWR + stm.PWR_CSR1] |= 0x200 # BRE backup regulator enable bit 72 | else: 73 | stm.mem32[stm.PWR + stm.PWR_CR] |= 0x100 # Set the DBP bit in the PWR power control register 74 | stm.mem32[stm.RCC + stm.RCC_AHB1ENR] |= 0x40000 # enable BKPSRAMEN 75 | stm.mem32[stm.PWR + stm.PWR_CSR] |= 0x200 # BRE backup regulator enable bit 76 | self._ba = uctypes.bytearray_at(self.BKPSRAM, 4096) 77 | def idxcheck(self, idx): 78 | bounds(idx, 0, 0x3ff, 'RTC backup RAM index out of range') 79 | def __getitem__(self, idx): 80 | self.idxcheck(idx) 81 | return stm.mem32[self.BKPSRAM + idx * 4] 82 | def __setitem__(self, idx, val): 83 | self.idxcheck(idx) 84 | stm.mem32[self.BKPSRAM + idx * 4] = val 85 | @property 86 | def ba(self): 87 | return self._ba # Access as bytearray 88 | 89 | # ***** RTC REGISTERS ***** 90 | 91 | @singleton 92 | class RTCRegs: 93 | 94 | def idxcheck(self, idx): 95 | bounds(idx, 0, 19, 'RTC register index out of range') 96 | def __getitem__(self, idx): 97 | self.idxcheck(idx) 98 | return stm.mem32[stm.RTC + stm.RTC_BKP0R+ idx * 4] 99 | def __setitem__(self, idx, val): 100 | self.idxcheck(idx) 101 | stm.mem32[stm.RTC + stm.RTC_BKP0R + idx * 4] = val 102 | 103 | 104 | # ***** LOW POWER pyb.delay() ALTERNATIVE ***** 105 | # Low power delay. Note stop() kills USB. 106 | # For the duratiom it stops the time source used by utime. 107 | def lpdelay(ms): 108 | global usb_connected 109 | rtc = pyb.RTC() 110 | if usb_connected: 111 | pyb.delay(ms) 112 | return 113 | rtc.wakeup(ms) 114 | pyb.stop() 115 | rtc.wakeup(None) 116 | 117 | # ***** TAMPER (X18) PIN SUPPORT ***** 118 | 119 | # Changes for Pyboard D ref https://forum.micropython.org/viewtopic.php?f=20&t=8518 120 | @singleton 121 | class Tamper: 122 | 123 | def __init__(self): 124 | self.edge_triggered = False 125 | self.triggerlevel = 0 126 | self.tampmask = 0 127 | self.disable() # Ensure no events occur until we're ready 128 | self.pin = pyb.Pin.cpu.C13 # X18 doesn't exist on Pyboard D 129 | self.pin_configured = False # Conserve power: enable pullup only if needed 130 | self.setup() 131 | 132 | def setup(self, level=0, *, freq=16, samples=2, edge=False): 133 | self.tampmask = 0 134 | if level == 1: 135 | self.tampmask |= 2 | (1 << 15) # Disable pullup and precharge 136 | self.triggerlevel = 1 137 | elif level == 0: 138 | self.triggerlevel = 0 139 | else: 140 | raise ValueError("level must be 0 or 1") 141 | if d_series: 142 | self.tampmask |= (1 << 17) # Disable erasure of backup registers 143 | 144 | if type(edge) == bool: 145 | self.edge_triggered = edge 146 | else: 147 | raise ValueError("edge must be True or False") 148 | if not self.edge_triggered: 149 | if freq in (1,2,4,8,16,32,64,128): 150 | self.tampmask |= ctz(freq) << 8 151 | else: 152 | raise ValueError("Frequency must be 1, 2, 4, 8, 16, 32, 64 or 128Hz") 153 | if samples in (2, 4, 8): 154 | self.tampmask |= ctz(samples) << 11 155 | else: 156 | raise ValueError("Number of samples must be 2, 4, or 8") 157 | 158 | def _pinconfig(self): 159 | if not self.pin_configured: 160 | if self.triggerlevel: 161 | self.pin.init(mode = pyb.Pin.IN, pull = pyb.Pin.PULL_DOWN) 162 | else: 163 | self.pin.init(mode = pyb.Pin.IN, pull = pyb.Pin.PULL_UP) 164 | self.pin_configured = True 165 | 166 | def disable(self): 167 | if d_series: 168 | stm.mem32[stm.RTC + stm.RTC_TAMPCR] = self.tampmask 169 | else: 170 | stm.mem32[stm.RTC + stm.RTC_TAFCR] = self.tampmask 171 | 172 | def wait_inactive(self): 173 | self._pinconfig() 174 | while self.pin.value() == self.triggerlevel: # Wait for pin to go logically off 175 | lpdelay(50) 176 | 177 | @property 178 | def pinvalue(self): 179 | self._pinconfig() 180 | return self.pin.value() 181 | 182 | def enable(self): 183 | BIT21 = 1 << 21 # Tamper mask bit 184 | self.disable() 185 | stm.mem32[stm.EXTI + stm.EXTI_IMR] |= BIT21 # Set up ext interrupt 186 | stm.mem32[stm.EXTI + stm.EXTI_RTSR] |= BIT21 # Rising edge 187 | stm.mem32[stm.EXTI + stm.EXTI_PR] |= BIT21 # Clear pending bit 188 | 189 | stm.mem32[stm.RTC + stm.RTC_ISR] &= 0xdfff # Clear tamp1f flag 190 | if d_series: 191 | stm.mem32[stm.PWR + stm.PWR_CR2] |= 0x3f # Clear power wakeup flag WUF 192 | stm.mem32[stm.RTC + stm.RTC_TAMPCR] = self.tampmask | 5 # Tamper interrupt enable and tamper1 enable 193 | else: 194 | stm.mem32[stm.PWR + stm.PWR_CR] |= 4 # Clear power wakeup flag WUF 195 | stm.mem32[stm.RTC + stm.RTC_TAFCR] = self.tampmask | 5 # Tamper interrupt enable and tamper1 enable 196 | 197 | # ***** WKUP PIN (X1) SUPPORT (V1.x) ***** 198 | 199 | @singleton 200 | class wakeup_X1: # Support wakeup on low-high edge on pin X1 201 | 202 | def __init__(self): 203 | if d_series: 204 | raise ValueError('Not supported by D series.') 205 | self.disable() 206 | self.pin = pyb.Pin.board.X1 # Don't configure pin unless user accesses wkup 207 | # On the Espruino Pico change X1 to A0 (issue #1) 208 | self.pin_configured = False 209 | 210 | def _pinconfig(self): 211 | if not self.pin_configured: 212 | self.pin.init(mode = pyb.Pin.IN, pull = pyb.Pin.PULL_DOWN) 213 | self.pin_configured = True 214 | 215 | def enable(self): # In this mode pin has pulldown enabled 216 | stm.mem32[stm.PWR + stm.PWR_CR] |= 4 # set CWUF to clear WUF in PWR_CSR 217 | stm.mem32[stm.PWR + stm.PWR_CSR] |= 0x100 # Enable wakeup 218 | 219 | def disable(self): 220 | stm.mem32[stm.PWR + stm.PWR_CSR] &= 0xfffffeff # Disable wakeup 221 | 222 | def wait_inactive(self): 223 | self._pinconfig() 224 | while self.pin.value() == 1: # Wait for pin to go low 225 | lpdelay(50) 226 | 227 | @property 228 | def pinvalue(self): 229 | self._pinconfig() 230 | return self.pin.value() 231 | 232 | # ***** PYBOARD D WKUP PIN SUPPORT ***** 233 | 234 | # Support wakeup on pin A0, A2, C1 or C13 235 | # Caller passes a Pin object. Pullup configuration does not work: if a switch is used 236 | # an external pull up or down is required. 237 | 238 | class WakeupPin: 239 | 240 | def __init__(self, pin, rising=True): 241 | if not d_series: 242 | raise ValueError('Only valid on Pyboard D') 243 | # Raise ValueError on invalid pin 244 | self.idx = ('A0', 'A2', 'C1', 'C13').index(pin.name()) 245 | self.disable() 246 | self.pin = pin 247 | self.rising = rising 248 | 249 | def enable(self): 250 | cr2 = 0x3f 251 | if not self.rising: 252 | cr2 |= (0x100 << self.idx) # Set WUPP bit if falling edge 253 | stm.mem32[stm.PWR + stm.PWR_CR2] |= cr2 # Clear all power wakeup flags, set WUPP for current pin 254 | stm.mem32[stm.PWR + stm.PWR_CSR2] |= (0x100 << self.idx) # Enable current pin wakeup 255 | 256 | def disable(self): 257 | stm.mem32[stm.PWR + stm.PWR_CSR2] &= ~(0x100 << self.idx) # Disable wakeup 258 | 259 | def wait_inactive(self): 260 | while self.pin.value() == self.rising: # Wait for pin to go inactive 261 | lpdelay(50) 262 | 263 | def pinvalue(self): 264 | return self.pin.value() 265 | 266 | def state(self): 267 | return self.pin.value() == self.rising 268 | 269 | # ***** RTC TIMER SUPPORT ***** 270 | 271 | def bcd(x): # integer to BCD (2 digit max) 272 | return (x % 10) + ((x//10) << 4) 273 | 274 | class Alarm: 275 | 276 | instantiated = False 277 | def __init__(self, ident): 278 | if not ident in ('a','A','b','B'): 279 | raise ValueError("Alarm iIdent must be 'A' or 'B'") 280 | self.ident = ident.lower() 281 | if self.ident == 'a': 282 | self.alclear = 0xffeeff 283 | self.alenable = 0x1100 284 | self.alreg = stm.RTC_ALRMAR 285 | self.alisr = 0x1feff 286 | self.albit = 1 287 | else: 288 | self.alclear = 0xffddff 289 | self.alenable = 0x2200 290 | self.alreg = stm.RTC_ALRMBR 291 | self.alisr = 0x1fdff 292 | self.albit = 2 293 | self.uval = 0 294 | self.lval = 0 295 | if not Alarm.instantiated: 296 | BIT17 = 1 << 17 297 | Alarm.instantiated = True 298 | stm.mem32[stm.EXTI + stm.EXTI_IMR] |= BIT17 # Set up ext interrupt 299 | stm.mem32[stm.EXTI + stm.EXTI_RTSR] |= BIT17 # Rising edge 300 | stm.mem32[stm.EXTI + stm.EXTI_PR] |= BIT17 # Clear pending bit 301 | 302 | def timeset(self, *, day_of_month = None, weekday = None, hour = None, minute = None, second = None): 303 | self.uval = 0x8080 # Mask everything off 304 | self.lval = 0x8080 305 | setlower = False 306 | if day_of_month is not None: 307 | bounds(day_of_month, 1 , 31, "Day of month must be between 1 and 31") 308 | self.uval &= 0x7fff # Unmask day 309 | self.uval |= (bcd(day_of_month) << 8) 310 | setlower = True 311 | elif weekday is not None: 312 | bounds(weekday, 1, 7, "Weekday must be from 1 (Monday) to 7") 313 | self.uval &= 0x7fff # Unmask day 314 | self.uval |= 0x4000 # Indicate day of week 315 | self.uval |= (weekday << 8) 316 | setlower = True 317 | if hour is not None: 318 | bounds(hour, 0, 23, "Hour must be 0 to 23") 319 | self.uval &= 0xff3f # Unmask hour, force 24 hour format 320 | self.uval |= bcd(hour) 321 | setlower = True 322 | elif setlower: 323 | self.uval &= 0xff3f # Unmask hour, force 24 hour format 324 | if minute is not None: 325 | bounds(minute, 0, 59, "Minute must be 0 to 59") 326 | self.lval &= 0x7fff # Unmask minute 327 | self.lval |= (bcd(minute) << 8) 328 | setlower = True 329 | elif setlower: 330 | self.lval &= 0x7fff # Unmask minute 331 | if second is not None: 332 | bounds(second, 0, 59, "Second must be 0 to 59") 333 | self.lval &= 0xff7f # Unmask second 334 | self.lval |= bcd(second) 335 | elif setlower: 336 | self.lval &= 0xff7f # Unmask second 337 | stm.mem32[stm.RTC + stm.RTC_WPR] |= 0xCA # enable write 338 | stm.mem32[stm.RTC + stm.RTC_WPR] |= 0x53 339 | stm.mem32[stm.RTC + stm.RTC_CR] &= self.alclear # Clear ALRxE in RTC_CR to disable Alarm 340 | if self.uval == 0x8080 and self.lval == 0x8080: # No alarm set: disable 341 | stm.mem32[stm.RTC + stm.RTC_WPR] = 0xff # Write protect 342 | return 343 | pyb.delay(5) 344 | if stm.mem32[stm.RTC + stm.RTC_ISR] & self.albit : # test ALRxWF IN RTC_ISR 345 | stm.mem32[stm.RTC + self.alreg] = self.lval + (self.uval << 16) 346 | stm.mem32[stm.RTC + stm.RTC_ISR] &= self.alisr # Clear the RTC alarm ALRxF flag 347 | if d_series: 348 | stm.mem32[stm.PWR + stm.PWR_CR2] |= 0x3f # Clear power wakeup flag WUF 349 | else: 350 | stm.mem32[stm.PWR + stm.PWR_CR] |= 4 # Clear the PWR Wakeup (WUF) flag 351 | stm.mem32[stm.RTC+stm.RTC_CR] |= self.alenable # Enable the RTC alarm and interrupt 352 | stm.mem32[stm.RTC + stm.RTC_WPR] = 0xff 353 | else: 354 | raise OSError("Can't access alarm " + self.ident) 355 | 356 | # Return the reason for a wakeup event. 357 | # machine.reset_cause() should be used initially, see UPOWER.md 358 | def why(): 359 | result = None 360 | rtc_isr = stm.mem32[stm.RTC + stm.RTC_ISR] 361 | if rtc_isr & 0x2000: 362 | result = 'TAMPER' 363 | elif rtc_isr & 0x400: 364 | result = 'WAKEUP' 365 | elif rtc_isr & 0x200: 366 | stm.mem32[stm.RTC + stm.RTC_ISR] |= 0x200 367 | result = 'ALARM_B' 368 | elif rtc_isr & 0x100 : 369 | stm.mem32[stm.RTC + stm.RTC_ISR] |= 0x100 370 | result = 'ALARM_A' 371 | else: 372 | if d_series: 373 | r = stm.mem32[stm.PWR + stm.PWR_CSR2] & 0xf 374 | if r > 0: 375 | result = ('X1', 'X3', 'C1', 'C13')[ctz(r)] 376 | else: 377 | if stm.mem32[stm.PWR + stm.PWR_CSR] & 1: # WUF set: the only remaining cause is X1 (?) 378 | result = 'X1' # if WUF not set, cause unknown, return None 379 | if d_series: 380 | stm.mem32[stm.PWR + stm.PWR_CR2] |= 0x3f # Clear power wakeup flag WUF 381 | else: 382 | stm.mem32[stm.PWR + stm.PWR_CR] |= 4 # Clear the PWR Wakeup (WUF) flag 383 | return result 384 | 385 | def bkpram_ok(): 386 | bkpram = BkpRAM() 387 | if bkpram[1023] == 0x27288a6f: # backup RAM has been used before 388 | return True 389 | else: 390 | bkpram[1023] = 0x27288a6f 391 | return False 392 | 393 | # Return the current time from the RTC in millisecs from year 2000 394 | def now(): 395 | rtc = pyb.RTC() 396 | secs = utime.time() 397 | if d_series: 398 | ms = rtc.datetime()[7] // 1000 399 | else: 400 | ms = 1000*(255 -rtc.datetime()[7]) >> 8 401 | if ms < 50: # Might have just rolled over 402 | secs = utime.time() 403 | return ms + 1000 * secs 404 | 405 | # An elapsed_ms function which works during lpdelays 406 | def lp_elapsed_ms(tstart): 407 | return now() - tstart 408 | 409 | # Save the current time in mS 410 | def savetime(addr = 1021): 411 | bkpram = BkpRAM() 412 | bkpram[addr], bkpram[addr +1] = divmod(now(), 1000) 413 | 414 | # Return the number of mS outstanding from a delay of delta mS 415 | def ms_left(delta, addr = 1021): 416 | bkpram = BkpRAM() 417 | if not (bkpram[addr +1] <= 1000 and bkpram[addr +1] >= 0): 418 | raise RTCError("Time data not saved.") 419 | start_ms = 1000 * bkpram[addr] + bkpram[addr +1] 420 | now_ms = now() 421 | result = max(start_ms + delta - now_ms, 0) # avoid -ve results where time was missed (e.g. power outage) 422 | if result > delta: 423 | raise RTCError("Invalid saved time data.") 424 | return result 425 | 426 | def adcread(chan): # 16 temp 17 vbat 18 vref 427 | bounds(chan, 16, 18, 'Invalid ADC channel') 428 | start = pyb.millis() 429 | timeout = 100 430 | stm.mem32[stm.RCC + stm.RCC_APB2ENR] |= 0x100 # enable ADC1 clock.0x4100 431 | stm.mem32[stm.ADC1 + stm.ADC_CR2] = 1 # Turn on ADC 432 | stm.mem32[stm.ADC1 + stm.ADC_CR1] = 0 # 12 bit 433 | if chan == 17: 434 | stm.mem32[stm.ADC1 + stm.ADC_SMPR1] = 0x200000 # 15 cycles channel 17 435 | stm.mem32[stm.ADC + 4] = 1 << 23 436 | elif chan == 18: 437 | stm.mem32[stm.ADC1 + stm.ADC_SMPR1] = 0x1000000 # 15 cycles channel 18 0x1200000 438 | stm.mem32[stm.ADC + 4] = 0xc00000 439 | else: 440 | stm.mem32[stm.ADC1 + stm.ADC_SMPR1] = 0x40000 # 15 cycles channel 16 441 | stm.mem32[stm.ADC + 4] = 1 << 23 442 | stm.mem32[stm.ADC1 + stm.ADC_SQR3] = chan 443 | stm.mem32[stm.ADC1 + stm.ADC_CR2] = 1 | (1 << 30) | (1 << 10) # start conversion 444 | while not stm.mem32[stm.ADC1 + stm.ADC_SR] & 2: # wait for EOC 445 | if pyb.elapsed_millis(start) > timeout: 446 | raise OSError('ADC timout') 447 | data = stm.mem32[stm.ADC1 + stm.ADC_DR] # clears down EOC 448 | stm.mem32[stm.ADC1 + stm.ADC_CR2] = 0 # Turn off ADC 449 | return data 450 | 451 | def v33(): 452 | return 4096 * 1.21 / adcread(17) 453 | 454 | def vbat(): 455 | return 1.21 * 2 * adcread(18) / adcread(17) # 2:1 divider on Vbat channel 456 | 457 | def vref(): 458 | return 3.3 * adcread(17) / 4096 459 | 460 | def temperature(): 461 | return 25 + 400 * (3.3 * adcread(16) / 4096 - 0.76) 462 | 463 | # ********** TEST CODE ********** 464 | 465 | def ms_set(): # For debug purposes only. Decodes outcome of setting rtc.wakeup(). 466 | dividers = (16, 8, 4, 2) 467 | wucksel = stm.mem32[stm.RTC + stm.RTC_CR] & 7 468 | div = dividers[wucksel & 3] 469 | wut = stm.mem32[stm.RTC + stm.RTC_WUTR] & 0xffff 470 | clock_period = div/32768 if wucksel < 4 else 1.0 # seconds 471 | period = clock_period * wut if wucksel < 6 else clock_period * (wut + 0x10000) 472 | return 1000 * period 473 | -------------------------------------------------------------------------------- /HARDWARE.md: -------------------------------------------------------------------------------- 1 | # Minimising Pyboard system power consumption 2 | 3 | This document covers Pyboard hardware V1.0, V1.1 and D series. Users of V1.0 4 | Pyboards should note that the standby current of this version is approximately 5 | 30μA as against 6μA for later versions. 6 | 7 | See also [upower](./UPOWER.md) for a Python module providing access to SOC 8 | resources of use in low power systems. 9 | 10 | All code is issued under the [MIT license](./LICENSE) 11 | 12 | # Abstract 13 | 14 | These notes describe some issues involved in minimising power draw in Pyboard 15 | based systems. In the case of Pyboard 1.x external circuitry can reduce 16 | consumption when the Pyboard is used with external chips or modules. A circuit 17 | design and PCB layout are offered for achieving this; it was specifically 18 | designed for an e-paper display and the NRF24L01 radio, but it could readily 19 | be used with other devices. Some calculations are presented suggesting limits 20 | to the runtimes that might be achieved from various types of batteries. 21 | 22 | The power overheads of the Pyboard are discussed and measurements presented. 23 | These overheads comprise the standby power consumption and the charge required 24 | to recover from standby and to load and compile typical application code. 25 | 26 | # The Pyboard D series 27 | 28 | The Pyboard D has an improved design, so external circuitry is not usually 29 | required. This is because the D series has a 3.3V regulator which is software 30 | switchable and may be used to control the power to peripherals and I2C 31 | pullups. It is turned on by 32 | ```python 33 | machine.Pin.board.EN_3V3.value(1) # 0 to turn off 34 | ``` 35 | In standby mode this regulator is off. It therefore cannot be used to power 36 | pullups on the wakeup pins. 37 | 38 | The D series has internal I2C pullups connected to this switched 3.3V supply 39 | for I2C(1). I2C(2) has no pullups: these must be supplied externally. 40 | 41 | The parts of this document detailing external circuitry and the module 42 | `micropower.py` may be ignored for the D series. General observations about 43 | nonvolatile storage, SD cards and power calculations remain relevant. 44 | 45 | ## Use cases 46 | 47 | I have considered two types of use case. The first is a monitoring application 48 | which periodically wakes, reads data from a sensor then returns to standby. At 49 | intervals it uses an NRF24L01 radio to send the accumulated data to a remote 50 | host. The second is a remote display using the NRF24L01 to acquire data from a 51 | remote host and an e-paper display to enable this to be presented when the 52 | Pyboard is in standby. In either case the Pyboard might be battery powered or 53 | powered from a power constrained source such as solar photovoltaic cells. 54 | 55 | ## Standby mode 56 | 57 | To achieve minimum power the code must be designed so that the Pyboard spends 58 | the majority of its time in standby mode. In this mode the current drawn by the 59 | Pyboard drops to some 6μA. Note that on recovery from standby the code will be 60 | loaded and run from the start: program state is not retained. Limited state 61 | information can be retained in the RTC backup registers and in RTC backup RAM. 62 | There are 20 32-bit backup registers and 4KiB of backup RAM. These are retained 63 | through a standby period. If an RTC backup battery is provided these will also 64 | be reyained through a power outage. The [upower](./UPOWER.md) module provides 65 | simple ways to access these. 66 | 67 | A typical application will use code along these lines: 68 | ```python 69 | import pyb 70 | import machine 71 | import upower 72 | rtc = pyb.RTC() 73 | rtc.wakeup(None) # If we have a backup battery clear down any setting from a previously running program 74 | 75 | if reason in (machine.PWRON_RESET, machine.HARD_RESET, machine.SOFT_RESET): # first boot 76 | rtc.datetime((2020, 8, 6, 4, 13, 0, 0, 0)) # Code to run on 1st boot only 77 | 78 | # code to run every time goes here 79 | rtc.wakeup(20000) 80 | if not upower.usb_connected: 81 | pyb.standby() 82 | ``` 83 | When the wakeup period has elapsed the board will wake up and the script will 84 | run again. 85 | 86 | The `usb_connected` logic simplifies debugging using a USB cable while 87 | minimising power when run without USB: `standby` permanently crashes USB. In 88 | general debugging micropower applications is simplified by using a UART rather 89 | than USB. This is strongly recommended and is discussed below. 90 | 91 | There are four ways to recover from standby: an RTC wakeup, RTC alarm wakeup, a 92 | tamper pin input, and a wakeup pin input. These are supported in `upower.py`. 93 | 94 | ## Nonvolatile memory and storage in standby 95 | 96 | To achieve the 6μA standby current it is necessary to use the internal flash 97 | memory for program storage rather than an SD card as SD cards draw significant 98 | standby current. The value varies with manufacturer but tends to dwarf the 99 | current draw of the Pyboard - 200μA is common. 100 | 101 | Some applications will require data to be retained while the Pyboard is in 102 | standby. Very small amounts may be stored in the RTC backup registers. The 103 | Pyboard also has 4KB of backup RAM which is more flexible and also retains data 104 | in standby. Code for using these options is provided below. 105 | 106 | In systems requiring true nonvolatile storage where data is retained after 107 | power loss as well as during standby one option is to use files in the internal 108 | flash, but this raises the issue of endurance. The Flash is rated at 10,000 109 | writes, a figure which is approached in a year even if the Pyboard only wakes 110 | and writes to it hourly (this is greatly mitigated by the littlefs filesystem). 111 | 112 | There are various high endurance nonvolatile memory technologies such as EEPROM 113 | and FRAM. Drivers for these may be found [here](https://github.com/peterhinch/micropython_eeprom). 114 | 115 | Larger volumes of data could be stored in an SD card whose power can be 116 | controlled as required: MicroPython supports an sdcard driver module enabling a 117 | card in an SPI adapter to be mounted in the filesystem. While lacking the 118 | extreme endurance of FRAM it should offer an effective solution in most 119 | applications, with power switching overcoming the "always on" power use of the 120 | inbuilt SD card connector. 121 | 122 | # Hardware issues 123 | 124 | Most practical applications will have hardware peripherals connected to Pyboard 125 | GPIO pins and the various Pyboard interfaces. To conserve power these should be 126 | powered down when the Pyboard is in standby. The Pyboard D enables this by 127 | means of a sofware-switchable 3.3V regulator. The Pyboard 1.x provides no means 128 | of doing this. In principle this could be achieved thus: 129 | 130 | ![Schematic](simple_schem.jpg) 131 | 132 | On first boot or on leaving standby the code drives the pin low, then drives it 133 | high before entering standby. In this state the GPIO pins go high impedance, so 134 | the MOSFET remains off by virtue of the resistor. 135 | 136 | On Pyboard 1.x this is inadequate for devices using the I2C bus or using the 137 | I2C pins as GPIO. This is because the Pyboard has pullup resistors on these 138 | pins which will source current into the connected hardware even when the latter 139 | is powered down. The simplest solution is to switch Vdd as in the above 140 | schematic and also to provide switches in series with the relevant GPIO pins to 141 | enable them to be put into a high impedance state. The current consumed by the 142 | connected hardware when the Pyboard is in standby is then negligible compared 143 | to the total current draw of 6μA. 144 | 145 | This problem is solved on the Pyboard D in that the I2C pullups for I2C(1) are 146 | powered from the switched 3.3V supply. I2C(2) has no pullups: these must be 147 | provided if this interface is to be used. 148 | 149 | A 6μA power consumption offers the possibility of a year's operation from a 150 | CR2032 button cell. In practice achieving this is dependent on the frequency 151 | and duration of power up events. 152 | 153 | ## Pyboard 1.x circuit design 154 | 155 | The following design provides for switching as described above and also - by 156 | virtue of a PCB design - simplifies the connection of the Pyboard to an e-paper 157 | display and the NRF24L01. Connections are provided for other I2C devices namely 158 | ferroelectric RAM (FRAM) [modules](https://learn.adafruit.com/adafruit-i2c-fram-breakout) 159 | and a BMP180 pressure sensor although these pins may readily be employed for 160 | other I2C modules. 161 | 162 | The design uses two Pyboard pins to control the peripheral power and the pullup 163 | resistors. Separate control is preferable because it enables devices to be 164 | powered off prior to disabling the pullups, which is recommended for some 165 | peripherals. An analog switch is employed to disable the pullup resistors. 166 | 167 | Resistors R3, R4 and capacitor C1 are optional and provide the facility for a 168 | switched filtered or slew rate limited 3.3V output. This was provided for the 169 | FRAM modules which specify a minimum and maximum slew rate: for these fit R3 170 | and R4. In this instance C1 may be omitted as the modules have a 10μF capacitor 171 | onboard. 172 | 173 | ![Schematic](epd_vddonly_schem.jpg) 174 | 175 | An editable version is provided in the file epd_vddonly.fzz - this requires the 176 | free (as in beer) software from [Fritzing](http://fritzing.org/home/) where 177 | PCB's can be ordered. The Fritzing software can produce extended Gerber files 178 | for those wishing to producure boards from other suppliers. 179 | 180 | I have an alternative version which replaces the FRAM with a power switched 181 | MicroSD card socket and provides a connector for an FTDI serial cable on UART4. 182 | I can provide details on request. 183 | 184 | ## Pyboard 1.x Driver micropower.py 185 | 186 | This is generalised to provide for hardware using a one or two pins to control 187 | power and pullups. If two pins are specified it assumes that the active high 188 | pin controls the pullups and the active low pin controls power as per the above 189 | schematic. If a single pin is specified it is taken to control both. 190 | 191 | The driver supports a single class `PowerController` 192 | 193 | ### Methods 194 | 195 | `PowerController()` The constructor has two arguments being strings 196 | representing Pyboard pins. If either is `None` it is assumed to control power 197 | and pullups. Arguments: 198 | 1. `pin_active_high` Driven high in response to `power_up()`. If both pins are 199 | defined powers I2C pullups. 200 | 2. `pin_active_low` Driven low in response to `power_up()`. If both pins are 201 | defined powers peripherals. 202 | 203 | `power_up()` No arguments. Powers up the peripherals, waits for power to settle 204 | before returning. 205 | `power_down()` No arguments. Powers down the peripherals. Waits for power to 206 | decay before powering down the I2C pullups and de-initialising the buses (the 207 | I2C driver seems to require this). 208 | 209 | ### Property 210 | 211 | `single_ended` Boolean: True if the PowerController has separate control of 212 | power and pullups. 213 | 214 | The driver provides optional support for use as a context manager thus: 215 | 216 | ```python 217 | from micropower import PowerController as pc 218 | p = pc(pin_active_high='Y12', pin_active_low='Y11') 219 | with p: 220 | f = FRAM(side = 'R') # Instantiate the hardware under power control 221 | pyb.mount(f, '/fram') # Use it 222 | os.listdir('/fram') 223 | pyb.mount(None, '/fram')# Perform any tidying up 224 | ``` 225 | 226 | Note that when peripheral is powered up it is usually necessary to create a 227 | device instance as shown above. Typical device drivers use the constructor to 228 | initialise the hardware, so you can't rely on a device instance persisting in a 229 | usable form after a power down event. 230 | 231 | The `power_up()` and `power_down()` methods support nested calls, with power 232 | only being removed at the outermost level. Use with care: the SPI and I2C buses 233 | will need to be re-initialised if they are to be used after a `power_down()` 234 | call. 235 | 236 | ### Footnote: I2C 237 | 238 | The Pyboard I2C bus driver doesn't respond well to the following sequence of 239 | events. 240 | 1. Power up the peripheral. 241 | 2. Initialise the bus. 242 | 3. Power down the peripheral. 243 | 4. Repeat the above sequence. 244 | 245 | It is necessary to de-initialise the bus prior to re-initialising it. In 246 | principle this could be done in the device constructor. However existing 247 | drivers are unlikely to do this. Consequently the `PowerController` does this 248 | on power down. I don't know of a similar issue with SPI, but the driver 249 | de-initialises this on a precautionary basis. 250 | 251 | # Some numbers 252 | 253 | The following calculations and measurements are based on a Pyboard 1.1 with 254 | hardware as described above. Similar results can be expected for a D series 255 | with the switchable 3.3V regulator turned on only when required. 256 | 257 | I tested a Pyboard D in a WBUS_DIP28 adaptor powered from a LiPo battery. The 258 | board ran a modified version of `ds_test.py` which woke only from RTC alarms. 259 | Current consumption between wakeups was 23.6μA. 260 | 261 | The capacity of small batteries is measured in milliamp hours (mAH), a measure 262 | of electric charge. For purposes of measurement on the Pyboard this is rather a 263 | large unit, and milliamp seconds (mAS) or millicoulomb is used here. 264 | 1mAH = 3600mAS. Note that, because the Pyboard uses a linear voltage regulator, 265 | the amount of current (and hence charge) used in any situation is substantially 266 | independent of battery voltage. 267 | 268 | After executing `pyb.standby()` the Pyboard consumes about 6μA. In a year's 269 | running this corrsponds to a charge utilisation of 53mAH, compared to the 225mAH 270 | nominal capacity of a CR2032 cell. 271 | 272 | @moose measured the startup charge required by the Pyboard 1.x 273 | [here](http://forum.micropython.org/viewtopic.php?f=6&t=607). 274 | This corresponds to about 9mAS or 0.0025mAH. If we start every ten minutes, 275 | annual consumption from startup events is 276 | 0.0025 x 6 x 24 x 365 = 131mAH. Adding the 53mAH from standby gives 184mAH, 277 | close to the capacity of the cell. This sets an upper bound on the frequency of 278 | power up events to achieve the notional one year runtime, although this could 279 | be doubled with an alternative button cell (see below). Two use cases, where 280 | the Pyboard performs a task on waking up, are studied below. 281 | 282 | ## Use case 1: reading a sensor and transmitting the data 283 | 284 | This was tested by reading a BMP180 pressure sensor and transmitting the data 285 | to a base station using an NRF24L01 radio, both with power switched. The 286 | current waveform (red trace, 40mA/div, 100ms/div) is shown below. The yellow 287 | trace shows the switched Vdd supply. Note the 300mA spike in the current 288 | waveform when Vdd switches on: this is caused by the charging of decoupling 289 | capacitors on the attached peripherals. 290 | 291 | ![Waveform](current.png) 292 | 293 | On my estimate the charge used each time the Pyboard wakes from standby is 294 | about 23mAS (comprising some 16mAS to boot up and 7mAS to read and transmit the 295 | data). If this ran once per hour the annual charge use would be 296 | 23 x 24 x 365/3600 mAH = 56mAH 297 | to which must be added the standby figure of 53mAH. One year's use with a 298 | CR2032 would seem feasible. 299 | 300 | The code is listed to indicate the approach used and to clarify my observations 301 | on charge usage. It is incomplete as I have not documented the slave end of the 302 | link or the `radio` module. 303 | 304 | ```python 305 | import struct, pyb, stm 306 | from radio import TwoWayRadio, RadioSetup 307 | from micropower import PowerController 308 | from bmp180 import BMP180 309 | 310 | RadioSetup['ce_pin'] = 'X4' 311 | ledy = pyb.LED(3) 312 | 313 | def send_data(): 314 | bmp180 = BMP180('Y') 315 | pressure = int(bmp180.pressure /100) 316 | nrf = TwoWayRadio(master = True, **RadioSetup) 317 | led_state = stm.mem32[stm.RTC + stm.RTC_BKP3R] 318 | led_state = max(1, (led_state << 1) & 0x0f) 319 | stm.mem32[stm.RTC + stm.RTC_BKP3R] = led_state 320 | # stop listening and send packet 321 | nrf.stop_listening() 322 | try: 323 | nrf.send(struct.pack('ii', pressure, led_state)) 324 | except OSError: # never happens 325 | pass 326 | nrf.start_listening() 327 | # wait for response, with 250ms timeout 328 | start_time = pyb.millis() 329 | timeout = False 330 | while not nrf.any() and not timeout: 331 | if pyb.elapsed_millis(start_time) > 250: 332 | timeout = True 333 | if timeout: 334 | ledy.on() 335 | pyb.delay(200) # get to see it before power goes down 336 | ledy.off() 337 | else: 338 | nrf.recv() # discard received data 339 | 340 | rtc = pyb.RTC() 341 | 342 | usb_connected = pyb.Pin.board.USB_VBUS.value() == 1 343 | if not usb_connected: 344 | pyb.usb_mode(None) # Save power 345 | 346 | if stm.mem32[stm.RTC + stm.RTC_BKP1R] == 0: # first boot 347 | rtc.datetime((2020, 8, 6, 4, 13, 0, 0, 0)) # Arbitrary 348 | 349 | p = PowerController(pin_active_high = 'Y12', pin_active_low = 'Y11') 350 | with p: 351 | send_data() 352 | rtc.wakeup(4000) 353 | stm.mem32[stm.RTC + stm.RTC_BKP1R] = 1 # indicate that we are going into standby mode 354 | if usb_connected: 355 | p.power_up() # Power up for testing 356 | else: 357 | pyb.standby() 358 | ``` 359 | 360 | Measurements were performed with the slave nearby so that timeouts never 361 | occurred. A comparison of the current waveform presented above with that 362 | recorded by @moose shows virtually identical behaviour for the first 180ms as 363 | the Pyboard boots up. There is then a period of some 230ms until power is 364 | applied to the peripherals where the board continues to draw some 60mA: 365 | compilation of imported modules to byte code is likely to be responsible for 366 | the bulk of this charge usage. The code which performs the actual application 367 | - namely power control and the reading and transmission of data is responsible 368 | for about a third of the total charge use. 369 | 370 | ## Use case 2: Displaying data on an e-paper screen 371 | 372 | A more computationally demanding test involved updating an epaper display with 373 | the following script 374 | 375 | ```python 376 | import pyb 377 | import epaper 378 | import machine 379 | from micropower import PowerController 380 | rtc = pyb.RTC() 381 | 382 | usb_connected = pyb.Pin.board.USB_VBUS.value() == 1 383 | if not usb_connected: 384 | pyb.usb_mode(None) # Save power 385 | 386 | if reason in (machine.PWRON_RESET, machine.HARD_RESET, machine.SOFT_RESET): # first boot 387 | rtc.datetime((2015, 8, 6, 4, 13, 0, 0, 0)) # Arbitrary 388 | 389 | t = rtc.datetime()[4:7] 390 | timestring = '{:02d}.{:02d}.{:02d}'.format(t[0],t[1],t[2]) 391 | p = PowerController(pin_active_high = 'Y12', pin_active_low = 'Y11') 392 | a = epaper.Display(side = 'Y', use_flash = True, pwr_controller = p) 393 | s = str(a.temperature) + "C\n" + timestring 394 | a.mountflash() # Power up 395 | with a.font('/fc/LiberationSerif-Regular45x44'): 396 | a.puts(s) 397 | a.umountflash() # Keep filesystem happy 398 | a.show() 399 | 400 | rtc.wakeup(20000) 401 | if usb_connected: 402 | p.power_up() # Power up for testing 403 | else: 404 | pyb.standby() 405 | ``` 406 | 407 | Modules epaper and micropower located [here](https://github.com/peterhinch/micropython-epaper.git). 408 | 409 | This used an average of 85mA for 6S to do an update. If the script performed 410 | one refresh per hour this would equate to 411 | 85 x 6/3600 = 141μA average + 6μA quiescent = 147μA.__ 412 | This would exhaust a CR2032 in 9 weeks. An alternative is the larger CR2450 413 | button cell with 540mAH capacity which would provide 5 months running. 414 | 415 | A year's running would be achievable if the circuit were powered from three AA 416 | alkaline cells: 417 | Power = 141μA + 6μA quiescent = 147μA x 24 x 365 = 1.28AH__ 418 | This is within the nominal capacity of these cells. 419 | 420 | # Hardware 421 | 422 | Hardware as used for tests of power switched peripherals. 423 | 424 | ![Hardware](hardware.JPG) 425 | 426 | # Pyboard 1.x: note for hardware designers 427 | 428 | When designing a board intended for micropower operation, consider 429 | incorporating a MOSFET to provide a switched 3.3V supply for peripherals. 430 | Pullups can either be powered from this switched supply, or for greater 431 | flexibility driven from a separately controlled MOSFET. 432 | -------------------------------------------------------------------------------- /UPOWER.md: -------------------------------------------------------------------------------- 1 | # 1. The upower module 2 | 3 | See also [hardware](./HARDWARE.md) for a document discussing hardware issues and 4 | power draw calculations and measurements. This document is based on the Pyboard 5 | V1.0, V1.1 and D series. The other platforms based on the STM chips may have 6 | different hardware functionality. This applies to the Pyboard Lite which 7 | supports only a subset. 8 | 9 | # 2. The Pyboard 10 | 11 | There was an issue with Pyboard D firmware which precluded the use of most pins 12 | to wake from standby. Ways to wake were restricted to timer alarms and the 13 | tamper mechanism via pin X18 (C13). As of 10th March 2021 14 | [my PR to fix this](https://github.com/micropython/micropython/pull/6494) 15 | has been merged: to use this capability use a daily build after this date or 16 | a release build >1.14. 17 | 18 | ## 2.1 Introduction 19 | 20 | This module provides access to features of the Pyboard which are useful in low 21 | power applications but not supported in firmware at the time of writing: check 22 | for official support for any specific feature before using. Access to the 23 | following processor features is provided: 24 | 25 | 1. 4KiB of backup RAM accessible as words or bytes. May be battery backed. 26 | Whether or not battery backed, retains data during standby. 27 | 2. 20 general purpose 32-bit registers also battery backed. 28 | 3. Wakeup from standby by means of a switch on the Tamper pin. 29 | 4. Wakeup from standby by means of two Pyboard pins (four on the D series). 30 | 5. Wakeup by means of two independent real time clock (RTC) alarms. An alarm 31 | provides for (say) a wakeup every the 1st day of the month at 03:15. 32 | 6. The ability to determine the reason for wakeup from standby. 33 | 7. Support for the `stop` condition. This stops the time source used by 34 | `utime`. The module provides alternative ways to do millisecond level timing 35 | via the RTC. 36 | 37 | All code is released under the [MIT license](./LICENSE) 38 | 39 | ## 2.2 Test scripts 40 | 41 | 1. `alarm.py` Illustrates wakeup from two RTC alarms. 42 | 2. `ttest.py` Wakes up periodically from an RTC alarm. Can be woken by linking 43 | pin X18 to Gnd or (on Pyboard 1.x) linking pin X1 to 3V3. On Pyboard D pin X1 44 | works if modified firmware is used and a pull-down is supplied. 45 | 3. `ds_test.py` Test script for Pyboard D. Tests wakeup using the various 46 | permitted pins. See [section 5](./UPOWER.md#5-module-ds_test). 47 | 48 | The `ttest` script illustrates a means of ensuring that the RTC alarm operates 49 | at fixed intervals in the presence of pin wakeups. 50 | 51 | ## 2.3 A typical application 52 | 53 | When a Pyboard goes into standby its consumption drops to about 6μA. When it is 54 | woken, program execution begins as if the board had been initially powered up: 55 | `boot.py` then `main.py` are executed. Unless `main.py` imports your 56 | application, a REPL prompt will result. Assuming your application is re-run, 57 | there are ways to retain some program state and to determine the cause of the 58 | wakeup. Mastering these enables practical applications to be developed. The 59 | following is one of the demo programs `alarm.py` which uses the two timers to 60 | wake the Pyboard alternately, each once per minute. 61 | 62 | ```python 63 | import stm, pyb, upower, machine 64 | 65 | red, green, yellow = (pyb.LED(x) for x in range(1, 4)) # LED(3) is blue, not yellow, on D series 66 | rtc = pyb.RTC() 67 | rtc.wakeup(None) # If we have a backup battery clear down any setting from a previously running program 68 | reason = machine.reset_cause() # Why have we woken? 69 | if reason in (machine.PWRON_RESET, machine.HARD_RESET, machine.SOFT_RESET): 70 | # Code to run when the application is first started 71 | aa = upower.Alarm('a') 72 | aa.timeset(second = 39) 73 | ab = upower.Alarm('b') 74 | ab.timeset(second = 9) 75 | red.on() 76 | elif reason == machine.DEEPSLEEP_RESET: 77 | reason = upower.why() 78 | if reason == 'ALARM_A': 79 | green.on() 80 | elif reason == 'ALARM_B': 81 | yellow.on() 82 | 83 | upower.lpdelay(1000) # Let LED's be seen for 1s. 84 | pyb.standby() 85 | ``` 86 | 87 | ## 2.4 Module description 88 | 89 | The module uses the topmost three 32 bit words of the backup RAM (1021-1023 90 | inclusive). 91 | 92 | Note on objects in this module. Once `rtc.wakeup()` is issued, methods other 93 | than `enable()` should be avoided as some employ the RTC. Issue `rtc.wakeup()` 94 | shortly before `pyb.standby`. 95 | 96 | ### 2.4.1 Globals 97 | 98 | The module provides a two boolean global variables: 99 | 1. `usb_connected` `True` if REPL via USB is enabled and a physical 100 | USB connection is in place. On the Pyboard 1.x this returns `True` if power is 101 | supplied from the USB connector. On the D series it returns `True` only if a 102 | terminal session is running on the USB connector. 103 | 2. `d_series` `True` if running on a Pyboard D. 104 | 105 | ### 2.4.2 Principal functions 106 | 107 | The module provides the following functions: 108 | 1. `lpdelay` A low power alternative to `pyb.delay()`. 109 | 2. `lp_elapsed_ms` An alternative to `pyb.elapsed_millis` which works 110 | during `lpdelay` calls. 111 | 3. `now` Returns RTC time in millisecs since the start of year 2000. 112 | 4. `savetime` Store current RTC time in backup RAM. Optional arg `addr` 113 | default 1021 (uses 2 words). 114 | 5. `ms_left` Enables a timed sleep or standby to be resumed after a tamper or 115 | WKUP interrupt. 116 | Requires `savetime` to have been called before commencing the sleep/standby. 117 | Arguments `delta` the delay period in ms, `addr` the address where the time 118 | was saved (default 1021). 119 | 6. `cprint` Same usage as `print` but does nothing if USB is connected. If USB 120 | is connected functions such as `standby` disable USB connectivity. Issuing 121 | `print()` under those circumstances would crash the program. 122 | 7. `why` No args. Returns the reason for a wakeup event. 123 | 8. `bkpram_ok` No args. Detection of valid data in backup RAM after a 124 | power up event. Returns `True` if RAM has retained data (i.e. it was battery 125 | backed during outage). 126 | 127 | ### 2.4.3 Other functions 128 | 129 | These functions were implemented to overcome a problem with the `pyb.ADCAll` 130 | class. This has been fixed but the functions are retained to avoid breaking 131 | code. 132 | 1. `v33` No args. Returns Vdd. If Vin > 3.3V Vdd should read approximately 133 | 3.3V. Lower values indicate a Vin which has dropped below 3.3V typically due 134 | to a failing battery. 135 | 2. `vref` Returns the reference voltage. 136 | 3. `vbat` Returns the backup battery voltage (if fitted). 137 | 4. `temperature` Returns the chip temperature in °C. Note that the chip 138 | datasheet points out that the absolute accuracy of this is poor, varies 139 | greatly from one chip to another, and is best suited for monitoring changes in 140 | temperature. It produces spectacularly poor results if the 3.3V supply drops 141 | out of spec. 142 | 143 | ### 2.4.4 Classes 144 | 145 | The module provides the following classes: 146 | 1. `Alarm` Provides access to the two RTC alarms. 147 | 2. `BkpRAM` Provides access to the backup RAM. 148 | 3. `RTC_Regs` Provides access to the backup registers. 149 | 4. `Tamper` Enables wakeup from the Tamper pin X18 (C13, W26 on Pyboard D). 150 | 5. `wakeup_X1` (Pyboard 1.x) Enables wakeup from a positive edge on pin X1. 151 | 6. `WakeupPin` (Pyboard D) Enable wakeup from either edge of upto four pins. 152 | 153 | ## 2.5 Function `lpdelay()` 154 | 155 | This accepts one argument: a delay in ms. It is a low power replacement for 156 | `utime.sleep_ms()`. The function normally uses `pyb.stop` to reduce power 157 | consumption from 20mA to 500μA. If USB is connected it reverts to `pyb.delay` 158 | to avoid killing the USB connection. There is a subtle issue when using this 159 | function: the Pyboard loses all sense of time when stopped, with the RTC being 160 | the only valid time source. Consequently you can't use `utime` or `pyb` 161 | functions to keep track of time through an `lpdelay`. The simplest solution is 162 | to use the provided `lp_elapsed_ms` function. 163 | 164 | ## 2.6 Function `lp_elapsed_ms()` 165 | 166 | Accepts one argument, a start time in ms from the `now` function. Typical code 167 | to implement a one second timeout might be along these lines: 168 | 169 | ```python 170 | start = upower.now() 171 | while upower.lp_elapsed_ms(start) < 1000: 172 | print(upower.lp_elapsed_ms(start)) # do something 173 | upower.lpdelay(100) 174 | ``` 175 | 176 | ## 2.7 Function `now()` 177 | 178 | Returns RTC time in milliseconds since the start of year 2000. The function is 179 | mainly intended for use in implementing sleep or standby delays which can be 180 | resumed after an interrupt from tamper or WKUP. Millisecond precision is 181 | meaningless in standby periods where wakeups are slow, but is relevant to sleep. 182 | On Pyboard 1.x precision is limited to about 4ms owing to the RTC hardware. 183 | 184 | ## 2.8 Function `savetime()` 185 | 186 | Store current RTC time in backup RAM. Optional argument `addr` default 1021. 187 | This uses two words to store the milliseconds value produced by `now()` 188 | 189 | ## 2.9 Function `ms_left()` 190 | 191 | This produces a value of delay for presenting to `wakeup()` and enables a 192 | timed sleep or standby to be resumed after a tamper or WKUP interrupt. To use 193 | it, execute `savetime` before commencing the sleep/standby. Arguments `delta` 194 | normally the original delay period in ms, `addr` the address where the time 195 | was saved (default 1021). The function can raise an exception in response to a 196 | number of errors such as the case where a time was not saved or the RTC was 197 | adjusted after saving. The defensive coder will trap these! 198 | 199 | If the time has expired it will return zero (i.e. it will never return negative 200 | values). 201 | 202 | The test program `ttest.py` illustrates its use. 203 | 204 | ## 2.10 Function `why()` 205 | 206 | This enhances `machine.reset_cause` by providing more detail on the reason 207 | why the Pyboard has emerged from deep sleep. `machine.reset_cause` should 208 | first be called to detect the conditions `PWRON_RESET` or `HARD_RESET`. 209 | If it returns `DEEPSLEEP_RESET` then `why()` may be called. It will 210 | return one of the following values: 211 | 212 | 1. 'TAMPER' Woken by the Tamper pin (X18, C13, W26). See below. 213 | 2. 'WAKEUP' Woken by RTC.wakeup(). 214 | 3. 'ALARM_A' Woken by RTC alarm A. 215 | 4. 'ALARM_B' Woken by RTC alarm B. 216 | 5. 'X1' Woken by the WKUP pin (X1, PA0, W19). 217 | 6. 'X3' (X3, PA2, W15) (Pyboard D only). 218 | 7. 'C1' (C1, W24) (Pyboard D only). 219 | 8. 'C13' (C13, X18, W26) (Pyboard D only). See below. 220 | 9. `None` Reason unknown. 221 | (Alternative pin names in parens). 222 | 223 | Re pin C13 on the Pyboard D, this may be used in either of two ways. The tamper 224 | mechanism is designed to interface to a switch and works with standard 225 | firmware. Alternatively, with adapted firmware, the `WakeupPin` class may be 226 | used, in which case 'C13' is returned. 227 | 228 | ## 2.11 Alarm class (access RTC alarms) 229 | 230 | The RTC supports two alarms 'A' and 'B' each of which can wake the Pyboard at 231 | programmed intervals. 232 | 233 | Constructor: an alarm is instantiated with a single mandatory argument, 'A' or 234 | 'B'. 235 | Method `timeset()` Assuming at least one kw only argument is passed, this will 236 | start the timer and cause periodic interrupts to be generated. In the absence of 237 | arguments the timer will be disabled. Arguments default to `None`. 238 | Arguments (kwonly args): 239 | 1. `day_of_month` 1..31 If present, alarm will occur only on that day 240 | 2. `weekday` 1 (Monday) - 7 (Sunday) If present, alarm will occur only on 241 | that day of the week 242 | 3. `hour` 0..23 243 | 4. `minute` 0..59 244 | 5. `second` 0..59 245 | 246 | Usage examples: 247 | ```python 248 | # Wake at 17:00 every Monday 249 | mytimer.timeset(weekday = 1, hour = 17) 250 | # Wake at 5am every day 251 | mytimer.timeset(hour = 5) 252 | # Wake up every hour at 10 mins, 30 secs after the hour 253 | mytimer.timeset(minute = 10, second = 30) 254 | # Wake up each time RTC seconds reads 30 i.e. once per minute 255 | mytimer.timeset(second = 30) 256 | ``` 257 | ## 2.12 BkpRAM class (access Backup RAM) 258 | 259 | This class enables the on-chip 4KB of battery backed RAM to be accessed as an 260 | array of integers or as a bytearray. The latter facilitates creating persistent 261 | arbitrary objects using JSON or pickle. 262 | 263 | Its initial contents after power up are arbitrary unless an RTC backup battery 264 | is used. Note that `savetime()` uses two 32 bit words at 1021 and 1022 by 265 | default and startup detection uses 1023 so these top three locations should 266 | normally be avoided. 267 | 268 | ```python 269 | from upower import BkpRAM 270 | bkpram = BkpRAM() 271 | bkpram[0] = 22 # use as integer array 272 | bkpram.ba[4] = 0 # or as a bytearray 273 | ``` 274 | 275 | The following code fragment illustrates the use of `ujson` to save an arbitrary 276 | Python object to backup RAM and restore it on a subsequent wakeup. 277 | 278 | ```python 279 | import ujson, upower 280 | bkpram = upower.BkpRAM() 281 | a = {'rats':77, 'dogs':99,'elephants':9, 'zoo':100} 282 | z = ujson.dumps(a).encode('utf8') 283 | bkpram[0] = len(z) 284 | bkpram.ba[4: 4+len(z)] = z # Copy into backup RAM 285 | # Resumption after standby 286 | import ujson, upower 287 | bkpram = upower.BkpRAM() 288 | # retrieve dictionary 289 | a = ujson.loads(bytes(bkpram.ba[4:4+bkpram[0]]).decode('utf-8')) 290 | ``` 291 | 292 | ## 2.13 RTCRegs class (RTC Register access) 293 | 294 | The RTC has a set of 20 32-bit backup registers. These are initialised to zero 295 | on boot, and are also cleared down after a Tamper event. Registers may be 296 | accessed as follows: 297 | 298 | ```python 299 | from upower import RTCRegs 300 | rtcregs = RTCRegs() 301 | rtcregs[3] = 42 302 | ``` 303 | 304 | ## 2.14 Tamper class (Enable wakeup on pin X18 (C13, W26 on Pyboard D)) 305 | 306 | This is a flexible way to interrupt a standby condition, providing for edge or 307 | level detection, the latter with hardware switch debouncing. Level detection 308 | operates as follows. The pin is normally high impedance. At intervals a pullup 309 | resistor is connected and the pin state sampled. After a given number of such 310 | intervals, if the pin continues to be in the active state, the Pyboard is woken. 311 | The active state, polling frequency and number of samples may be configured 312 | using `tamper.setup()`. 313 | 314 | Note that in edge triggered mode the pin behaves as a normal input with no 315 | pullup. If driving from a switch, you must provide a pullup (to 3V3) or pulldown 316 | as appropriate. 317 | 318 | In use first instatiate the tamper object: 319 | 320 | ```python 321 | from upower import Tamper 322 | tamper = Tamper() 323 | ``` 324 | 325 | The class supports the following methods and properties. 326 | 327 | `setup()` method accepts the following arguments: 328 | 1. `level` Mandatory: valid options 0 or 1. In level triggered mode, 329 | determines the active level. 330 | In edge triggered mode, 0 indicates rising edge trigger, 1 falling edge. 331 | Optional kwonly args: 332 | 2. `freq ` Valid options 1, 2, 4, 8, 16, 32, 64, 128: polling frequency in 333 | Hz. Default 16. 334 | 3. `samples` Valid options 2, 4, 8: number of consecutive samples before 335 | wakeup occurs. Default 2. 336 | 4. `edge` Boolean. If True, the pin is edge triggered. `freq` and 337 | `samples` are ignored. Default False. 338 | 339 | `enable()` method enables the tamper interrupt. Call just before issuing 340 | `pyb.standby()` and after the use of any other methods as it reconfigures the 341 | pin. 342 | 343 | `tamper.wait_inactive()` method returns when pin X18 has returned to its 344 | inactive state. In level triggered mode this may be called before issuing the 345 | `enable()` method to avoid recurring interrupts. In edge triggered mode where 346 | the signal is from a switch it might be used to debounce the trailing edge of 347 | the contact period. 348 | 349 | `disable()` method disables the interrupt. Not normally required as the 350 | interrupt is disabled by the constructor. 351 | 352 | `pinvalue` property returning the value of the signal on the pin: 0 is 0V 353 | regardless of `level`. 354 | 355 | See `ttest.py` for an example of its usage. 356 | 357 | ## 2.15 wakeup_X1 class (Enable wakeup on pin X1: Pyboard 1.x) 358 | 359 | Enabling this converts pin X1 into an input. A low to high transition will wake 360 | the Pyboard from standby. It is recommended to add an external pull down 361 | resistor if a switch is used.The following code fragment illustrates its use. A 362 | complete example is in `ttest.py`. 363 | 364 | ```python 365 | from upower import wakeup_X1 366 | wkup = wakeup_X1() 367 | # code omitted 368 | wkup.enable() 369 | if not upower.usb_connected: 370 | pyb.standby() 371 | ``` 372 | 373 | The `wakeup_X1` class has the following methods and properties. 374 | Constructor: 375 | This tkes no args. A `ValueError` will result if run on a Pyboard D (the 376 | `WakeupPin` class should be used). 377 | 378 | Methods: 379 | 1. `enable()` enables the wkup interrupt. Call just before issuing `pyb.standby()` 380 | and after the use of any other wkup methods as it reconfigures the pin. 381 | 2. `wait_inactive()` This method returns when pin X1 has returned low. This 382 | might be used to debounce the trailing edge of the contact period: call 383 | `lpdelay(50)` after the function returns and before entering standby to ensure 384 | that contact bounce is over. 385 | 3. `disable()` disables the interrupt. Not normally required as the interrupt 386 | is disabled by the constructor. 387 | 388 | Property: 389 | 1. `pinvalue` Returns the value of the signal on the pin: 0 is low, 1 high. 390 | 391 | ## 2.16 WakeupPin class (Pyboard D only) 392 | 393 | The Pyboard D can wake from standby by means of inputs on the following pins. 394 | Note the pin name aliases: 395 | 1. `A0 X1 W19` 396 | 2. `A2 X3 W15` 397 | 3. `C1 W24` 398 | 4. `C13 X18 W26` 399 | 400 | See firmware note in [section 2](./UPOWER.md#2-the-pyboard). 401 | 402 | It does not seem to be possible to configure the internal pullups or pull-downs 403 | in this mode, so if switches are used an external resistor must be supplied. 404 | Wakeups may be on a rising or falling transition. If a falling transition is 405 | specified and a switch is used, note that the external pullup must be to a 406 | power source which is present in standby mode. The switched 3V3 supply is not. 407 | The datasheet lists these pins as 5V tolerant. 408 | 409 | If you experience constant or erratic retriggering it is almost certainly a 410 | problem with pullups. 411 | 412 | Constructor: 413 | This takes two args, a `Pin` instance and `rising=True`. The `Pin` instance 414 | does not need to be configured. If `rising` is `True`, wakeup will occur on a 415 | low to high transition. A `ValueError` will result if the host is not a Pyboard 416 | D or if the `Pin` instance is not one of the above pins. 417 | 418 | Methods: 419 | 1. `enable()` enables the wkup interrupt. Call just before issuing 420 | `pyb.standby()` and after the use of any other wkup methods as it reconfigures 421 | the pin. 422 | 2. `wait_inactive()` This method returns when the pin has returned to the 423 | inactive state. This might be used to debounce a switch contact: call 424 | `lpdelay(50)` after the function returns and before entering standby to ensure 425 | that contact bounce is over. 426 | 3. `disable()` disables the interrupt. Not normally required as the interrupt 427 | is disabled by the constructor. 428 | 4. `pinvalue` Returns the value of the signal on the pin: 0 is low, 1 high. 429 | 5. `state` Returns `True` if the pin is active. 430 | 431 | # 3. Module ttest 432 | 433 | Demonstrates various ways to wake up from standby and how to differentiate 434 | between them. 435 | 436 | To run this, edit your `main.py` to include `import ttest`. Power the 437 | Pyboard from a source other than USB. It will flash the red, yellow and green 438 | LEDs after boot and the green and yellow ones every ten seconds in response to a 439 | timer wakeup. If pin X1 is pulled to 3V3 red and green will flash. If pin X18 440 | (C13, W26 on Pyboard D) is pulled low red will flash. If a backup battery is in 441 | use and power to the board is cycled, power up events subsequent to the first 442 | will cause the yellow LED to flash. 443 | 444 | If a UART is initialised for REPL in boot.py the time of each event will be 445 | output. 446 | 447 | If an RTC backup battery is used and the Pyboard power is removed while a wakeup 448 | delay is pending it will behave as follows. If power is re-applied before the 449 | delay times out, it will time out at the correct time. If power is applied after 450 | the time has passed two wakeups will occur soon after power up: the first caused 451 | by the power up, and the second by the deferred wakeup. 452 | 453 | # 4. Module alarm 454 | 455 | Demonstrates the RTC alarms. Runs both alarms concurrently, waking every 30 456 | seconds and flashing LED's to indicate which timer has caused the wakeup. To run 457 | this, edit your `main.py` to include `import alarm`. 458 | 459 | # 5. Module ds_test 460 | 461 | At the time of writing this test requires modified firmware as detailed in 462 | [section 2](./UPOWER.md#2-the-pyboard-d). 463 | 464 | This test is specific to Pyboard D and tests wakeup from any of the four legal 465 | pins. To run it modify `main.py` as follows: 466 | ```python 467 | import ds_test 468 | ds_test.test('X1', 'X3' 'C1', 'C13', rising=True) 469 | ``` 470 | Any combination of these pins may be selected, but there must be a physical 471 | pull-down resistor on any that are listed. If `rising` is `False` the resistor 472 | must be a pullup to a voltage which will be present when the board is in 473 | standby; the switchable 3.3V supply is not. These pins are +5V tolerant. 474 | 475 | An RTC alarm causes white to flash periodically. A high level on any specified 476 | pin will wake the board. Each will flash a different LED. 477 | 478 | # 6. Coding tips 479 | 480 | ## 6.1 Debugging using print() 481 | 482 | Using USB for the REPL makes this impractical because `stop()` and `standby()` 483 | break the connection. A solution is to redirect the REPL to a UART and use a 484 | terminal application via a USB to serial adaptor. If your code uses `standby()` 485 | a delay may be necessary prior to the call to ensure sufficient time elapses for 486 | the data to be transmitted before the chip shuts down. 487 | 488 | On resumption from standby the Pyboard will execute `boot.py` and `main.py`, 489 | so unless `main.py` restarts your program, you will be returned to the REPL. 490 | 491 | Other points to note when debugging code which uses standby mode. If using a 492 | backup battery the RTC will remember settings even if the Pyboard is powered 493 | down. So if you run `rtc.wakeup(value)`, power down the board, then power it 494 | up to run another program, it will continue to wake from standby at the interval 495 | specified. Issue `rtc.wakeup(None)` if this is not the desired outcome. The 496 | same applies to alarms: to clear down an alarm instantiate it and issue its 497 | `timeset()` method with no arguments. 498 | 499 | Another potential source of confusion arises if you use `rshell` to access the 500 | Pyboard. Helpfully it automatically sets the RTC from the connected computer. 501 | However it can result in unexpected timings if the RTC is adjusted when delays 502 | or alarms are pending. 503 | 504 | ## 6.2 CPU clock speed 505 | 506 | When coding for minimum power consumption there are various options. One is to 507 | reduce the CPU clock speed: its current draw in normal running mode is roughly 508 | proportional to clock speed. However in computationally intensive tasks the 509 | total charge drawn from a battery may not be reduced since processing time will 510 | double if the clock rate is halved. This is a consequence of the way CMOS logic 511 | works: gates use a fixed amount of charge per transition. If your code spends 512 | time waiting on `pyb.delay()` reducing clock rate will help, but if using 513 | `upower.lpdelay()` gains may be negligible. 514 | 515 | If your code uses standby and is in a `.py` module it will be recompiled each 516 | time the board exits standby. Solutions are to cross-compile or to use frozen 517 | bytecode. The latter should be the most efficient as it eliminates the 518 | filesystem access required to load an `.mpy` module. 519 | --------------------------------------------------------------------------------