稼働中

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外観

接続例

DS3231 RTC の動作電圧は2.3V~5.5VなのでRaspberry Pi Picoと接続できます。
I2C通信にはI2C1のSDA(GP10)SCL(GP11)を使いました。
DS3231接続図
接続出来たら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です。
DS3231アドレスマップ
※ Maxim Integrated DS3231 のデータシートから抜粋

■Clock and Calendar
カレンダは0x00~0x06のレジスタで設定します。読出しすれば時刻が分かります。
■Alarms
アラーム関係は0x07~0x0dのレジスタで設定します。A1,A2の2つあります。Bit7(Alarm Mask Bits)等でアラームモードを設定して他Bitでアラームの一致データを設定します。
DS3231_Alarm Mask Bits
※ 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)

で日時を設定しておけば使えるような気がします。