-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathupower.py
472 lines (406 loc) · 16.7 KB
/
upower.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# upower.py Enables access to functions useful in low power Pyboard projects
# Copyright 2016-2020 Peter Hinch
# This code is released under the MIT licence
# V0.43 Sep 2020 Further Pyboard D fixes.
# V0.42 15th June 2020 Fix Tamper for Pyboard D. Ref
# https://forum.micropython.org/viewtopic.php?f=20&t=8518&p=48337
# http://www.st.com/web/en/resource/technical/document/application_note/DM00025071.pdf
import pyb, stm, os, utime, uctypes, machine
# CODE RUNS ON IMPORT **** START ****
d_series = os.uname().machine.split(' ')[0][:4] == 'PYBD'
# https://forum.micropython.org/viewtopic.php?f=20&t=6222&p=35497
usb_connected = False
if pyb.usb_mode() is not None: # User has enabled VCP in boot.py
if d_series:
# Detect an active debugging session
usb_connected = pyb.USB_VCP().isconnected()
else:
usb_connected = pyb.Pin.board.USB_VBUS.value() == 1
if not usb_connected:
pyb.usb_mode(None) # Save power
# CODE RUNS ON IMPORT **** END ****
def bounds(val, minval, maxval, msg): # Bounds check
if not (val >= minval and val <= maxval):
raise ValueError(msg)
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
class RTCError(OSError):
pass
def cprint(*args, **kwargs): # Conditional print: USB fails if low power modes are used
global usb_connected
if not usb_connected: # assume a UART has been specified in boot.py
print(*args, **kwargs)
# Count the trailing zeros in an integer
def ctz(n):
if not n:
return 32
count = 0
while not n & 1:
n >>= 1
count += 1
return count
# ***** BACKUP RAM SUPPORT *****
@singleton
class BkpRAM:
BKPSRAM = 0x40024000
def __init__(self):
stm.mem32[stm.RCC + stm.RCC_APB1ENR] |= 0x10000000 # PWREN bit
if d_series:
stm.mem32[stm.PWR + stm.PWR_CR1] |= 0x100 # Set the DBP bit in the PWR power control register
stm.mem32[stm.RCC + stm.RCC_AHB1ENR] |= 0x40000 # enable BKPSRAMEN
stm.mem32[stm.PWR + stm.PWR_CSR1] |= 0x200 # BRE backup regulator enable bit
else:
stm.mem32[stm.PWR + stm.PWR_CR] |= 0x100 # Set the DBP bit in the PWR power control register
stm.mem32[stm.RCC + stm.RCC_AHB1ENR] |= 0x40000 # enable BKPSRAMEN
stm.mem32[stm.PWR + stm.PWR_CSR] |= 0x200 # BRE backup regulator enable bit
self._ba = uctypes.bytearray_at(self.BKPSRAM, 4096)
def idxcheck(self, idx):
bounds(idx, 0, 0x3ff, 'RTC backup RAM index out of range')
def __getitem__(self, idx):
self.idxcheck(idx)
return stm.mem32[self.BKPSRAM + idx * 4]
def __setitem__(self, idx, val):
self.idxcheck(idx)
stm.mem32[self.BKPSRAM + idx * 4] = val
@property
def ba(self):
return self._ba # Access as bytearray
# ***** RTC REGISTERS *****
@singleton
class RTCRegs:
def idxcheck(self, idx):
bounds(idx, 0, 19, 'RTC register index out of range')
def __getitem__(self, idx):
self.idxcheck(idx)
return stm.mem32[stm.RTC + stm.RTC_BKP0R+ idx * 4]
def __setitem__(self, idx, val):
self.idxcheck(idx)
stm.mem32[stm.RTC + stm.RTC_BKP0R + idx * 4] = val
# ***** LOW POWER pyb.delay() ALTERNATIVE *****
# Low power delay. Note stop() kills USB.
# For the duratiom it stops the time source used by utime.
def lpdelay(ms):
global usb_connected
rtc = pyb.RTC()
if usb_connected:
pyb.delay(ms)
return
rtc.wakeup(ms)
pyb.stop()
rtc.wakeup(None)
# ***** TAMPER (X18) PIN SUPPORT *****
# Changes for Pyboard D ref https://forum.micropython.org/viewtopic.php?f=20&t=8518
@singleton
class Tamper:
def __init__(self):
self.edge_triggered = False
self.triggerlevel = 0
self.tampmask = 0
self.disable() # Ensure no events occur until we're ready
self.pin = pyb.Pin.cpu.C13 # X18 doesn't exist on Pyboard D
self.pin_configured = False # Conserve power: enable pullup only if needed
self.setup()
def setup(self, level=0, *, freq=16, samples=2, edge=False):
self.tampmask = 0
if level == 1:
self.tampmask |= 2 | (1 << 15) # Disable pullup and precharge
self.triggerlevel = 1
elif level == 0:
self.triggerlevel = 0
else:
raise ValueError("level must be 0 or 1")
if d_series:
self.tampmask |= (1 << 17) # Disable erasure of backup registers
if type(edge) == bool:
self.edge_triggered = edge
else:
raise ValueError("edge must be True or False")
if not self.edge_triggered:
if freq in (1,2,4,8,16,32,64,128):
self.tampmask |= ctz(freq) << 8
else:
raise ValueError("Frequency must be 1, 2, 4, 8, 16, 32, 64 or 128Hz")
if samples in (2, 4, 8):
self.tampmask |= ctz(samples) << 11
else:
raise ValueError("Number of samples must be 2, 4, or 8")
def _pinconfig(self):
if not self.pin_configured:
if self.triggerlevel:
self.pin.init(mode = pyb.Pin.IN, pull = pyb.Pin.PULL_DOWN)
else:
self.pin.init(mode = pyb.Pin.IN, pull = pyb.Pin.PULL_UP)
self.pin_configured = True
def disable(self):
if d_series:
stm.mem32[stm.RTC + stm.RTC_TAMPCR] = self.tampmask
else:
stm.mem32[stm.RTC + stm.RTC_TAFCR] = self.tampmask
def wait_inactive(self):
self._pinconfig()
while self.pin.value() == self.triggerlevel: # Wait for pin to go logically off
lpdelay(50)
@property
def pinvalue(self):
self._pinconfig()
return self.pin.value()
def enable(self):
BIT21 = 1 << 21 # Tamper mask bit
self.disable()
stm.mem32[stm.EXTI + stm.EXTI_IMR] |= BIT21 # Set up ext interrupt
stm.mem32[stm.EXTI + stm.EXTI_RTSR] |= BIT21 # Rising edge
stm.mem32[stm.EXTI + stm.EXTI_PR] |= BIT21 # Clear pending bit
stm.mem32[stm.RTC + stm.RTC_ISR] &= 0xdfff # Clear tamp1f flag
if d_series:
stm.mem32[stm.PWR + stm.PWR_CR2] |= 0x3f # Clear power wakeup flag WUF
stm.mem32[stm.RTC + stm.RTC_TAMPCR] = self.tampmask | 5 # Tamper interrupt enable and tamper1 enable
else:
stm.mem32[stm.PWR + stm.PWR_CR] |= 4 # Clear power wakeup flag WUF
stm.mem32[stm.RTC + stm.RTC_TAFCR] = self.tampmask | 5 # Tamper interrupt enable and tamper1 enable
# ***** WKUP PIN (X1) SUPPORT (V1.x) *****
@singleton
class wakeup_X1: # Support wakeup on low-high edge on pin X1
def __init__(self):
if d_series:
raise ValueError('Not supported by D series.')
self.disable()
self.pin = pyb.Pin.board.X1 # Don't configure pin unless user accesses wkup
# On the Espruino Pico change X1 to A0 (issue #1)
self.pin_configured = False
def _pinconfig(self):
if not self.pin_configured:
self.pin.init(mode = pyb.Pin.IN, pull = pyb.Pin.PULL_DOWN)
self.pin_configured = True
def enable(self): # In this mode pin has pulldown enabled
stm.mem32[stm.PWR + stm.PWR_CR] |= 4 # set CWUF to clear WUF in PWR_CSR
stm.mem32[stm.PWR + stm.PWR_CSR] |= 0x100 # Enable wakeup
def disable(self):
stm.mem32[stm.PWR + stm.PWR_CSR] &= 0xfffffeff # Disable wakeup
def wait_inactive(self):
self._pinconfig()
while self.pin.value() == 1: # Wait for pin to go low
lpdelay(50)
@property
def pinvalue(self):
self._pinconfig()
return self.pin.value()
# ***** PYBOARD D WKUP PIN SUPPORT *****
# Support wakeup on pin A0, A2, C1 or C13
# Caller passes a Pin object. Pullup configuration does not work: if a switch is used
# an external pull up or down is required.
class WakeupPin:
def __init__(self, pin, rising=True):
if not d_series:
raise ValueError('Only valid on Pyboard D')
# Raise ValueError on invalid pin
self.idx = ('A0', 'A2', 'C1', 'C13').index(pin.name())
self.disable()
self.pin = pin
self.rising = rising
def enable(self):
cr2 = 0x3f
if not self.rising:
cr2 |= (0x100 << self.idx) # Set WUPP bit if falling edge
stm.mem32[stm.PWR + stm.PWR_CR2] |= cr2 # Clear all power wakeup flags, set WUPP for current pin
stm.mem32[stm.PWR + stm.PWR_CSR2] |= (0x100 << self.idx) # Enable current pin wakeup
def disable(self):
stm.mem32[stm.PWR + stm.PWR_CSR2] &= ~(0x100 << self.idx) # Disable wakeup
def wait_inactive(self):
while self.pin.value() == self.rising: # Wait for pin to go inactive
lpdelay(50)
def pinvalue(self):
return self.pin.value()
def state(self):
return self.pin.value() == self.rising
# ***** RTC TIMER SUPPORT *****
def bcd(x): # integer to BCD (2 digit max)
return (x % 10) + ((x//10) << 4)
class Alarm:
instantiated = False
def __init__(self, ident):
if not ident in ('a','A','b','B'):
raise ValueError("Alarm iIdent must be 'A' or 'B'")
self.ident = ident.lower()
if self.ident == 'a':
self.alclear = 0xffeeff
self.alenable = 0x1100
self.alreg = stm.RTC_ALRMAR
self.alisr = 0x1feff
self.albit = 1
else:
self.alclear = 0xffddff
self.alenable = 0x2200
self.alreg = stm.RTC_ALRMBR
self.alisr = 0x1fdff
self.albit = 2
self.uval = 0
self.lval = 0
if not Alarm.instantiated:
BIT17 = 1 << 17
Alarm.instantiated = True
stm.mem32[stm.EXTI + stm.EXTI_IMR] |= BIT17 # Set up ext interrupt
stm.mem32[stm.EXTI + stm.EXTI_RTSR] |= BIT17 # Rising edge
stm.mem32[stm.EXTI + stm.EXTI_PR] |= BIT17 # Clear pending bit
def timeset(self, *, day_of_month = None, weekday = None, hour = None, minute = None, second = None):
self.uval = 0x8080 # Mask everything off
self.lval = 0x8080
setlower = False
if day_of_month is not None:
bounds(day_of_month, 1 , 31, "Day of month must be between 1 and 31")
self.uval &= 0x7fff # Unmask day
self.uval |= (bcd(day_of_month) << 8)
setlower = True
elif weekday is not None:
bounds(weekday, 1, 7, "Weekday must be from 1 (Monday) to 7")
self.uval &= 0x7fff # Unmask day
self.uval |= 0x4000 # Indicate day of week
self.uval |= (weekday << 8)
setlower = True
if hour is not None:
bounds(hour, 0, 23, "Hour must be 0 to 23")
self.uval &= 0xff3f # Unmask hour, force 24 hour format
self.uval |= bcd(hour)
setlower = True
elif setlower:
self.uval &= 0xff3f # Unmask hour, force 24 hour format
if minute is not None:
bounds(minute, 0, 59, "Minute must be 0 to 59")
self.lval &= 0x7fff # Unmask minute
self.lval |= (bcd(minute) << 8)
setlower = True
elif setlower:
self.lval &= 0x7fff # Unmask minute
if second is not None:
bounds(second, 0, 59, "Second must be 0 to 59")
self.lval &= 0xff7f # Unmask second
self.lval |= bcd(second)
elif setlower:
self.lval &= 0xff7f # Unmask second
stm.mem32[stm.RTC + stm.RTC_WPR] |= 0xCA # enable write
stm.mem32[stm.RTC + stm.RTC_WPR] |= 0x53
stm.mem32[stm.RTC + stm.RTC_CR] &= self.alclear # Clear ALRxE in RTC_CR to disable Alarm
if self.uval == 0x8080 and self.lval == 0x8080: # No alarm set: disable
stm.mem32[stm.RTC + stm.RTC_WPR] = 0xff # Write protect
return
pyb.delay(5)
if stm.mem32[stm.RTC + stm.RTC_ISR] & self.albit : # test ALRxWF IN RTC_ISR
stm.mem32[stm.RTC + self.alreg] = self.lval + (self.uval << 16)
stm.mem32[stm.RTC + stm.RTC_ISR] &= self.alisr # Clear the RTC alarm ALRxF flag
if d_series:
stm.mem32[stm.PWR + stm.PWR_CR2] |= 0x3f # Clear power wakeup flag WUF
else:
stm.mem32[stm.PWR + stm.PWR_CR] |= 4 # Clear the PWR Wakeup (WUF) flag
stm.mem32[stm.RTC+stm.RTC_CR] |= self.alenable # Enable the RTC alarm and interrupt
stm.mem32[stm.RTC + stm.RTC_WPR] = 0xff
else:
raise OSError("Can't access alarm " + self.ident)
# Return the reason for a wakeup event.
# machine.reset_cause() should be used initially, see UPOWER.md
def why():
result = None
rtc_isr = stm.mem32[stm.RTC + stm.RTC_ISR]
if rtc_isr & 0x2000:
result = 'TAMPER'
elif rtc_isr & 0x400:
result = 'WAKEUP'
elif rtc_isr & 0x200:
stm.mem32[stm.RTC + stm.RTC_ISR] |= 0x200
result = 'ALARM_B'
elif rtc_isr & 0x100 :
stm.mem32[stm.RTC + stm.RTC_ISR] |= 0x100
result = 'ALARM_A'
else:
if d_series:
r = stm.mem32[stm.PWR + stm.PWR_CSR2] & 0xf
if r > 0:
result = ('X1', 'X3', 'C1', 'C13')[ctz(r)]
else:
if stm.mem32[stm.PWR + stm.PWR_CSR] & 1: # WUF set: the only remaining cause is X1 (?)
result = 'X1' # if WUF not set, cause unknown, return None
if d_series:
stm.mem32[stm.PWR + stm.PWR_CR2] |= 0x3f # Clear power wakeup flag WUF
else:
stm.mem32[stm.PWR + stm.PWR_CR] |= 4 # Clear the PWR Wakeup (WUF) flag
return result
def bkpram_ok():
bkpram = BkpRAM()
if bkpram[1023] == 0x27288a6f: # backup RAM has been used before
return True
else:
bkpram[1023] = 0x27288a6f
return False
# Return the current time from the RTC in millisecs from year 2000
def now():
rtc = pyb.RTC()
secs = utime.time()
if d_series:
ms = rtc.datetime()[7] // 1000
else:
ms = 1000*(255 -rtc.datetime()[7]) >> 8
if ms < 50: # Might have just rolled over
secs = utime.time()
return ms + 1000 * secs
# An elapsed_ms function which works during lpdelays
def lp_elapsed_ms(tstart):
return now() - tstart
# Save the current time in mS
def savetime(addr = 1021):
bkpram = BkpRAM()
bkpram[addr], bkpram[addr +1] = divmod(now(), 1000)
# Return the number of mS outstanding from a delay of delta mS
def ms_left(delta, addr = 1021):
bkpram = BkpRAM()
if not (bkpram[addr +1] <= 1000 and bkpram[addr +1] >= 0):
raise RTCError("Time data not saved.")
start_ms = 1000 * bkpram[addr] + bkpram[addr +1]
now_ms = now()
result = max(start_ms + delta - now_ms, 0) # avoid -ve results where time was missed (e.g. power outage)
if result > delta:
raise RTCError("Invalid saved time data.")
return result
def adcread(chan): # 16 temp 17 vbat 18 vref
bounds(chan, 16, 18, 'Invalid ADC channel')
start = pyb.millis()
timeout = 100
stm.mem32[stm.RCC + stm.RCC_APB2ENR] |= 0x100 # enable ADC1 clock.0x4100
stm.mem32[stm.ADC1 + stm.ADC_CR2] = 1 # Turn on ADC
stm.mem32[stm.ADC1 + stm.ADC_CR1] = 0 # 12 bit
if chan == 17:
stm.mem32[stm.ADC1 + stm.ADC_SMPR1] = 0x200000 # 15 cycles channel 17
stm.mem32[stm.ADC + 4] = 1 << 23
elif chan == 18:
stm.mem32[stm.ADC1 + stm.ADC_SMPR1] = 0x1000000 # 15 cycles channel 18 0x1200000
stm.mem32[stm.ADC + 4] = 0xc00000
else:
stm.mem32[stm.ADC1 + stm.ADC_SMPR1] = 0x40000 # 15 cycles channel 16
stm.mem32[stm.ADC + 4] = 1 << 23
stm.mem32[stm.ADC1 + stm.ADC_SQR3] = chan
stm.mem32[stm.ADC1 + stm.ADC_CR2] = 1 | (1 << 30) | (1 << 10) # start conversion
while not stm.mem32[stm.ADC1 + stm.ADC_SR] & 2: # wait for EOC
if pyb.elapsed_millis(start) > timeout:
raise OSError('ADC timout')
data = stm.mem32[stm.ADC1 + stm.ADC_DR] # clears down EOC
stm.mem32[stm.ADC1 + stm.ADC_CR2] = 0 # Turn off ADC
return data
def v33():
return 4096 * 1.21 / adcread(17)
def vbat():
return 1.21 * 2 * adcread(18) / adcread(17) # 2:1 divider on Vbat channel
def vref():
return 3.3 * adcread(17) / 4096
def temperature():
return 25 + 400 * (3.3 * adcread(16) / 4096 - 0.76)
# ********** TEST CODE **********
def ms_set(): # For debug purposes only. Decodes outcome of setting rtc.wakeup().
dividers = (16, 8, 4, 2)
wucksel = stm.mem32[stm.RTC + stm.RTC_CR] & 7
div = dividers[wucksel & 3]
wut = stm.mem32[stm.RTC + stm.RTC_WUTR] & 0xffff
clock_period = div/32768 if wucksel < 4 else 1.0 # seconds
period = clock_period * wut if wucksel < 6 else clock_period * (wut + 0x10000)
return 1000 * period