Advertisement
Guest User

ds3231_port.py

a guest
Aug 14th, 2022
453
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.60 KB | None | 0 0
  1. # ds3231_port.py Portable driver for DS3231 precison real time clock.
  2. # Adapted from WiPy driver at https://github.com/scudderfish/uDS3231
  3.  
  4. # Author: Peter Hinch
  5. # Copyright Peter Hinch 2018 Released under the MIT license.
  6.  
  7. # August 14,2022
  8. # Author: Daniel Perron
  9. # Added alarm.
  10.  
  11. import utime
  12. import machine
  13. import sys
  14. DS3231_I2C_ADDR = 104
  15.  
  16. try:
  17. rtc = machine.RTC()
  18. except:
  19. print('Warning: machine module does not support the RTC.')
  20. rtc = None
  21.  
  22. def bcd2dec(bcd):
  23. return (((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f))
  24.  
  25. def dec2bcd(dec):
  26. tens, units = divmod(dec, 10)
  27. return (tens << 4) + units
  28.  
  29. def tobytes(num):
  30. return num.to_bytes(1, 'little')
  31.  
  32. class DS3231:
  33. ALARM_EVERY_S = [ 0, 0x80, 0x80 , 0x80 , 0x80]
  34. ALARM_S_MATCH = [ 0, 0x80, 0x80 , 0x80 , 0x00]
  35. ALARM_MS_MATCH = [ 0, 0x80, 0x80 , 0x00 , 0x00]
  36. ALARM_HMS_MATCH = [ 0, 0x80, 0x00 , 0x00 , 0x00]
  37. ALARM_DM_HMS_MATCH = [ 0, 0x00, 0x00 , 0x00 , 0x00]
  38. ALARM_DW__MATCH = [ 0x40, 0x00, 0x00 , 0x00 , 0x00]
  39.  
  40. def __init__(self, i2c):
  41. self.ds3231 = i2c
  42. self.timebuf = bytearray(7)
  43. if DS3231_I2C_ADDR not in self.ds3231.scan():
  44. raise RuntimeError("DS3231 not found on I2C bus at %d" % DS3231_I2C_ADDR)
  45.  
  46. def get_time(self, set_rtc=False):
  47. if set_rtc:
  48. self.await_transition() # For accuracy set RTC immediately after a seconds transition
  49. else:
  50. self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf) # don't wait
  51. return self.convert(set_rtc)
  52.  
  53. def convert(self, set_rtc=False): # Return a tuple in localtime() format (less yday)
  54. data = self.timebuf
  55. ss = bcd2dec(data[0])
  56. mm = bcd2dec(data[1])
  57. if data[2] & 0x40:
  58. hh = bcd2dec(data[2] & 0x1f)
  59. if data[2] & 0x20:
  60. hh += 12
  61. else:
  62. hh = bcd2dec(data[2])
  63. wday = data[3]
  64. DD = bcd2dec(data[4])
  65. MM = bcd2dec(data[5] & 0x1f)
  66. YY = bcd2dec(data[6])
  67. if data[5] & 0x80:
  68. YY += 2000
  69. else:
  70. YY += 1900
  71. # Time from DS3231 in time.localtime() format (less yday)
  72. result = YY, MM, DD, hh, mm, ss, wday -1, 0
  73. if set_rtc:
  74. if rtc is None:
  75. # Best we can do is to set local time
  76. secs = utime.mktime(result)
  77. utime.localtime(secs)
  78. else:
  79. rtc.datetime((YY, MM, DD, wday, hh, mm, ss, 0))
  80. return result
  81.  
  82. def save_time(self):
  83. (YY, MM, mday, hh, mm, ss, wday, yday) = utime.localtime() # Based on RTC
  84. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 0, tobytes(dec2bcd(ss)))
  85. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 1, tobytes(dec2bcd(mm)))
  86. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 2, tobytes(dec2bcd(hh))) # Sets to 24hr mode
  87. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 3, tobytes(dec2bcd(wday + 1))) # 1 == Monday, 7 == Sunday
  88. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 4, tobytes(dec2bcd(mday))) # Day of month
  89. if YY >= 2000:
  90. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 5, tobytes(dec2bcd(MM) | 0b10000000)) # Century bit
  91. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 6, tobytes(dec2bcd(YY-2000)))
  92. else:
  93. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 5, tobytes(dec2bcd(MM)))
  94. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 6, tobytes(dec2bcd(YY-1900)))
  95.  
  96. # Wait until DS3231 seconds value changes before reading and returning data
  97. def await_transition(self):
  98. self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf)
  99. ss = self.timebuf[0]
  100. while ss == self.timebuf[0]:
  101. self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf)
  102. return self.timebuf
  103.  
  104. # Test hardware RTC against DS3231. Default runtime 10 min. Return amount
  105. # by which DS3231 clock leads RTC in PPM or seconds per year.
  106. # Precision is achieved by starting and ending the measurement on DS3231
  107. # one-seond boundaries and using ticks_ms() to time the RTC.
  108. # For a 10 minute measurement +-1ms corresponds to 1.7ppm or 53s/yr. Longer
  109. # runtimes improve this, but the DS3231 is "only" good for +-2ppm over 0-40C.
  110. def rtc_test(self, runtime=600, ppm=False, verbose=True):
  111. if rtc is None:
  112. raise RuntimeError('machine.RTC does not exist')
  113. verbose and print('Waiting {} minutes for result'.format(runtime//60))
  114. factor = 1_000_000 if ppm else 114_155_200 # seconds per year
  115.  
  116. self.await_transition() # Start on transition of DS3231. Record time in .timebuf
  117. t = utime.ticks_ms() # Get system time now
  118. ss = rtc.datetime()[6] # Seconds from system RTC
  119. while ss == rtc.datetime()[6]:
  120. pass
  121. ds = utime.ticks_diff(utime.ticks_ms(), t) # ms to transition of RTC
  122. ds3231_start = utime.mktime(self.convert()) # Time when transition occurred
  123. t = rtc.datetime()
  124. rtc_start = utime.mktime((t[0], t[1], t[2], t[4], t[5], t[6], t[3] - 1, 0)) # y m d h m s wday 0
  125.  
  126. utime.sleep(runtime) # Wait a while (precision doesn't matter)
  127.  
  128. self.await_transition() # of DS3231 and record the time
  129. t = utime.ticks_ms() # and get system time now
  130. ss = rtc.datetime()[6] # Seconds from system RTC
  131. while ss == rtc.datetime()[6]:
  132. pass
  133. de = utime.ticks_diff(utime.ticks_ms(), t) # ms to transition of RTC
  134. ds3231_end = utime.mktime(self.convert()) # Time when transition occurred
  135. t = rtc.datetime()
  136. rtc_end = utime.mktime((t[0], t[1], t[2], t[4], t[5], t[6], t[3] - 1, 0)) # y m d h m s wday 0
  137.  
  138. d_rtc = 1000 * (rtc_end - rtc_start) + de - ds # ms recorded by RTC
  139. d_ds3231 = 1000 * (ds3231_end - ds3231_start) # ms recorded by DS3231
  140. ratio = (d_ds3231 - d_rtc) / d_ds3231
  141. ppm = ratio * 1_000_000
  142. verbose and print('DS3231 leads RTC by {:4.1f}ppm {:4.1f}mins/yr'.format(ppm, ppm*1.903))
  143. return ratio * factor
  144.  
  145.  
  146. def _twos_complement(self, input_value: int, num_bits: int) -> int:
  147. mask = 2 ** (num_bits - 1)
  148. return -(input_value & mask) + (input_value & ~mask)
  149.  
  150.  
  151. def get_temperature(self):
  152. t = self.ds3231.readfrom_mem(DS3231_I2C_ADDR, 0x11, 2)
  153. i = t[0] << 8 | t[1]
  154. return self._twos_complement(i >> 6, 10) * 0.25
  155.  
  156.  
  157. def enable_alarm(self, alarm_id=1, enable=True):
  158. if (alarm_id != 1) and (alarm_id!=2):
  159. return
  160. #first read register
  161. c_reg = bytearray(self.ds3231.readfrom_mem(DS3231_I2C_ADDR,0xe,2))
  162.  
  163. if alarm_id == 1:
  164. if enable:
  165. c_reg[0]= c_reg[0] | 0x5
  166. else:
  167. c_reg[0]= c_reg[0] & 0xfe
  168. #clear flag
  169. c_reg[1] = 0xfe
  170. else:
  171. if enable:
  172. c_reg[0]= c_reg[0] | 0x6
  173. else:
  174. c_reg[0]= c_reg[0] & 0xfd
  175. #clear flag
  176. c_reg[1] = 0xfd
  177. self.ds3231.writeto_mem(DS3231_I2C_ADDR,0xe,c_reg)
  178.  
  179.  
  180. def set_alarm(self,alarm_id=1,alarm_time=None,match=ALARM_HMS_MATCH):
  181. if (alarm_id != 1) and (alarm_id !=2):
  182. return
  183. if alarm_time is None:
  184. #disable alarm
  185. if alarm_id == 1:
  186. self.enable_alarm(1,False)
  187. else:
  188. self.enable_alarm(2,False)
  189. return # need to have alarm time
  190. buffer = bytearray(4)
  191. #sec
  192. buffer[0] = dec2bcd(alarm_time[5]) | match[4]
  193. #min
  194. buffer[1] = dec2bcd(alarm_time[4]) | match[3]
  195. #hour 24Hrs
  196. buffer[2] = dec2bcd(alarm_time[3]) | match[2]
  197. if match[0] == 0:
  198. #day of the month
  199. buffer[3]= dec2bcd(alarm_time[2]) | match[1]
  200. else:
  201. buffer[3]= dec2bcd(alarm_time[6]) | match[1]
  202. #day of the week
  203. if alarm_id == 1:
  204. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 7, buffer)
  205. else:
  206. self.ds3231.writeto_mem(DS3231_I2C_ADDR,0xb,buffer[1:])
  207. self.enable_alarm(alarm_id,True)
  208. % danielperron@MacBook-Air-de-Daniel ~ % cat ds3231_port.py
  209. # ds3231_port.py Portable driver for DS3231 precison real time clock.
  210. # Adapted from WiPy driver at https://github.com/scudderfish/uDS3231
  211.  
  212. # Author: Peter Hinch
  213. # Copyright Peter Hinch 2018 Released under the MIT license.
  214.  
  215. import utime
  216. import machine
  217. import sys
  218. DS3231_I2C_ADDR = 104
  219.  
  220. try:
  221. rtc = machine.RTC()
  222. except:
  223. print('Warning: machine module does not support the RTC.')
  224. rtc = None
  225.  
  226. def bcd2dec(bcd):
  227. return (((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f))
  228.  
  229. def dec2bcd(dec):
  230. tens, units = divmod(dec, 10)
  231. return (tens << 4) + units
  232.  
  233. def tobytes(num):
  234. return num.to_bytes(1, 'little')
  235.  
  236. class DS3231:
  237. # _CONTROL_REG = 0x0e
  238. # _STATUS_REG = 0x0f
  239. # _DATETIME_REG = 0x00
  240. # _ALARM1_REG = (0x08, 0x0b)
  241. # _SQUARE_WAVE_REG = 0x0e
  242. ALARM_EVERY_S = [ 0, 0x80, 0x80 , 0x80 , 0x80]
  243. ALARM_S_MATCH = [ 0, 0x80, 0x80 , 0x80 , 0x00]
  244. ALARM_MS_MATCH = [ 0, 0x80, 0x80 , 0x00 , 0x00]
  245. ALARM_HMS_MATCH = [ 0, 0x80, 0x00 , 0x00 , 0x00]
  246. ALARM_DM_HMS_MATCH = [ 0, 0x00, 0x00 , 0x00 , 0x00]
  247. ALARM_DW__MATCH = [ 0x40, 0x00, 0x00 , 0x00 , 0x00]
  248. _year=0
  249. _month=1
  250. mday=2
  251.  
  252.  
  253. def __init__(self, i2c):
  254. self.ds3231 = i2c
  255. self.timebuf = bytearray(7)
  256. if DS3231_I2C_ADDR not in self.ds3231.scan():
  257. raise RuntimeError("DS3231 not found on I2C bus at %d" % DS3231_I2C_ADDR)
  258.  
  259. def get_time(self, set_rtc=False):
  260. if set_rtc:
  261. self.await_transition() # For accuracy set RTC immediately after a seconds transition
  262. else:
  263. self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf) # don't wait
  264. return self.convert(set_rtc)
  265.  
  266. def convert(self, set_rtc=False): # Return a tuple in localtime() format (less yday)
  267. data = self.timebuf
  268. ss = bcd2dec(data[0])
  269. mm = bcd2dec(data[1])
  270. if data[2] & 0x40:
  271. hh = bcd2dec(data[2] & 0x1f)
  272. if data[2] & 0x20:
  273. hh += 12
  274. else:
  275. hh = bcd2dec(data[2])
  276. wday = data[3]
  277. DD = bcd2dec(data[4])
  278. MM = bcd2dec(data[5] & 0x1f)
  279. YY = bcd2dec(data[6])
  280. if data[5] & 0x80:
  281. YY += 2000
  282. else:
  283. YY += 1900
  284. # Time from DS3231 in time.localtime() format (less yday)
  285. result = YY, MM, DD, hh, mm, ss, wday -1, 0
  286. if set_rtc:
  287. if rtc is None:
  288. # Best we can do is to set local time
  289. secs = utime.mktime(result)
  290. utime.localtime(secs)
  291. else:
  292. rtc.datetime((YY, MM, DD, wday, hh, mm, ss, 0))
  293. return result
  294.  
  295. def save_time(self):
  296. (YY, MM, mday, hh, mm, ss, wday, yday) = utime.localtime() # Based on RTC
  297. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 0, tobytes(dec2bcd(ss)))
  298. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 1, tobytes(dec2bcd(mm)))
  299. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 2, tobytes(dec2bcd(hh))) # Sets to 24hr mode
  300. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 3, tobytes(dec2bcd(wday + 1))) # 1 == Monday, 7 == Sunday
  301. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 4, tobytes(dec2bcd(mday))) # Day of month
  302. if YY >= 2000:
  303. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 5, tobytes(dec2bcd(MM) | 0b10000000)) # Century bit
  304. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 6, tobytes(dec2bcd(YY-2000)))
  305. else:
  306. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 5, tobytes(dec2bcd(MM)))
  307. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 6, tobytes(dec2bcd(YY-1900)))
  308.  
  309. # Wait until DS3231 seconds value changes before reading and returning data
  310. def await_transition(self):
  311. self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf)
  312. ss = self.timebuf[0]
  313. while ss == self.timebuf[0]:
  314. self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf)
  315. return self.timebuf
  316.  
  317. # Test hardware RTC against DS3231. Default runtime 10 min. Return amount
  318. # by which DS3231 clock leads RTC in PPM or seconds per year.
  319. # Precision is achieved by starting and ending the measurement on DS3231
  320. # one-seond boundaries and using ticks_ms() to time the RTC.
  321. # For a 10 minute measurement +-1ms corresponds to 1.7ppm or 53s/yr. Longer
  322. # runtimes improve this, but the DS3231 is "only" good for +-2ppm over 0-40C.
  323. def rtc_test(self, runtime=600, ppm=False, verbose=True):
  324. if rtc is None:
  325. raise RuntimeError('machine.RTC does not exist')
  326. verbose and print('Waiting {} minutes for result'.format(runtime//60))
  327. factor = 1_000_000 if ppm else 114_155_200 # seconds per year
  328.  
  329. self.await_transition() # Start on transition of DS3231. Record time in .timebuf
  330. t = utime.ticks_ms() # Get system time now
  331. ss = rtc.datetime()[6] # Seconds from system RTC
  332. while ss == rtc.datetime()[6]:
  333. pass
  334. ds = utime.ticks_diff(utime.ticks_ms(), t) # ms to transition of RTC
  335. ds3231_start = utime.mktime(self.convert()) # Time when transition occurred
  336. t = rtc.datetime()
  337. rtc_start = utime.mktime((t[0], t[1], t[2], t[4], t[5], t[6], t[3] - 1, 0)) # y m d h m s wday 0
  338.  
  339. utime.sleep(runtime) # Wait a while (precision doesn't matter)
  340.  
  341. self.await_transition() # of DS3231 and record the time
  342. t = utime.ticks_ms() # and get system time now
  343. ss = rtc.datetime()[6] # Seconds from system RTC
  344. while ss == rtc.datetime()[6]:
  345. pass
  346. de = utime.ticks_diff(utime.ticks_ms(), t) # ms to transition of RTC
  347. ds3231_end = utime.mktime(self.convert()) # Time when transition occurred
  348. t = rtc.datetime()
  349. rtc_end = utime.mktime((t[0], t[1], t[2], t[4], t[5], t[6], t[3] - 1, 0)) # y m d h m s wday 0
  350.  
  351. d_rtc = 1000 * (rtc_end - rtc_start) + de - ds # ms recorded by RTC
  352. d_ds3231 = 1000 * (ds3231_end - ds3231_start) # ms recorded by DS3231
  353. ratio = (d_ds3231 - d_rtc) / d_ds3231
  354. ppm = ratio * 1_000_000
  355. verbose and print('DS3231 leads RTC by {:4.1f}ppm {:4.1f}mins/yr'.format(ppm, ppm*1.903))
  356. return ratio * factor
  357.  
  358.  
  359. def _twos_complement(self, input_value: int, num_bits: int) -> int:
  360. mask = 2 ** (num_bits - 1)
  361. return -(input_value & mask) + (input_value & ~mask)
  362.  
  363.  
  364. def get_temperature(self):
  365. t = self.ds3231.readfrom_mem(DS3231_I2C_ADDR, 0x11, 2)
  366. i = t[0] << 8 | t[1]
  367. return self._twos_complement(i >> 6, 10) * 0.25
  368.  
  369.  
  370. def enable_alarm(self, alarm_id=1, enable=True):
  371. if (alarm_id != 1) and (alarm_id!=2):
  372. return
  373. #first read register
  374. c_reg = bytearray(self.ds3231.readfrom_mem(DS3231_I2C_ADDR,0xe,2))
  375.  
  376. if alarm_id == 1:
  377. if enable:
  378. c_reg[0]= c_reg[0] | 0x5
  379. else:
  380. c_reg[0]= c_reg[0] & 0xfe
  381. #clear flag
  382. c_reg[1] = 0xfe
  383. else:
  384. if enable:
  385. c_reg[0]= c_reg[0] | 0x6
  386. else:
  387. c_reg[0]= c_reg[0] & 0xfd
  388. #clear flag
  389. c_reg[1] = 0xfd
  390. self.ds3231.writeto_mem(DS3231_I2C_ADDR,0xe,c_reg)
  391.  
  392.  
  393. def set_alarm(self,alarm_id=1,alarm_time=None,match=ALARM_HMS_MATCH):
  394. if (alarm_id != 1) and (alarm_id !=2):
  395. return
  396. if alarm_time is None:
  397. #disable alarm
  398. if alarm_id == 1:
  399. self.enable_alarm(1,False)
  400. else:
  401. self.enable_alarm(2,False)
  402. return # need to have alarm time
  403. buffer = bytearray(4)
  404. #sec
  405. buffer[0] = dec2bcd(alarm_time[5]) | match[4]
  406. #min
  407. buffer[1] = dec2bcd(alarm_time[4]) | match[3]
  408. #hour 24Hrs
  409. buffer[2] = dec2bcd(alarm_time[3]) | match[2]
  410. if match[0] == 0:
  411. #day of the month
  412. buffer[3]= dec2bcd(alarm_time[2]) | match[1]
  413. else:
  414. buffer[3]= dec2bcd(alarm_time[6]) | match[1]
  415. #day of the week
  416. if alarm_id == 1:
  417. self.ds3231.writeto_mem(DS3231_I2C_ADDR, 7, buffer)
  418. else:
  419. self.ds3231.writeto_mem(DS3231_I2C_ADDR,0xb,buffer[1:])
  420. self.enable_alarm(alarm_id,True)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement