pimoroni-pico
pimoroni-pico copied to clipboard
PCF85063A library: can we set RTC timers longer than 255 ticks?
I'm having a lot of fun with the Inky Frame, but I've hit a stumbling block: how would I go about sleeping the Frame for longer than 255 seconds? I'd like it to update once an hour to conserve battery life.
This code:
HOLD_VSYS_EN_PIN = 2
hold_vsys_en_pin = Pin(HOLD_VSYS_EN_PIN, Pin.OUT)
hold_vsys_en_pin.value(True)
i2c = PimoroniI2C(I2C_SDA_PIN, I2C_SCL_PIN, 100000)
rtc = PCF85063A(i2c)
UPDATE_INTERVAL = 60 * 60
rtc.enable_timer_interrupt(True)
rtc.set_timer(UPDATE_INTERVAL)
hold_vsys_en_pin.init(Pin.IN)
time.sleep(UPDATE_INTERVAL)
Returns this error:
ValueError: ticks out of range. Expected 0 to 255
You have two choices:
- You can adjust the clock frequency of the timer to make 1 tick != 1 second and adjust your ticks amount accordingly.
- You can calculate a future date and use the alarm feature instead
The number of ticks is otherwise hard-limited to 255 by the 8-bit register in the PCF86063A.
You can accomplish 1 using the ttp
argument of set_timer
though the available values - afaict - are 64Hz and 4096Hz.
In your case 60 * 60 could be:
rtc.set_timer(60, ttp=TIMER_TICK_64HZ)
Which I think is equivalent to 60 * 64
and roughly converts the set_timer
value to minutes instead of seconds.
I spent some time trying to accomplish 2 cleanly, and have some code I'll have to dig up. Adding dates/times together is non-trivial and the comprehensive datetime library is rather big.
Here's a really cut down datetime.py
:
_DBM = (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334)
_DIM = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
def _leap(y):
return y % 4 == 0 and (y % 100 != 0 or y % 400 == 0)
def _dby(y):
# year -> number of days before January 1st of year.
Y = y - 1
return Y * 365 + Y // 4 - Y // 100 + Y // 400
def _dim(y, m):
# year, month -> number of days in that month in that year.
if m == 2 and _leap(y):
return 29
return _DIM[m]
def _dbm(y, m):
# year, month -> number of days in year preceding first day of month.
return _DBM[m] + (m > 2 and _leap(y))
def _ymd2o(y, m, d):
# y, month, day -> ordinal, considering 01-Jan-0001 as day 1.
return _dby(y) + _dbm(y, m) + d
def _o2ymd(n):
# ordinal -> (year, month, day), considering 01-Jan-0001 as day 1.
n -= 1
n400, n = divmod(n, 146_097)
y = n400 * 400 + 1
n100, n = divmod(n, 36_524)
n4, n = divmod(n, 1_461)
n1, n = divmod(n, 365)
y += n100 * 100 + n4 * 4 + n1
if n1 == 4 or n100 == 4:
return y - 1, 12, 31
m = (n + 50) >> 5
prec = _dbm(y, m)
if prec > n:
m -= 1
prec -= _dim(y, m)
n -= prec
return y, m, n + 1
MINYEAR = 1
MAXYEAR = 9_999
class timedelta:
def __init__(
self, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0
):
s = (((weeks * 7 + days) * 24 + hours) * 60 + minutes) * 60 + seconds
self._us = round((s * 1000 + milliseconds) * 1000 + microseconds)
def __neg__(self):
return timedelta(0, 0, -self._us)
def tuple(self):
d, us = divmod(self._us, 86_400_000_000)
s, us = divmod(us, 1_000_000)
h, s = divmod(s, 3600)
m, s = divmod(s, 60)
return d, h, m, s, us
def _date(y, m, d):
if MINYEAR <= y <= MAXYEAR and 1 <= m <= 12 and 1 <= d <= _dim(y, m):
return _ymd2o(y, m, d)
elif y == 0 and m == 0 and 1 <= d <= 3_652_059:
return d
else:
raise ValueError
def _time(h, m, s, us):
if (
0 <= h < 24
and 0 <= m < 60
and 0 <= s < 60
and 0 <= us < 1_000_000
) or (h == 0 and m == 0 and s == 0 and 0 < us < 86_400_000_000):
return timedelta(0, s, us, 0, m, h)
else:
raise ValueError
class datetime:
def __init__(
self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tz=0):
self._d = _date(year, month, day)
self._t = _time(hour, minute, second, microsecond)
def __add__(self, other):
us = self._t._us + other._us
d, us = divmod(us, 86_400_000_000)
d += self._d
return datetime(0, 0, d, 0, 0, 0, us)
def __sub__(self, other):
if isinstance(other, timedelta):
return self.__add__(-other)
elif isinstance(other, datetime):
d, us = self._sub(other)
return timedelta(d, 0, us)
else:
raise TypeError
def _sub(self, other):
# Subtract two datetime instances.
dt1 = self
dt2 = other
os1 = dt1.utcoffset()
os2 = dt2.utcoffset()
if os1 != os2:
dt1 -= os1
dt2 -= os2
D = dt1._d - dt2._d
us = dt1._t._us - dt2._t._us
d, us = divmod(us, 86_400_000_000)
return D + d, us
def tuple(self):
d = _o2ymd(self._d)
t = self._t.tuple()[1:]
return d + t
And an example adding 6 hours:
import time
import datetime
d = datetime.timedelta(hours=6)
t = datetime.datetime(*time.localtime()) + d
target_time = t.tuple()
print(target_time)
This can be used with set_alarm
.
This is very helpful, thank you - I will use it elsewhere.
I may have gone about this a naive way but I ended up simply taking the current hour, adding 1 modulo 24, and using that as the next hour for set_alarm
, copying the current minutes and seconds from the RTC. It means the update time drifts by about a minute each time as it takes that length of time for the frame to wake up and update, but it seems to do the trick - unless I've missed something fudnamental!
Based on https://github.com/pimoroni/pimoroni-pico/blob/ac2fa97e9673d0f59ccb0ac2b41eaa2a7283f033/libraries/inky_frame/inky_frame.cpp#L112-L120
I think
In your case 60 * 60 could be:
rtc.set_timer(60, ttp=TIMER_TICK_64HZ)
should be
rtc.set_timer(60, ttp=PCF85063A.TIMER_TICK_1_OVER_60HZ)
for 60 minute RP2040-power-off sleep. Having said that none of this code is working for me at the moment...
OK, so I'd really like to use an alarm rather than sleep and then have a callback function, but I can't figure out if this is even possible - am I looking in the wrong place for the PCF85063A python documentation? I can't find it anyway.
Might be helpful to someone else: I found I had to call rtc.reset()
before setting the RTC timer; otherwise, it only entered deep sleep the first time, and after subsequent awakenings, it would never deep sleep again. I guess that might be obvious to some, but I didn't see it documented in the guide or examples.
Using this with rtc.set_timer(60, ttp=PCF85063A.TIMER_TICK_1_OVER_60HZ)
(as mentioned above), I have it reliably sleeping for (close to) an hour at a time :)
I got it working in the end in https://github.com/kevinjwalters/micropython-examples/blob/master/pico-w/sdslideshow.py. I also am using rtc.reset()
but I've forgotten the details of the fiddling around I had to do to get this working.
Relevant parts of the code for rtc/sleeping are:
https://github.com/kevinjwalters/micropython-examples/blob/490f5d0cffc3d813dfd6fea0efce3cd90be148df/pico-w/sdslideshow.py#L133-L137
https://github.com/kevinjwalters/micropython-examples/blob/490f5d0cffc3d813dfd6fea0efce3cd90be148df/pico-w/sdslideshow.py#L237-L245
@turley Did you do any timing to check accuracy of the clock? I've given away the one I did some timings on but I was getting 146 seconds for a 180 second rtc sleep. I've got a replacement now so I look at this again...
@turley Did you do any timing to check accuracy of the clock? I've given away the one I did some timings on but I was getting 146 seconds for a 180 second rtc sleep. I've got a replacement now so I look at this again...
@kevinjwalters I have noticed that it tends to wake up earlier than intended, but no, I haven't done any proper measurements. For my project, the timing isn't super critical. Given that it does always seem early though, I assume one could measure and compensate for at least some of the inaccuracy when setting the timer.
RTC things should be much easier to do with the new Inky Frame helper module, so closing this one for now.