Raspberry Pi Pico(d_11)DS3231 RTCモジュール
DS3231 RTC (I2C)
Raspberry Pi PicoでDS3231モジュールを使ってみました。
DS3231は温度補償されたreal-time clock (RTC)です。I2C通信で制御します。
このRTCモジュールにはAT24C32 32Kビット=4KバイトのEEPROMも付いています。
※micro:bitの記事e_17、e_18、e_19からPiPicoで使えるように追記、修正等をしました。
外観
DS3231 RTCモジュールの外観写真です。(以後はDS3231と記載します)
充電可能なリチウムコイン電池LIR2032を使うそうです。価格がけっこう高いです。安価なリチウムコイン電池CR2032を使う場合にはダイオードかチップ抵抗を外して充電できないようにする必要があります。※コイン電池をセットしなくても動作の確認はできます。
AT24C32のスレーブアドレスはモジュールに抵抗追加で変更できるようです。※詳細はモジュールのデータシートなど参照ください。
接続例
DS3231 RTC の動作電圧は2.3V~5.5VなのでRaspberry Pi Picoと接続できます。
I2C通信にはI2C1のSDA(GP10)SCL(GP11)を使いました。
接続出来たらI2Cデバイスのスレーブアドレスを調べてみます。
ThonnyのShellで確認しました。
RTCモジュールのスレーブアドレスは
DS3231(RTC)が0x68(104)
AT24C32(EEPROM)が0x57(87)
を確認できました。
>>> from machine import I2C,Pin
>>> i2c=I2C(1, scl=Pin(11), sda=Pin(10), freq=100_000)
>>> i2c.scan()
[87, 104]
>>>
DS3231の説明
データシートから抜粋してDS3231の説明します。下表はAddress Mapです。
※ Maxim Integrated DS3231 のデータシートから抜粋
■Clock and Calendar
カレンダは0x00~0x06のレジスタで設定します。読出しすれば時刻が分かります。
■Alarms
アラーム関係は0x07~0x0dのレジスタで設定します。A1,A2の2つあります。Bit7(Alarm Mask Bits)等でアラームモードを設定して他Bitでアラームの一致データを設定します。
※ Maxim Integrated DS3231 のデータシートから抜粋
■Control Register
割込みの許可は0Eh(Control Register)のBit0(A1IE)、Bit1(A2IE)で設定します。
(#印の行は下方のスクリプトで対応する部分です。adrはDS3231 RTCのアドレスです。)
■Status Register
割込みの有無(アラーム値と一致)は0Fh(Status Register)のBit0(A1F)、Bit1(A2F)で確認します。リセット(‘0’)は書込めますがセット(‘1’)は出来ません。
■Temperature Register
0x11レジスタと0x12の上位2bitで10bitの符号付きデータになります。分解能は 0.25°C/bitです。0b0001100101(101)なら101×0.25=25.25℃になります。
スクリプト
DS3231 RTCのスクリプトは以下のようにしました。※24H形式のみ
※開発環境はThonnyです。ThonnyでMicroPythonをRaspberry Pi Pico with RP2040にインストールして使っています。
pico_ds3231_02_all_02.py
#!/usr/bin/env python
# -*- coding: utf-8 -*
from machine import I2C,Pin
import time
#RTC DS3231 0x68(104) EEPROM(AT24C32)0x50~0x57
# i2c init
i2c=I2C(1, scl=Pin(11), sda=Pin(10), freq=100_000)
# slave address DS3231
adr=0x68
# zero 穴埋め 8bit
def z_padd(dat): # int
dat=bin(dat) # str
value='0b'+'0'*(8-len(dat[2:])) + dat[2:] # str 8bit
return value
# read register reg:レジスタアドレス numデータバイト長さ
def read_reg(reg,num):
buf=bytearray(1)
buf[0]=reg
i2c.writeto(adr,buf)
time.sleep_ms(5)
r_reg=i2c.readfrom(adr,num)
time.sleep_ms(5)
return r_reg
# 書込むデータを上位下位に分ける hex表示にすると読視できる
def s_dat(dat):
s_dat=(dat//10 << 4) | (dat%10)
return s_dat
### Calender data read/write
def write_Cal(l_data): #l_data=calender list-setdata
dat=l_data
s_reg=bytearray(1)
s_reg[0]=0x00 # 書込むレジスタのアドレス
# リストデータをバイトアレイに格納
buf=bytearray(7)
# 0x00 seconds
buf[0]= s_dat(dat[5])
# 0x01 minutes
buf[1]= s_dat(dat[4])
# 0x02 hour
buf[2]= s_dat(dat[3])
# 0x03 day of week
buf[3]= s_dat(dat[6])
# 0x04 date
buf[4]= s_dat(dat[2])
# 0x05 month over century
buf[5]= s_dat(dat[1])
# 0x06 year
buf[6]= s_dat(dat[0]-2000)
# writing to registers
s_buf=bytearray(8)
s_buf=s_reg + buf # 開始レジスタ + データ
i2c.writeto(adr,s_buf) #**change
def read_Cal():
# read real time
wday=['sun','mon','tue','wed','thu','fri','sat'] #曜日データ
s_reg=bytearray(1)
s_reg[0]=0x00
i2c.writeto(adr,s_reg)
r_reg=i2c.readfrom(adr,7)
r_data=[]
for i in range(7):
a=hex(r_reg[i])[2:] #a='0x36' a[2:]='36' 0xを除く文字列に
if i==3: # 0x03 曜日に
a=wday[int(a)]
if i==6:
a=str(2000+int(a)) # 0x06 西暦に
r_data.append(a)
# [::-1]逆ソート 逆ソートしてから並べ替え
ss=r_data[::-1][0:3]+r_data[::-1][4:7]+r_data[::-1][3:4]
return ss
### Alarm data read/write
def write_Adata(ldata):
a1= ldata[0]
a2= ldata[1]
a1m=ldata[2]
a2m=ldata[3]
a1d=ldata[4]
a2d=ldata[5]
# write alarm data set-regi 0x07-0xd
s_reg=bytearray(1)
s_reg[0]=0x07 # write start regi-num
buf=bytearray(7)
for i in range(4):
buf[i]=s_dat(a1[i]) #一致データを上位下位に変換する
# mask bit
if a1d[0]=='':
buf[i]=buf[i] | a1m[i]<< 7
else:
buf[i]=buf[i] & 0x7f # AM bit7= '0'
if a1d[0]=='1':
print('a1d=1')
buf[3]=buf[3] | 0x40 # DY/DT bit wo '1'
for i in range(3):
buf[i+4]=s_dat(a2[i]) #設定データを上位下位に変換する
if a2d[0]=='':
buf[i+4]=buf[i+4] | a2m[i]<< 7
else:
buf[i+4]=buf[i+4] & 0x7f # AM bit7 '0'
if a2d[0]=='1':
print('a2d=1')
buf[6]=buf[6] | 0x40 # DY/DT bit wo '1'
s_buf=s_reg+buf # 開始レジスタ + データ
i2c.writeto(adr,s_buf) # write mach data
# read Alarm data 0x07から7bytes
def read_Adata():
s_reg=bytearray(1)
s_reg[0]=0x07
i2c.writeto(adr,s_reg)
r_reg=i2c.readfrom(adr,7)
return r_reg
# Disp Alarm regi-data
def disp_Adata(r_reg):
for i in range(7):
print(hex(7+i),z_padd(r_reg[i]),hex(r_reg[i]))
# Disp set_data dy/dt-hour-min-sec
def disp_Sdtim(r_reg):
DAT1=[] #sec,min,hour,DY/DT
for i in range(4):
#a='0x36' a[2:]='36' 0xを除く文字列に
a=hex(r_reg[i]& 0b0111_1111)[2:]
if i==3:
a=hex(r_reg[i]& 0b0011_1111)[2:]
DAT1.append(a)
print(DAT1)
DAT2=[] #min,hour,DY/DT
for i in range(3):
#a='0x36' a[2:]='36' 0xを除く文字列に
a=hex(r_reg[i+4]& 0b0111_1111)[2:]
if i==2:
a=hex(r_reg[i+4]& 0b0011_1111)[2:]
DAT2.append(a)
print(DAT2)
# Mask dataの表示
def disp_Mask(r_reg):
ALM1=[] #ALM1=[A1M1,A1M2,A1M3,A1M4,DY/DT]
for i in range(4):
ALM1.append((r_reg[i]>>7))
ALM1.append((r_reg[3] & 0b0100_0000)>>6) #最後にDY/DTを加える
print(ALM1)
ALM2=[] #ALM2=[A2M2,A2M3,A1M4,DY/DT]
for i in range(3):
ALM2.append((r_reg[i+4]>>7))
ALM2.append((r_reg[6] & 0b0100_0000)>>6)
print(ALM2)
## Alarm Flag(read/reset) Ctrl-AIE(read/set)
# read Alarm Flag |A2F|A1F|
def read_Aflg():
a_flag=read_reg(0x0f,1)[0]& 0x03
return a_flag
# reset Alarm Flag |A2F|A1F|
def reset_Aflag():
r_reg=read_reg(0x0f,1)
buf=bytearray(2)
buf[0]=0x0f
buf[1]=r_reg[0] & 0xfc # 0b_1111_1100
i2c.writeto(adr,buf)
## INTCON Alarm INT Enabel
# read |INTCN|A2IE|A1IE| Control/Status(0x0e)
def read_Aie():
a_ie= read_reg(0x0e,1)[0] & 0x07
return a_ie
# write |A2IE|A1IE| Control/Status(0x0e)
def set_Aie(s_num=3):
#|A2IE|A1IE| reset してから書込む
r_ie=read_reg(0x0e,1)[0] & 0xfc
aie=s_num # 00,01,10,11(0,1,2,3)
buf=bytearray(2)
buf[0]=0x0e
buf[1]=r_ie | aie
i2c.writeto(adr,buf)
## TEMPの測定
## temp read regi-0x11,0x12 Sign 10bit
# 2'complement
def s_comp(value):
bits=10
a = value[2:]
b_dat = '0'*(bits-len(a)) + a
conv_value = -int(b_dat[0]) << (bits-1) | int(b_dat,2)
return conv_value
# MSB(8)+LSB(2) 10bit
def add_bit(msb, lsb):
m = msb[2:]
msb_dat = '0'*(8-len(m)) + m
msb_8bit='0b'+msb_dat
l = lsb[2:]
lsb_dat = '0'*(8-len(l)) + l
lsb_2bit=lsb_dat[:2]
# 10bit data = MSB a_8bit+LSB b_2bit
x_reg = msb_8bit + lsb_2bit
return x_reg
# temp calc
def read_Temp():
# Temp Reg 0x11,0x12
r_reg=read_reg(0x11,2)
# 10bit data
t_bit=add_bit(bin(r_reg[0]),bin(r_reg[1]))
# 2'complement
t_dat=s_comp(t_bit)
#print(t_bit,t_dat)
t=0.25*t_dat
return t
関数などの説明
スクリプトの関数を使いながら部分的な説明をします。
■write_Cal(l_data)
カレンダを設定します。l_dataはカレンダのリストデータです。
2022/12/24 14:15:00 sat ( 0:sun 1:mon 2:tue 3:wed…)に設定するなら
l_cal=([2022, 12, 24, 17, 15, 0, 6])
write_Cal(l_cal)
とします。
■read_Cal()
現在の日時を読み出します。
※スクリプトをRun後にThonnyのshellで実行
>>> l_cal=([2022, 12, 24, 17, 15, 0, 6])
>>> write_Cal(l_cal)
>>> read_Cal()
['2022', '12', '24', '17', '15', '3', 'sat']
※書込み後、少し遅れて読出しているので一致はしていない。
■write_Adata(set_ldata)
アラーム設定をします。
set_ldataはマッチデータとアラームマスクデータのリストです。
例えば
set_ldata=[md1,md2,am1,am2,yt1,yt2]
md1,md2,am1,am2,yt1,yt2は以下のように設定します。
0x07~0x0dレジスタの日時等の一致データを設定します。
md1はアラーム1の一致リストデータで[秒,分,時,曜日/日付]
md2はアラーム1の一致リストデータで[分,時,曜日/日付]
で与えます。例えば、
# mach data
md1=[59,59,23,31] # sec,min,hour,day/date(0-6/1-31)
md2=[52,23,31] # min,hour,day/date(0-6/1-31)
※以下のアラームマスクと無関係でも何か数値を設定しておきます。空にするとエラーになります。
# ALARM MASK BITS
0x07~0x0dレジスタのBit7にマスク設定します。
am1=[1,1,1,1] #alarm1 mask(bit7)0x07-0x0a
am2=[1,1,1] #alarm1 mask(bit7)0x0b-0x0d
DT DATE(日付 1-31)をセットする場合
0x0a、0x0dレジスタのBit6を設定します。”:不使用,’0′:曜日,’1’日付
yt1=[”] #day/date ”,’0′,’1′ 自動的に a1m=[0,0,0,0] 曜日/日付
yt2=[”] #day/date ”,’0′,’1′ 自動的に a2m=[0,0,0]
※Table 2. Alarm Mask Bitsを参照
■read_Adata()
アラームデータ(0x07~0x0dレジスタ)を返します。
■disp_Adata(r_reg)
アラームデータ(0x07~0x0dレジスタ)の値を表示します。
■def disp_Sdtim(r_reg)
アラームデータ(0x07~0x0d)に設定されたマッチデータを表示します。
■disp_Mask(r_reg)
アラームデータ(0x07~0x0d)に設定されたマスクデータを表示します。
[am1,yt1][am2,yt2]のような表示をします。
※スクリプトをRun後にThonnyのshellで実行
>>> md1=[59,59,23,31]
>>> md2=[50,23,31]
>>> am1=[1,1,1,1]
>>> am2=[1,1,1]
>>> yt1=['']
>>> yt2=['']
>>>
>>> set_ldata=[md1,md2,am1,am2,yt1,yt2]
>>>
>>> write_Adata(set_ldata)
>>>
>>> a_dat=read_Adata()
>>> disp_Adata(a_dat)
0x7 0b11011001 0xd9
0x8 0b11011001 0xd9
0x9 0b10100011 0xa3
0xa 0b10110001 0xb1
0xb 0b11010000 0xd0
0xc 0b10100011 0xa3
0xd 0b10110001 0xb1
>>>
>>> disp_Sdtim(a_dat)
['59', '59', '23', '31']
['50', '23', '31']
>>> disp_Mask(a_dat)
[1, 1, 1, 1, 0]
[1, 1, 1, 0]
>>>
■set_Aie(s_num=3)
割込みの許可を設定します。0Ehレジスタの|A2IE|A1IE|に値を与えます。デフォルトは両方割込み可です。
■read_Aie()
割込みの許可の状態を調べます。|INTCN|A2IE|A1IE|の値が返ります。
■read_Aflg()
割込みの有無を調べます。|A2F|A1F|の値が返ります。
■reset_Aflag()
割込みフラグをリセットします。|A2F|A1F|を’0’にします。
※スクリプトをRun後にThonnyのshellで実行
>>> set_Aie(3)
>>> bin(read_Aie())
'0b111'
>>>
>>> bin(read_Aflg())
'0b11'
>>> reset_Aflag();bin(read_Aflg())
'0b0'
>>> bin(read_Aflg())
'0b1'
am1=[0,1,1,1] #秒が一致
am2=[1,1,1] #分ごと
md1=[55,59,23,31] # sec,min,hour,day/date(0-6/1-31)
md2=[50,23,31] # min,hour,day/date
に変更して、アラーム1は55秒で、アラーム2は分ごとでアラームフラグを立てるようにして見ます。
先のスクリプトの末尾に以下を挿入して実行しました。
reset_Aflag()
while True:
print(read_Cal())
aflag=read_Aflg()
print(bin(aflag))
time.sleep(1)
※l_cal=([2022, 12, 24, 17, 15, 50, 6]) 50秒にして実行しています。
>>> %Run -c $EDITOR_CONTENT
['2022', '12', '24', '17', '15', '50', 'sat']
0b0
['2022', '12', '24', '17', '15', '51', 'sat']
0b0
['2022', '12', '24', '17', '15', '52', 'sat']
0b0
['2022', '12', '24', '17', '15', '53', 'sat']
0b0
['2022', '12', '24', '17', '15', '54', 'sat']
0b0
['2022', '12', '24', '17', '15', '55', 'sat'] # アラーム1がマッチ
0b1 # 55秒でマッチ
['2022', '12', '24', '17', '15', '56', 'sat']
0b1
['2022', '12', '24', '17', '15', '57', 'sat']
0b1
['2022', '12', '24', '17', '15', '58', 'sat']
0b1
['2022', '12', '24', '17', '15', '59', 'sat']
0b1
['2022', '12', '24', '17', '16', '0', 'sat'] # アラーム2がマッチ
0b11 # 分ごとでマッチ
['2022', '12', '24', '17', '16', '1', 'sat']
0b11
Traceback (most recent call last):
File "", line 317, in
KeyboardInterrupt:
>>>
■read_Temp()
温度測定をします。温度計算後の値を返します。
※スクリプトをRun後にThonnyのshellで実行
>>> read_Temp()
18.75 # 部屋が寒い
>>>
まとめ
Raspberry Pi PicoでDS3231 RTCの動作を確認しました。
■追記
RTC DS3231が無くてもRaspberry Pi PicoのMicroPythonにクラス RTC — リアルタイムクロックがあるので
>>> from machine import RTC
>>> rtc = RTC()
rtc.datetime((year, month, day, weekday, hours, minutes, seconds))
※weekday 月曜日(0)-日曜日(6)
で日時を設定しておけば使えるような気がします。