diff --git a/README.rst b/README.rst index ca4d4a3..5427270 100644 --- a/README.rst +++ b/README.rst @@ -79,7 +79,7 @@ Of course, you must import the library to use it: .. code:: python3 import time - import adafruit_pcf8523 + from adafruit_pcf8523.pcf8523 import PCF8523 All the Adafruit RTC libraries take an instantiated and active I2C object (from the `board` library) as an argument to their constructor. The way to @@ -101,7 +101,7 @@ the RTC object: .. code:: python3 - rtc = adafruit_pcf8523.PCF8523(i2c) + rtc = PCF8523(i2c) Date and time ------------- diff --git a/adafruit_pcf8523/clock.py b/adafruit_pcf8523/clock.py new file mode 100644 index 0000000..fc59006 --- /dev/null +++ b/adafruit_pcf8523/clock.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2016 Philip R. Moyer for Adafruit Industries +# SPDX-FileCopyrightText: 2016 Radomir Dopieralski for Adafruit Industries +# SPDX-FileCopyrightText: Copyright (c) 2023 Bernhard Bablok +# +# SPDX-License-Identifier: MIT + +""" +`clock` - PCF8523 Clock module +============================== + +This class supports the clkout-feature of the PCF8523-based RTC in CircuitPython. + +Functions are included for reading and writing registers to configure +clklout frequency. + +The class supports stand-alone usage. In this case, pass an i2-bus object +to the constructor. If used together with the PCF8523 class (rtc), instantiate +the rtc-object first and then pass the i2c_device attribute of the rtc +to the constructor of the clock. + +Author(s): Bernhard Bablok +Date: September 2023 + +Implementation Notes +-------------------- + +**Hardware:** + +* Adafruit `Adalogger FeatherWing - RTC + SD Add-on `_ + (Product ID: 2922) +* Adafruit `PCF8523 RTC breakout `_ (Product ID: 3295) + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register + +* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice + +**Notes:** + +#. Milliseconds are not supported by this RTC. +#. The alarm does not support seconds. It will always fire on full minutes. +#. Datasheet: http://cache.nxp.com/documents/data_sheet/PCF8523.pdf + +""" + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PCF8523.git" + +import time + +from adafruit_bus_device.i2c_device import I2CDevice +from adafruit_register import i2c_bits +from micropython import const + +try: + from typing import Union + from busio import I2C +except ImportError: + pass + + +class Clock: # pylint: disable=too-few-public-methods + """Interface to the clkout of the PCF8523 RTC. + + :param I2C i2c_bus: The I2C bus object + """ + + clockout_frequency = i2c_bits.RWBits(3, 0x0F, 3) # COF[2:0] + """Clock output frequencies generated. Default is 32.768kHz. + Possible values are as shown (selection value - frequency). + 000 - 32.768khz + 001 - 16.384khz + 010 - 8.192kHz + 011 - 4.096kHz + 100 - 1.024kHz + 101 - 0.032kHz (32Hz) + 110 - 0.001kHz (1Hz) + 111 - Disabled + """ + + CLOCKOUT_FREQ_32KHZ = const(0b000) + """Clock frequency of 32 KHz""" + CLOCKOUT_FREQ_16KHZ = const(0b001) + """Clock frequency of 16 KHz""" + CLOCKOUT_FREQ_8KHZ = const(0b010) + """Clock frequency of 8 KHz""" + CLOCKOUT_FREQ_4KHZ = const(0b011) + """Clock frequency of 4 KHz""" + CLOCKOUT_FREQ_1KHZ = const(0b100) + """Clock frequency of 4 KHz""" + CLOCKOUT_FREQ_32HZ = const(0b101) + """Clock frequency of 32 Hz""" + CLOCKOUT_FREQ_1HZ = const(0b110) + """Clock frequency of 1 Hz""" + CLOCKOUT_FREQ_DISABLED = const(0b111) + """Clock output disabled""" + + def __init__(self, i2c: Union[I2C, I2CDevice]) -> None: + if isinstance(i2c, I2CDevice): + self.i2c_device = i2c # reuse i2c_device (from PCF8563-instance) + else: + time.sleep(0.05) + self.i2c_device = I2CDevice(i2c, 0x68) diff --git a/adafruit_pcf8523.py b/adafruit_pcf8523/pcf8523.py similarity index 84% rename from adafruit_pcf8523.py rename to adafruit_pcf8523/pcf8523.py index c6e04e2..65f201b 100644 --- a/adafruit_pcf8523.py +++ b/adafruit_pcf8523/pcf8523.py @@ -4,8 +4,8 @@ # SPDX-License-Identifier: MIT """ -`adafruit_pcf8523` - PCF8523 Real Time Clock module -==================================================== +`pcf8523` - PCF8523 Real Time Clock module +========================================== This library supports the use of the PCF8523-based RTC in CircuitPython. It contains a base RTC class used by all Adafruit RTC libraries. This base @@ -39,6 +39,7 @@ class is inherited by the chip-specific subclasses. **Notes:** #. Milliseconds are not supported by this RTC. +#. The alarm does not support seconds. It will always fire on full minutes. #. Datasheet: http://cache.nxp.com/documents/data_sheet/PCF8523.pdf """ @@ -106,32 +107,21 @@ class PCF8523: power_management = i2c_bits.RWBits(3, 0x02, 5) """Power management state that dictates battery switchover, power sources - and low battery detection. Defaults to BATTERY_SWITCHOVER_OFF (0b000).""" + and low battery detection. Defaults to BATTERY_SWITCHOVER_OFF (0b111).""" # The False means that day comes before weekday in the registers. The 0 is # that the first day of the week is value 0 and not 1. datetime_register = i2c_bcd_datetime.BCDDateTimeRegister(0x03, False, 0) """Current date and time.""" - clockout_frequency = i2c_bits.RWBits(3, 0x0F, 3) - """Clock output frequencies generated. Default is 32.768kHz. - Possible values are as shown (selection value - frequency). - 000 - 32.768khz - 001 - 16.384khz - 010 - 8.192kHz - 011 - 4.096kHz - 100 - 1.024kHz - 101 - 0.032kHz (32Hz) - 110 - 0.001kHz (1Hz) - 111 - Disabled - """ - # The False means that day and weekday share a register. The 0 is that the # first day of the week is value 0 and not 1. alarm = i2c_bcd_alarm.BCDAlarmTimeRegister( 0x0A, has_seconds=False, weekday_shared=False, weekday_start=0 ) - """Alarm time for the first alarm.""" + """Alarm time for the first alarm. Note that the value of the seconds-fields + is ignored, i.e. alarms only fire at full minutes. For short-term + alarms, use a timer instead.""" alarm_interrupt = i2c_bit.RWBit(0x00, 1) """True if the interrupt pin will output when alarm is alarming.""" @@ -159,17 +149,6 @@ class PCF8523: def __init__(self, i2c_bus: I2C): self.i2c_device = I2CDevice(i2c_bus, 0x68) - # Try and verify this is the RTC we expect by checking the timer B - # frequency control bits which are 1 on reset and shouldn't ever be - # changed. - buf = bytearray(2) - buf[0] = 0x12 - with self.i2c_device as i2c: - i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) - - if (buf[1] & 0b00000111) != 0b00000111: - raise ValueError("Unable to find PCF8523 at i2c address 0x68.") - @property def datetime(self) -> struct_time: """Gets the current date and time or sets the current date and time then starts the diff --git a/adafruit_pcf8523/timer.py b/adafruit_pcf8523/timer.py new file mode 100644 index 0000000..ad3dd8f --- /dev/null +++ b/adafruit_pcf8523/timer.py @@ -0,0 +1,168 @@ +# SPDX-FileCopyrightText: 2016 Philip R. Moyer for Adafruit Industries +# SPDX-FileCopyrightText: 2016 Radomir Dopieralski for Adafruit Industries +# SPDX-FileCopyrightText: Copyright (c) 2023 Bernhard Bablok +# +# SPDX-License-Identifier: MIT + +""" +`timer` - PCF8523 Timer module +============================== + +This class supports the timer of the PCF8523-based RTC in CircuitPython. + +Functions are included for reading and writing registers and manipulating +timer objects. + +The PCF8523 support two timers named Tmr_A and Tmr_B in the datasheet. +For compatibility with the PCF8563, the Tmr_A is named timer while the +second timer is named timerB. + +The class supports stand-alone usage. In this case, pass an i2-bus object +to the constructor. If used together with the PCF8523 class (rtc), instantiate +the rtc-object first and then pass the i2c_device attribute of the rtc +to the constructor of the timer. + +Author(s): Bernhard Bablok +Date: September 2023 + +Implementation Notes +-------------------- + +**Hardware:** + +* Adafruit `Adalogger FeatherWing - RTC + SD Add-on `_ + (Product ID: 2922) +* Adafruit `PCF8523 RTC breakout `_ (Product ID: 3295) + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register + +* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice + +**Notes:** + +#. Milliseconds are not supported by this RTC. +#. The alarm does not support seconds. It will always fire on full minutes. +#. Datasheet: http://cache.nxp.com/documents/data_sheet/PCF8523.pdf + +""" + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PCF8523.git" + +import time + +from adafruit_bus_device.i2c_device import I2CDevice +from adafruit_register import i2c_bit +from adafruit_register import i2c_bits +from micropython import const + +try: + from typing import Union + from busio import I2C +except ImportError: + pass + + +class Timer: # pylint: disable=too-few-public-methods + """Interface to the timer of the PCF8563 RTC. + + :param I2C i2c_bus: The I2C bus object + """ + + timer_enabled = i2c_bits.RWBits(2, 0x0F, 1) # TAC[1:0] + """Configures timer. Possible values: + 00 - disabled + 01 - enabled as countdown timer + 10 - enabled as watchdog timer + 11 - disabled + """ + + timer_frequency = i2c_bits.RWBits(3, 0x10, 0) # TAQ[2:0] + """TimerA clock frequency. Default is 1/3600Hz. + Possible values are as shown (selection value - frequency). + 000 - 4.096kHz + 001 - 64Hz + 010 - 1Hz + 011 - 1/60Hz + 111 - 1/3600Hz + """ + + TIMER_FREQ_4KHZ = const(0b000) + """Timer frequency of 4 KHz""" + TIMER_FREQ_64HZ = const(0b001) + """Timer frequency of 64 Hz""" + TIMER_FREQ_1HZ = const(0b010) + """Timer frequency of 1 Hz""" + TIMER_FREQ_1_60HZ = const(0b011) + """Timer frequency of 1/60 Hz""" + TIMER_FREQ_1_3600HZ = const(0b111) + """Timer frequency of 1/3600 Hz""" + + timer_value = i2c_bits.RWBits(8, 0x11, 0) # T_A[7:0] + """ TimerA value (0-255). The default is undefined. + The total countdown duration is calcuated by + timer_value/timer_frequency. For a higher precision, use higher values + and frequencies, e.g. for a one minute timer you could use + value=1, frequency=1/60Hz or value=60, frequency=1Hz. The + latter will give better results. See the PCF85x3 User's Manual + for details.""" + + timer_interrupt = i2c_bit.RWBit(0x01, 1) # CTAIE + """True if the interrupt pin will assert when timer has elapsed. + Defaults to False.""" + + timer_watchdog = i2c_bit.RWBit(0x01, 2) # WTAIE + """True if the interrupt pin will output when timer generates a + watchdog-alarm. Defaults to False.""" + + timer_status = i2c_bit.RWBit(0x01, 6) # CTAF + """True if timer has elapsed. Set to False to reset.""" + + timer_pulsed = i2c_bit.RWBit(0x0F, 7) # TAM + """True if timer asserts INT as a pulse. The default + value False asserts INT permanently.""" + + timerB_enabled = i2c_bit.RWBit(0x0F, 0) # TBC + """True if the timerB is enabled. Default is False.""" + + timerB_frequency = i2c_bits.RWBits(3, 0x12, 0) # TBQ[2:0] + """TimerB clock frequency. Default is 1/3600Hz. + Possible values are as shown (selection value - frequency). + 000 - 4.096kHz + 001 - 64Hz + 010 - 1Hz + 011 - 1/60Hz + 111 - 1/3600Hz + """ + + timerB_value = i2c_bits.RWBits(8, 0x13, 0) # T_B[7:0] + """ TimerB value (0-255). The default is undefined. + The total countdown duration is calcuated by + timerB_value/timerB_frequency. For a higher precision, use higher values + and frequencies, e.g. for a one minute timer you could use + value=1, frequency=1/60Hz or value=60, frequency=1Hz. The + latter will give better results. See the PCF85x3 User's Manual + for details.""" + + timerB_interrupt = i2c_bit.RWBit(0x01, 0) # CTBIE + """True if the interrupt pin will assert when timerB has elapsed. + Defaults to False.""" + + timerB_status = i2c_bit.RWBit(0x01, 5) # CTBF + """True if timerB has elapsed. Set to False to reset.""" + + timerB_pulsed = i2c_bit.RWBit(0x0F, 6) # TBM + """True if timerB asserts INT as a pulse. The default + value False asserts INT permanently.""" + + def __init__(self, i2c: Union[I2C, I2CDevice]) -> None: + if isinstance(i2c, I2CDevice): + self.i2c_device = i2c # reuse i2c_device (from PCF8523-instance) + else: + time.sleep(0.05) + self.i2c_device = I2CDevice(i2c, 0x68) diff --git a/docs/api.rst b/docs/api.rst index 7572842..f16a851 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,3 +1,9 @@ -.. automodule:: adafruit_pcf8523 +.. automodule:: adafruit_pcf8523.pcf8523 + :members: + +.. automodule:: adafruit_pcf8523.timer + :members: + +.. automodule:: adafruit_pcf8523.clock :members: diff --git a/docs/conf.py b/docs/conf.py index 48a178d..2c6085e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -39,7 +39,7 @@ # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -# autodoc_mock_imports = ["adafruit_bus_device", "adafruit_register"] +autodoc_mock_imports = ["adafruit_bus_device", "adafruit_register"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] diff --git a/examples/pcf8523_clockout.py b/examples/pcf8523_clockout.py new file mode 100644 index 0000000..3c8d629 --- /dev/null +++ b/examples/pcf8523_clockout.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2023 Bernhard Bablok +# SPDX-License-Identifier: MIT + +# Simple demo for clockout-mode (square-wave generation) +# Note that for 32kHz, the duty-cycle is from 60:40 to 40:60, thus +# it is not a perfect square wave (see datasheet 8.9.1.2) + +import time +import board +import busio +import countio +from digitalio import Pull +from adafruit_pcf8523.clock import Clock + +PIN_SDA = board.GP2 # connect to RTC +PIN_SCL = board.GP3 # connect to RTC +# use board.SCL and board.SDA if available + +i2c = busio.I2C(PIN_SCL, PIN_SDA) +# or i2c = board.I2C() if available +clock = Clock(i2c) + +# pin must support countio +PIN_COUT = board.GP5 +counter = countio.Counter(pin=PIN_COUT, edge=countio.Edge.RISE, pull=Pull.UP) +DURATION = 10 + +# Main loop: +while True: + # disable clockout + print(f"testing disabled clock for {DURATION} seconds") + clock.clockout_frequency = clock.CLOCKOUT_FREQ_DISABLED + counter.reset() + time.sleep(DURATION) + print(f"clock-pulses: {counter.count}") + print(f"clock-freq: {counter.count/DURATION}") + + # test 32kHz + print(f"testing 32 kHz clock for {DURATION} seconds") + clock.clockout_frequency = clock.CLOCKOUT_FREQ_32KHZ + counter.reset() + time.sleep(DURATION) + clock.clockout_frequency = clock.CLOCKOUT_FREQ_DISABLED + print(f"clock-pulses: {counter.count}") + print(f"clock-freq: {counter.count/DURATION}") + + # test 4kHz + print(f"testing 4 kHz clock for {DURATION} seconds") + clock.clockout_frequency = clock.CLOCKOUT_FREQ_4KHZ + counter.reset() + time.sleep(DURATION) + clock.clockout_frequency = clock.CLOCKOUT_FREQ_DISABLED + print(f"clock-pulses: {counter.count}") + print(f"clock-freq: {counter.count/DURATION}") + + # test 1Hz + print(f"testing 1 Hz clock for {DURATION} seconds") + clock.clockout_frequency = clock.CLOCKOUT_FREQ_1HZ + counter.reset() + time.sleep(DURATION) + clock.clockout_frequency = clock.CLOCKOUT_FREQ_DISABLED + print(f"clock-pulses: {counter.count}") + print(f"clock-freq: {counter.count/DURATION}") diff --git a/examples/pcf8523_simpletest.py b/examples/pcf8523_simpletest.py index 2604985..847643a 100644 --- a/examples/pcf8523_simpletest.py +++ b/examples/pcf8523_simpletest.py @@ -8,11 +8,11 @@ import time import board -import adafruit_pcf8523 +from adafruit_pcf8523.pcf8523 import PCF8523 i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller -rtc = adafruit_pcf8523.PCF8523(i2c) +rtc = PCF8523(i2c) # Lookup table for names of days (nicer printing). days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") diff --git a/examples/pcf8523_timer_flag.py b/examples/pcf8523_timer_flag.py new file mode 100644 index 0000000..ed646d9 --- /dev/null +++ b/examples/pcf8523_timer_flag.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2023 Bernhard Bablok +# SPDX-License-Identifier: MIT + +# Simple demo for timer operation using the timer-flag + +import time +import board +import busio +from adafruit_pcf8523.timer import Timer +from adafruit_pcf8523.clock import Clock + +LOW_FREQ_TIMER = 10 +HIGH_FREQ_TIMER = 0.02 +HIGH_FREQ_TIME = 10 +PIN_SDA = board.GP2 +PIN_SCL = board.GP3 +# use board.SCL and board.SDA if available + +i2c = busio.I2C(PIN_SCL, PIN_SDA) +# or i2c = board.I2C() if available +timer = Timer(i2c) +clock = Clock(timer.i2c_device) +clock.clockout_frequency = clock.CLOCKOUT_FREQ_DISABLED + +# Main loop: +while True: + print("low-frequency timer: checking timer-flag") + timer.timer_enabled = False + timer.timer_status = False + timer.timer_frequency = timer.TIMER_FREQ_1HZ + timer.timer_value = LOW_FREQ_TIMER + start = time.monotonic() + timer.timer_enabled = True + while not timer.timer_status and time.monotonic() - start < LOW_FREQ_TIMER + 1: + pass + if not timer.timer_status: + # shoud not happen! + print(f"error: timer did not fire within {LOW_FREQ_TIMER+1} seconds!") + else: + elapsed = time.monotonic() - start + print(f"elapsed: {elapsed}") + + print("high-frequency timer: checking timer-flag") + timer.timer_enabled = False + timer.timer_status = False + timer.timer_frequency = timer.TIMER_FREQ_4KHZ + timer.timer_value = min(round(HIGH_FREQ_TIMER * 4096), 255) + counter = 0 + start = time.monotonic() + end = start + HIGH_FREQ_TIME + timer.timer_enabled = True + while time.monotonic() < end: + if not timer.timer_status: + continue + timer.timer_status = False + counter += 1 + if counter > 0: + mean_interval = (time.monotonic() - start) / counter + print(f"interval requested: {HIGH_FREQ_TIMER}") + print(f"interval observed: {mean_interval} (mean of {counter} alarms)") + else: + print(f"error: timer did not fire within {HIGH_FREQ_TIME} seconds!") + print("error: timer did not fire") diff --git a/examples/pcf8523_timer_interrupt.py b/examples/pcf8523_timer_interrupt.py new file mode 100644 index 0000000..37d8c44 --- /dev/null +++ b/examples/pcf8523_timer_interrupt.py @@ -0,0 +1,72 @@ +# SPDX-FileCopyrightText: 2023 Bernhard Bablok +# SPDX-License-Identifier: MIT + +# Simple demo for timer operation, using the interrupt-pin + +import time +import board +import busio +from digitalio import DigitalInOut, Direction, Pull +from adafruit_pcf8523.timer import Timer +from adafruit_pcf8523.clock import Clock + +LOW_FREQ_TIMER = 10 +HIGH_FREQ_TIMER = 0.02 +HIGH_FREQ_TIME = 10 +PIN_INT = board.GP5 +PIN_SDA = board.GP2 +PIN_SCL = board.GP3 +# use board.SCL and board.SDA if available + +i2c = busio.I2C(PIN_SCL, PIN_SDA) +# or i2c = board.I2C() if available +timer = Timer(i2c) +clock = Clock(timer.i2c_device) +clock.clockout_frequency = clock.CLOCKOUT_FREQ_DISABLED + +# interrupt pin +intpin = DigitalInOut(PIN_INT) +intpin.direction = Direction.INPUT +intpin.pull = Pull.UP + +# Main loop: +timer.pulsed = False +timer.timer_interrupt = True +while True: + print("low-frequency timer: checking interrupt") + timer.timer_enabled = False + timer.timer_status = False + timer.timer_frequency = timer.TIMER_FREQ_1HZ + timer.timer_value = LOW_FREQ_TIMER + start = time.monotonic() + timer.timer_enabled = True + while intpin.value and time.monotonic() - start < LOW_FREQ_TIMER + 1: + pass + if intpin.value: + # shoud not happen! + print(f"error: timer did not fire within {LOW_FREQ_TIMER+1} seconds!") + else: + elapsed = time.monotonic() - start + print(f"elapsed: {elapsed}") + + print("high-frequency timer: checking interrupt") + timer.timer_enabled = False + timer.timer_status = False + timer.timer_frequency = timer.TIMER_FREQ_4KHZ + timer.timer_value = min(round(HIGH_FREQ_TIMER * 4096), 255) + counter = 0 + start = time.monotonic() + end = start + HIGH_FREQ_TIME + timer.timer_enabled = True + while time.monotonic() < end: + if intpin.value: + continue + timer.timer_status = False + counter += 1 + if counter > 0: + mean_interval = (time.monotonic() - start) / counter + print(f"interval requested: {HIGH_FREQ_TIMER}") + print(f"interval observed: {mean_interval} (mean of {counter} alarms)") + else: + print(f"error: timer did not fire within {HIGH_FREQ_TIME} seconds!") + print("error: timer did not fire") diff --git a/pyproject.toml b/pyproject.toml index 5ffa8d3..b98198f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ classifiers = [ dynamic = ["dependencies", "optional-dependencies"] [tool.setuptools] -py-modules = ["adafruit_pcf8523"] +packages = ["adafruit_pcf8523"] [tool.setuptools.dynamic] dependencies = {file = ["requirements.txt"]}