|
| 1 | +''' |
| 2 | +MIT License |
| 3 | +
|
| 4 | +Copyright (c) 2019 lewis he |
| 5 | +
|
| 6 | +Permission is hereby granted, free of charge, to any person obtaining a copy |
| 7 | +of this software and associated documentation files (the "Software"), to deal |
| 8 | +in the Software without restriction, including without limitation the rights |
| 9 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 10 | +copies of the Software, and to permit persons to whom the Software is |
| 11 | +furnished to do so, subject to the following conditions: |
| 12 | +
|
| 13 | +The above copyright notice and this permission notice shall be included in all |
| 14 | +copies or substantial portions of the Software. |
| 15 | +
|
| 16 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 17 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 18 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 19 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 20 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 22 | +SOFTWARE. |
| 23 | +
|
| 24 | +pcf8563.py - MicroPython library for NXP PCF8563 Real-time clock/calendar |
| 25 | +Created by Lewis he on September 17, 2019. |
| 26 | +github:https://github.com/lewisxhe/PCF8563_PythonLibrary |
| 27 | +''' |
| 28 | + |
| 29 | +from machine import I2C |
| 30 | +from time import localtime, sleep |
| 31 | +from gc import collect |
| 32 | +from driver.timezone import TZONE |
| 33 | +from micropython import const |
| 34 | + |
| 35 | +from .pahub import PAHUBUnit |
| 36 | +from .unit_helper import UnitError |
| 37 | + |
| 38 | +import sys |
| 39 | + |
| 40 | +if sys.platform != "esp32": |
| 41 | + from typing import Union |
| 42 | + |
| 43 | +PCF8563_SLAVE_ADDRESS = const(0x51) |
| 44 | +PCF8563_STAT1_REG = const(0x00) |
| 45 | +PCF8563_STAT2_REG = const(0x01) |
| 46 | +PCF8563_SEC_REG = const(0x02) |
| 47 | +PCF8563_MIN_REG = const(0x03) |
| 48 | +PCF8563_HR_REG = const(0x04) |
| 49 | +PCF8563_DAY_REG = const(0x05) |
| 50 | +PCF8563_WEEKDAY_REG = const(0x06) |
| 51 | +PCF8563_MONTH_REG = const(0x07) |
| 52 | +PCF8563_YEAR_REG = const(0x08) |
| 53 | +PCF8563_SQW_REG = const(0x0D) |
| 54 | +PCF8563_TIMER1_REG = const(0x0E) |
| 55 | +PCF8563_TIMER2_REG = const(0x0F) |
| 56 | +PCF8563_VOL_LOW_MASK = const(0x80) |
| 57 | +PCF8563_minuteS_MASK = const(0x7F) |
| 58 | +PCF8563_HOUR_MASK = const(0x3F) |
| 59 | +PCF8563_WEEKDAY_MASK = const(0x07) |
| 60 | +PCF8563_CENTURY_MASK = const(0x80) |
| 61 | +PCF8563_DAY_MASK = const(0x3F) |
| 62 | +PCF8563_MONTH_MASK = const(0x1F) |
| 63 | +PCF8563_TIMER_CTL_MASK = const(0x03) |
| 64 | +PCF8563_ALARM_AF = const(0x08) |
| 65 | +PCF8563_TIMER_TF = const(0x04) |
| 66 | +PCF8563_ALARM_AIE = const(0x02) |
| 67 | +PCF8563_TIMER_TIE = const(0x01) |
| 68 | +PCF8563_TIMER_TE = const(0x80) |
| 69 | +PCF8563_TIMER_TD10 = const(0x03) |
| 70 | +PCF8563_NO_ALARM = const(0xFF) |
| 71 | +PCF8563_ALARM_ENABLE = const(0x80) |
| 72 | +PCF8563_CLK_ENABLE = const(0x80) |
| 73 | +PCF8563_ALARM_MINUTES = const(0x09) |
| 74 | +PCF8563_ALARM_HOURS = const(0x0A) |
| 75 | +PCF8563_ALARM_DAY = const(0x0B) |
| 76 | +PCF8563_ALARM_WEEKDAY = const(0x0C) |
| 77 | + |
| 78 | +CLOCK_CLK_OUT_FREQ_32_DOT_768KHZ = const(0x80) |
| 79 | +CLOCK_CLK_OUT_FREQ_1_DOT_024KHZ = const(0x81) |
| 80 | +CLOCK_CLK_OUT_FREQ_32_KHZ = const(0x82) |
| 81 | +CLOCK_CLK_OUT_FREQ_1_HZ = const(0x83) |
| 82 | +CLOCK_CLK_HIGH_IMPEDANCE = const(0x0) |
| 83 | + |
| 84 | +SECONDS = 0 |
| 85 | +MINUTES = 1 |
| 86 | +HOURS = 2 |
| 87 | +DAY = 3 |
| 88 | +DATE = 4 |
| 89 | +MONTH = 5 |
| 90 | +YEAR = 6 |
| 91 | + |
| 92 | +class RTC8563Unit: |
| 93 | + def __init__(self, i2c: Union[I2C, PAHUBUnit], address=PCF8563_SLAVE_ADDRESS): |
| 94 | + """Initialization needs to be given an initialized I2C port |
| 95 | + """ |
| 96 | + self.i2c = i2c |
| 97 | + self.i2c_addr = address |
| 98 | + self.buffer = bytearray(16) |
| 99 | + self.bytebuf = memoryview(self.buffer[0:1]) |
| 100 | + self._available() |
| 101 | + self.clear_alarm_flag() |
| 102 | + self.clear_timer_flag() |
| 103 | + self.turn_off_alarm() |
| 104 | + self.turn_off_timer() |
| 105 | + |
| 106 | + def _available(self): |
| 107 | + if not (self.i2c_addr in self.i2c.scan()): |
| 108 | + raise UnitError("RTC unit maybe not connect") |
| 109 | + |
| 110 | + def __write_byte(self, reg, val): |
| 111 | + self.bytebuf[0] = val |
| 112 | + self.i2c.writeto_mem(self.i2c_addr, reg, self.bytebuf) |
| 113 | + |
| 114 | + def __read_byte(self, reg): |
| 115 | + self.i2c.readfrom_mem_into(self.i2c_addr, reg, self.bytebuf) |
| 116 | + return self.bytebuf[0] |
| 117 | + |
| 118 | + def __bcd2dec(self, bcd): |
| 119 | + return (((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f)) |
| 120 | + |
| 121 | + def __dec2bcd(self, dec): |
| 122 | + tens, units = divmod(dec, 10) |
| 123 | + return (tens << 4) + units |
| 124 | + |
| 125 | + def get_date_time(self, select=0): |
| 126 | + if select == SECONDS: |
| 127 | + return self.__bcd2dec(self.__read_byte(PCF8563_SEC_REG) & 0x7F) |
| 128 | + |
| 129 | + elif select == MINUTES: |
| 130 | + return self.__bcd2dec(self.__read_byte(PCF8563_MIN_REG) & 0x7F) |
| 131 | + |
| 132 | + elif select == HOURS: |
| 133 | + d = self.__read_byte(PCF8563_HR_REG) & 0x3F |
| 134 | + return self.__bcd2dec(d & 0x3F) |
| 135 | + |
| 136 | + elif select == DAY: |
| 137 | + return self.__bcd2dec(self.__read_byte(PCF8563_WEEKDAY_REG) & 0x07) |
| 138 | + |
| 139 | + elif select == DATE: |
| 140 | + return self.__bcd2dec(self.__read_byte(PCF8563_DAY_REG) & 0x3F) |
| 141 | + |
| 142 | + elif select == MONTH: |
| 143 | + return self.__bcd2dec(self.__read_byte(PCF8563_MONTH_REG) & 0x1F) |
| 144 | + |
| 145 | + elif select == YEAR: |
| 146 | + return self.__bcd2dec(self.__read_byte(PCF8563_YEAR_REG)) |
| 147 | + |
| 148 | + def set_date_time(self, seconds=None, minutes=None, hours=None, day=None, |
| 149 | + date=None, month=None, year=None): |
| 150 | + """Direct write un-none value. |
| 151 | + Range: seconds [0,59], minutes [0,59], hours [0,23], |
| 152 | + day [0,7], date [1-31], month [1-12], year [0-99]. |
| 153 | + """ |
| 154 | + if seconds is not None: |
| 155 | + if seconds < 0 or seconds > 59: |
| 156 | + raise ValueError('Seconds is out of range [0,59].') |
| 157 | + seconds_reg = self.__dec2bcd(seconds) |
| 158 | + self.__write_byte(PCF8563_SEC_REG, seconds_reg) |
| 159 | + |
| 160 | + if minutes is not None: |
| 161 | + if minutes < 0 or minutes > 59: |
| 162 | + raise ValueError('Minutes is out of range [0,59].') |
| 163 | + self.__write_byte(PCF8563_MIN_REG, self.__dec2bcd(minutes)) |
| 164 | + |
| 165 | + if hours is not None: |
| 166 | + if hours < 0 or hours > 23: |
| 167 | + raise ValueError('Hours is out of range [0,23].') |
| 168 | + # no 12 hour mode |
| 169 | + self.__write_byte(PCF8563_HR_REG, self.__dec2bcd(hours)) |
| 170 | + |
| 171 | + if year is not None: |
| 172 | + if year < 0 or year > 99: |
| 173 | + raise ValueError('Years is out of range [0,99].') |
| 174 | + self.__write_byte(PCF8563_YEAR_REG, self.__dec2bcd(year)) |
| 175 | + |
| 176 | + if month is not None: |
| 177 | + if month < 1 or month > 12: |
| 178 | + raise ValueError('Month is out of range [1,12].') |
| 179 | + self.__write_byte(PCF8563_MONTH_REG, self.__dec2bcd(month)) |
| 180 | + |
| 181 | + if date is not None: |
| 182 | + if date < 1 or date > 31: |
| 183 | + raise ValueError('Date is out of range [1,31].') |
| 184 | + self.__write_byte(PCF8563_DAY_REG, self.__dec2bcd(date)) |
| 185 | + |
| 186 | + if day is not None: |
| 187 | + if day < 0 or day > 6: |
| 188 | + raise ValueError('Day is out of range [0,6].') |
| 189 | + self.__write_byte(PCF8563_WEEKDAY_REG, self.__dec2bcd(day)) |
| 190 | + |
| 191 | + def datetime(self, dt): |
| 192 | + """Input a tuple such as (year, month, date, day, hours, minutes, |
| 193 | + seconds). |
| 194 | + """ |
| 195 | + self.set_date_time(dt[5], dt[4], dt[3], |
| 196 | + dt[6], dt[2], dt[1], dt[0] % 100) |
| 197 | + |
| 198 | + def write_now(self): |
| 199 | + """Write the current system time to PCF8563 |
| 200 | + """ |
| 201 | + self.datetime(localtime()) |
| 202 | + |
| 203 | + def set_internet_time(self, source='ntp', host='cn.pool.ntp.org', tzone=0): |
| 204 | + """Set the time from the NTP server |
| 205 | + """ |
| 206 | + if source == 'ntp': |
| 207 | + self.tzone = TZONE(tzone) |
| 208 | + for i in range(5): |
| 209 | + ntp = self.tzone.getntp(host) |
| 210 | + if ntp != 0: |
| 211 | + break |
| 212 | + sleep(5) |
| 213 | + # z = self.tzone.adj_tzone(localtime(ntp)) |
| 214 | + tzone = int(3600 * (int(tzone) + ((tzone - int(tzone)) * 100/60))) |
| 215 | + utc = localtime(ntp + tzone) |
| 216 | + |
| 217 | + (yy, MM, mday, hh, mm, ss, wday, yday) = utc |
| 218 | + self.datetime((yy - 2000, MM, mday, hh, mm, ss, wday)) |
| 219 | + |
| 220 | + def set_clk_out_frequency(self, frequency=CLOCK_CLK_OUT_FREQ_1_HZ): |
| 221 | + """Set the clock output pin frequency |
| 222 | + """ |
| 223 | + self.__write_byte(PCF8563_SQW_REG, frequency) |
| 224 | + |
| 225 | + def check_if_alarm_on(self): |
| 226 | + """Read the register to get the alarm enabled |
| 227 | + """ |
| 228 | + return bool(self.__read_byte(PCF8563_STAT2_REG) & PCF8563_ALARM_AF) |
| 229 | + |
| 230 | + def turn_off_alarm(self): |
| 231 | + """Should not affect the alarm interrupt state. |
| 232 | + """ |
| 233 | + alarm_state = self.__read_byte(PCF8563_STAT2_REG) |
| 234 | + self.__write_byte(PCF8563_STAT2_REG, alarm_state & 0xf7) |
| 235 | + |
| 236 | + def clear_alarm_flag(self): |
| 237 | + """Clear status register. |
| 238 | + """ |
| 239 | + alarm_state = self.__read_byte(PCF8563_STAT2_REG) |
| 240 | + alarm_state &= ~(PCF8563_ALARM_AF) |
| 241 | + alarm_state |= PCF8563_TIMER_TF |
| 242 | + self.__write_byte(PCF8563_STAT2_REG, alarm_state) |
| 243 | + |
| 244 | + self.__write_byte(PCF8563_ALARM_MINUTES, 0x80) |
| 245 | + self.__write_byte(PCF8563_ALARM_HOURS, 0x80) |
| 246 | + self.__write_byte(PCF8563_ALARM_DAY, 0x80) |
| 247 | + self.__write_byte(PCF8563_ALARM_WEEKDAY, 0x80) |
| 248 | + |
| 249 | + def set_daily_alarm(self, hours=None, minutes=None, date=None, weekday=None): |
| 250 | + """Set alarm match, allow sometimes, minute, day, week |
| 251 | + """ |
| 252 | + if minutes is None: |
| 253 | + minutes = PCF8563_ALARM_ENABLE |
| 254 | + self.__write_byte(PCF8563_ALARM_MINUTES, minutes) |
| 255 | + else: |
| 256 | + if minutes < 0 or minutes > 59: |
| 257 | + raise ValueError('Minutes is out of range [0,59].') |
| 258 | + self.__write_byte(PCF8563_ALARM_MINUTES, |
| 259 | + self.__dec2bcd(minutes) & 0x7f) |
| 260 | + |
| 261 | + if hours is None: |
| 262 | + hours = PCF8563_ALARM_ENABLE |
| 263 | + self.__write_byte(PCF8563_ALARM_HOURS, hours) |
| 264 | + else: |
| 265 | + if hours < 0 or hours > 23: |
| 266 | + raise ValueError('Hours is out of range [0,23].') |
| 267 | + self.__write_byte(PCF8563_ALARM_HOURS, self.__dec2bcd( |
| 268 | + hours) & 0x7f) |
| 269 | + |
| 270 | + if date is None: |
| 271 | + date = PCF8563_ALARM_ENABLE |
| 272 | + self.__write_byte(PCF8563_ALARM_DAY, date) |
| 273 | + else: |
| 274 | + if date < 1 or date > 31: |
| 275 | + raise ValueError('date is out of range [1,31].') |
| 276 | + self.__write_byte(PCF8563_ALARM_DAY, self.__dec2bcd( |
| 277 | + date) & 0x7f) |
| 278 | + |
| 279 | + if weekday is None: |
| 280 | + weekday = PCF8563_ALARM_ENABLE |
| 281 | + self.__write_byte(PCF8563_ALARM_WEEKDAY, weekday) |
| 282 | + else: |
| 283 | + if weekday < 0 or weekday > 6: |
| 284 | + raise ValueError('weekday is out of range [0,6].') |
| 285 | + self.__write_byte(PCF8563_ALARM_WEEKDAY, self.__dec2bcd( |
| 286 | + weekday) & 0x7f) |
| 287 | + |
| 288 | + def set_timer_mode(self, mode=0, value=0): |
| 289 | + """ |
| 290 | + Set the timer mode. |
| 291 | + """ |
| 292 | + self.__write_byte(PCF8563_TIMER2_REG, value) |
| 293 | + timer_state = (PCF8563_TIMER_TE | 0x02 | mode) |
| 294 | + self.__write_byte(PCF8563_TIMER1_REG, timer_state) |
| 295 | + |
| 296 | + def get_timer_value(self): |
| 297 | + """ |
| 298 | + get the timer value. |
| 299 | + """ |
| 300 | + return self.__read_byte(PCF8563_TIMER2_REG) |
| 301 | + |
| 302 | + def check_if_timer_on(self): |
| 303 | + """ |
| 304 | + Read the register to get the alarm status |
| 305 | + """ |
| 306 | + return bool(self.__read_byte(PCF8563_STAT2_REG) & PCF8563_TIMER_TF) |
| 307 | + |
| 308 | + def turn_off_timer(self): |
| 309 | + """ |
| 310 | + clear the timer flag and disable timer. |
| 311 | + """ |
| 312 | + self.__write_byte(PCF8563_TIMER1_REG, PCF8563_TIMER_TD10) |
| 313 | + self.__write_byte(PCF8563_TIMER2_REG, 0x00) |
| 314 | + |
| 315 | + timer_state = self.__read_byte(PCF8563_STAT2_REG) |
| 316 | + timer_state &= ~(PCF8563_TIMER_TF) |
| 317 | + self.__write_byte(PCF8563_STAT2_REG, timer_state) |
| 318 | + |
| 319 | + def clear_timer_flag(self): |
| 320 | + """ |
| 321 | + clear the timer flag. |
| 322 | + """ |
| 323 | + timer_state = self.__read_byte(PCF8563_STAT2_REG) |
| 324 | + self.__write_byte(PCF8563_STAT2_REG, (timer_state & 0xfb)) |
0 commit comments