""" Test script for an NXP PCF8523T real-time clock on a Pico 2. Wiring: SDA=GPIO4, SCL=GPIO5 (I2C0), device address 0x51. NOTE: if the FT6336U touch controller (address 0x38) is on this same bus, the scan below will list it too. That is fine, they have different addresses. """ from machine import Pin, I2C from time import sleep_ms, mktime, localtime # ---------------- Settings ---------------- ADDR = 0x51 I2C_FREQ = 400_000 # Set to a (year, month, day, hour, minute, second) tuple to set the clock # at the start of the test, or None to leave the current time alone. SET_TIME_TO = (2026, 10, 18, 12, 0, 0) # The chip powers up with battery switchover DISABLED (PM = 111), so it will # lose the time when main power is removed. Set True to enable switchover # (PM = 000) if you have a backup battery or supercap fitted. SET_BATTERY_SWITCHOVER = False # ---------------- Registers ---------------- REG_CONTROL_1 = 0x00 REG_CONTROL_2 = 0x01 REG_CONTROL_3 = 0x02 REG_SECONDS = 0x03 # bit 7 = OS (oscillator stop flag) REG_OFFSET = 0x0E REG_CLKOUT = 0x0F CTRL1_STOP = 0x20 CTRL1_12_24 = 0x08 # 1 = 12 hour mode, 0 = 24 hour mode CTRL3_PM_MASK = 0xE0 CTRL3_BSF = 0x08 CTRL3_BLF = 0x04 # ---------------- Hardware ---------------- i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=I2C_FREQ) # ---------------- Helpers ---------------- def bcd2dec(b): return (b >> 4) * 10 + (b & 0x0F) def dec2bcd(d): return ((d // 10) << 4) | (d % 10) def read_reg(reg, n=1): return i2c.readfrom_mem(ADDR, reg, n) def write_reg(reg, data): i2c.writeto_mem(ADDR, reg, bytes(data)) def read_time(): """Return (year, month, day, hour, minute, second, weekday, os_flag).""" d = read_reg(REG_SECONDS, 7) return (2000 + bcd2dec(d[6]), bcd2dec(d[5] & 0x1F), bcd2dec(d[3] & 0x3F), bcd2dec(d[2] & 0x3F), bcd2dec(d[1] & 0x7F), bcd2dec(d[0] & 0x7F), d[4] & 0x07, bool(d[0] & 0x80)) def write_time(year, month, day, hour, minute, second): # Weekday: 0 = Monday ... 6 = Sunday (MicroPython convention) wd = localtime(mktime((year, month, day, hour, minute, second, 0, 0)))[6] write_reg(REG_SECONDS, (dec2bcd(second), # also clears the OS flag dec2bcd(minute), dec2bcd(hour), dec2bcd(day), wd, dec2bcd(month), dec2bcd(year - 2000))) def as_seconds(t): return mktime((t[0], t[1], t[2], t[3], t[4], t[5], 0, 0)) def fmt(t): return "%04d-%02d-%02d %02d:%02d:%02d (weekday %d)" % t[:7] def valid(t): return (2000 <= t[0] <= 2099 and 1 <= t[1] <= 12 and 1 <= t[2] <= 31 and t[3] <= 23 and t[4] <= 59 and t[5] <= 59 and t[6] <= 6) # ---------------- Test framework ---------------- results = [] def check(name, ok, detail=""): results.append(ok) print("[%s] %s %s" % ("PASS" if ok else "FAIL", name, detail)) return ok def hexs(b): return " ".join("%02X" % x for x in b) # ---------------- Tests ---------------- def test_presence(): found = i2c.scan() print("[INFO] I2C devices found: %s" % ", ".join("0x%02X" % a for a in found)) return check("PCF8523 responds at 0x%02X" % ADDR, ADDR in found) def test_registers(): regs = read_reg(0x00, 0x14) print("[INFO] Registers 0x00-0x13:") for i in range(0, len(regs), 8): print(" %02X: %s" % (i, hexs(regs[i:i + 8]))) # Make sure the clock is running and in 24 hour mode c1 = read_reg(REG_CONTROL_1)[0] if c1 & (CTRL1_STOP | CTRL1_12_24): print("[INFO] Clearing STOP and 12-hour bits (Control_1 was 0x%02X)" % c1) write_reg(REG_CONTROL_1, (c1 & ~(CTRL1_STOP | CTRL1_12_24),)) c1 = read_reg(REG_CONTROL_1)[0] check("Clock running, 24 hour mode", (c1 & (CTRL1_STOP | CTRL1_12_24)) == 0, "Control_1=0x%02X" % c1) def test_set_and_read_time(): if SET_TIME_TO is not None: write_time(*SET_TIME_TO) print("[INFO] Time set to %04d-%02d-%02d %02d:%02d:%02d" % SET_TIME_TO) sleep_ms(50) t = read_time() # Allow a second of drift between the write and the read delta = as_seconds(t) - mktime(SET_TIME_TO + (0, 0)) check("Time readback matches what was set", 0 <= delta <= 1, fmt(t)) else: t = read_time() print("[INFO] Current time: %s" % fmt(t)) check("Time registers hold valid values", valid(t)) t = read_time() check("Oscillator stop flag (OS) clear", not t[7], "" if not t[7] else "(OS set: time is unreliable until the time is written)") def test_oscillator(): t1 = read_time() sleep_ms(3100) t2 = read_time() delta = as_seconds(t2) - as_seconds(t1) check("Clock advances in real time (3.1 s wait)", delta in (3, 4), "(%d s counted: %s -> %s)" % (delta, fmt(t1)[11:19], fmt(t2)[11:19])) def test_register_readwrite(): # The offset register is harmless to change, so use it for a readback test original = read_reg(REG_OFFSET)[0] ok = True for value in (0x00, 0x55, 0x2A, 0x7F): write_reg(REG_OFFSET, (value,)) if read_reg(REG_OFFSET)[0] != value: ok = False write_reg(REG_OFFSET, (original,)) check("Offset register write/readback", ok and read_reg(REG_OFFSET)[0] == original) def test_battery(): c3 = read_reg(REG_CONTROL_3)[0] if SET_BATTERY_SWITCHOVER and (c3 & CTRL3_PM_MASK) != 0: write_reg(REG_CONTROL_3, (c3 & ~CTRL3_PM_MASK,)) # PM = 000 c3 = read_reg(REG_CONTROL_3)[0] print("[INFO] Battery switchover enabled (PM = 000)") pm = c3 >> 5 print("[INFO] Control_3 = 0x%02X, PM = %s" % (c3, "{:03b}".format(pm))) if pm == 0b111: print("[INFO] Battery switchover is DISABLED (the power-on default). The") print(" time will be lost if main power is removed. Set") print(" SET_BATTERY_SWITCHOVER = True if a backup battery is fitted.") elif pm == 0b000: check("Battery low flag clear", not (c3 & CTRL3_BLF), "(battery low!)" if c3 & CTRL3_BLF else "") else: print("[INFO] Battery switchover mode is PM = %s" % "{:03b}".format(pm)) # ---------------- Run ---------------- def main(): print("PCF8523T RTC test, I2C0 SDA=GPIO4 SCL=GPIO5 @ %d Hz" % I2C_FREQ) print("-" * 40) try: if not test_presence(): print("No response at 0x%02X: check wiring, pull-ups and power." % ADDR) else: test_registers() test_set_and_read_time() test_oscillator() test_register_readwrite() test_battery() except Exception as e: check("Unexpected exception", False, repr(e)) print("-" * 40) print("%d of %d checks passed" % (sum(results), len(results))) main()