やっつけな日常(1) ラズパイ4のPython3でRTC-4543SAの読み書きテスト

Joseph Halfmoon

やっつけでご乱心な今回は、先週クロックの出力のみ確認してあった「RTC-4543SAモジュールのカレンダ・タイマを実際に読み書きしてみよう」の回です。読み書きに使うのはRaspberry Pi 4であります。ラズパイはネットワークに同期した実時間時計が使用できるので外付けRTCなど不要。RTCモジュールの初期化と動作確認の目的のみ。

(RTC-4543SAモジュールの読み書き実験に使用したRaspberry Pi 4上のPython3スクリプト全文を末尾に掲げました。)

Device Under Test

現物写真は先頭のアイキャッチ画像に掲げました。まずは、やっつけでご乱心なテスト回路を以下に示します。U2にRASPBERRYPI4とありますのは、ラズパイの40ピン拡張端子です。今回使用したのはRaspberry Pi 4(32bit版のRaspberry Pi OS)ですが、Raspberry Pi 3 などでも同じ端子配列なので、そのまま使えるのではないかと思います(実機で確かめておらんけど。。。)

そしてU3にあるのが、「先週ちょっと悲しかった」RTC-4543SAのモジュールです。秋月電子通商殿の2.54mmピッチ化基板なので、デバイスは悲しくなりましたが、基板レベルでは手にはいるもの。

そしてU1とありますのが、電源IC、3.3Vのレギュレータです。ご乱心の今回はラズパイからは電源をもらわず、RTCはバッテリで動かしておいて、ラズパイで時刻を設定した後、ラズパイの電源を落としても、そしてラズパイから取り外して他のデバイスに接続しても時刻を刻み続けてくれい、という勝手な願望であります。普通、RTCのバックアップといったら、場所を食わないCR系のボタン電池1個(電気を食わないからボタン電池でOK)というのが定番ですが、ボタン電池用の電池ホルダが手元にないし、単三電池3本のホルダがあったし、という成り行きで、単三3本の4.5Vから3.3Vを作って供給しています。使っているレギュレータICは、Microchip社のMCP1792です。このチップは、こんな目的に使うのが「モッタイナイ」チップです。後で別投稿するかもしれません。

4543Schematicさて、これでラズパイの拡張端子4本とRTCを接続し、読み書きする準備ができました。

読み書きするソフトウエア

やっつけでご乱心な今回、読み書きに使用いたしましたのは、Python3であります。書きなぐっても動くだろ~、コメントなくても分かるだろ~という目論見。RTCの端子の意味はだいたい以下の通りです。

CE端子をHIGHにするとシリアル入出力可能となります。LOWだと入出力端子は無視されるみたいです。配線をラズパイから引き剥がすような乱暴なことをしても誤動作しないで欲しいという儚い期待のもと、気休めの33kΩでプルダウンしています。ホストからHIGH入れればHIGHになるのでOKと。読み書き用の関数を走らせる期間CEはHIGHで維持します。

WR端子は、読み出し、書き込みの切り替えです。HIGHで書き込み(カレンダ時刻の設定)、LOWで読み出しです。

そしてCLKとDATAでシリアル入出力するのです。CLKは立ち上がりエッジで動作です。書き込むときは立ち上がりエッジの前に書き込むデータをDATA線に出力しておいてクロックを立ち上げます。読み込むときは立ち上がりエッジの後(当然立下りの前に)、ちょっとしてからデータを読みだします。「ちょっとしてから」のタイミングですが、それほどシビアな動作を狙っていない「やっつけ仕事」なので、タイミングはいい加減です。デバイス的には相当遅くて大丈夫(速過ぎるとダメ)なので、Pythonインタプリタでゆるゆる操作すれば「ちょうど良い感じ」でタイミングはとれてるみたいです。やっつけなので波形も確認していません。

データのフォーマット自体は、RTCにはありがちな伝統のBCD(バイナリ・コーデッド・デシマル)で、LSBファーストです。秒のLSBから、年のMSBにむけて52ビットの順です。時と日の間の曜日だけが4ビットで、秒、分、時、日、月、年は8ビットずつです。フィールドにより1から31までとか、BCD表記上、上位2ビットくらい余るビットが出ます。単なるメモリとして使える部分があったり、電源監視ビットや、テスト用の内部使用のビットなどがアサインされていたりします。そいつらはとりあえず0にしておけば良い感じ(詳細はデータシート確認。)

1点問題は、曜日のエンコードです。1から7を循環するようになっているようです。0はありません。デバイス・ベンダのセイコー・エプソン的には月曜が1始まりにして欲しいみたいですが、何ら制約はないです、日曜を1にしたってOK。しかし、世の中のPython的には月曜が0であるので、Pythonからカレンダを入出力する場合は要変換です。

末尾のコードは、やっつけでご乱心なのでPythonの曜日をそのまま代入していました。今気づきました。ハッキリ言ってバグ。でも読み出しのときは、曜日を無視しているので問題なし。そんなことで良いのか。

さて、末尾に掲げたプログラムを走らせると

  1. まずラズパイの実時間時計の時刻を表示する
  2. その時刻をRTCに書き込む
  3. ベリファイのためRTCから時刻を読み出して表示
  4. 10秒まつ
  5. しつこくRTCから時刻を読み出して表示(だいたい10秒たっていたらやっつけ仕事は成功)

動作の様子は以下です。

$ python3 test.py
RPi DATETIME >> 21-10-26 20:05:39
Read from RTC>> 21-10-26 20:05:39
Sleep 10 seconds.
Read from RTC>> 21-10-26 20:05:49

動いたね。

ラズパイ4の電源を落としました。ほぼ一日待ちました。勿論、RTCはバッテリで動作している筈。先ほど、再び読み出してみました。今回は、

  1. ラズパイの実時間時計の時刻を表示する
  2. RTCから時刻を読み出して表示

の2ステップだけ。こんな結果。

RPi DATETIME >> 21-10-27 16:44:17
Read from RTC>> 21-10-27 16:44:16

ラズパイ時刻とRTC時刻が1秒ずれてますが、ご愛敬ですかね。だいたい初期化の書き込み時点で、最悪0.999999…秒と1秒近い誤差がありえるので。RTCのデバイス的規格では無調整で月差1分相当とありますが、実力はもっと良いのではないかと想像。

動けばいいんだよ、動けば。(やっつけでご乱心)

デバイスビジネス開拓団トップへ

やっつけな日常(2) Scilab+Xcosで、伝達関数からシミュレーション へ進む

実験に使用したPython3スクリプト全文
#RTC-4543SA TEST
import RPi.GPIO as GPIO
import datetime
import time

GPIO.setmode(GPIO.BCM)

DATA_PIN = 17
CLK_PIN = 27
WR_PIN = 22
CE_PIN = 26

def initRTC4543pins():
    GPIO.setup(CE_PIN, GPIO.OUT)
    GPIO.output(CE_PIN, False)
    GPIO.setup(DATA_PIN, GPIO.OUT)
    GPIO.output(DATA_PIN, False)
    GPIO.setup(WR_PIN, GPIO.OUT)
    GPIO.output(WR_PIN, False)
    GPIO.setup(CLK_PIN, GPIO.OUT)
    GPIO.output(CLK_PIN, False)   

def iowait():
    for _i in range(2):
        pass

def bitRead():
    GPIO.output(CLK_PIN, True)
    iowait()
    lv = GPIO.input(DATA_PIN)
    GPIO.output(CLK_PIN, False)
    iowait()
    return lv    

def dataRead():
    GPIO.setup(DATA_PIN, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
    bitLis = list()
    GPIO.output(CE_PIN, True)
    for _i in range(52):
        bitLis.append(bitRead())
    GPIO.output(CE_PIN, False)
    GPIO.setup(DATA_PIN, GPIO.OUT)
    GPIO.output(DATA_PIN, False)
    return bitLis

def bitWrite(bv):
    GPIO.output(DATA_PIN, bv)
    iowait()
    GPIO.output(CLK_PIN, True)
    iowait()
    GPIO.output(CLK_PIN, False)
    iowait()

def dataWrite(lis):
    GPIO.output(WR_PIN, True)
    GPIO.output(CE_PIN, True)
    for i in range(52):
        bitWrite(lis[i])
    GPIO.output(CE_PIN, False)
    GPIO.output(WR_PIN, False)
    GPIO.output(DATA_PIN, False)

def decode4543(L):
    Lsec = L[6]*40 + L[5]*20 + L[4]*10 + L[3]*8 + L[2]*4 + L[1]*2 + L[0]
    Lfdt = L[7]
    Lmin = L[14]*40 + L[13]*20 + L[12]*10 + L[11]*8 + L[10]*4 + L[9]*2 + L[8]
    Lh   = L[21]*20 + L[20]*10 + L[19]*8 + L[18]*4 + L[17]*2 + L[16]
    LDoW = L[26]*4 + L[25]*2 + L[24]
    LDay = L[33]*20 + L[32]*10 + L[31]*8 + L[30]*4 + L[29]*2 + L[28]
    LMon = L[40]*10 + L[39]*8 + L[38]*4 + L[37]*2 + L[36]
    LYea = L[51]*80 + L[50]*40 + L[49]*20 + L[48]*10 + L[47]*8 + L[46]*4 + L[45]*2 + L[44]
    return (LYea, LMon, LDay, LDoW, Lh, Lmin, Lsec, Lfdt)

def bcdLis(num):
    result = [0, 0, 0, 0]
    for i in range(4):
        result[i] = num & 1
        num >>= 1
    return result

def encode4543(tpl):
    lis = []
    lis.extend(bcdLis(tpl[6] % 10))
    lis.extend(bcdLis(tpl[6] // 10))
    lis.extend(bcdLis(tpl[5] % 10))
    lis.extend(bcdLis(tpl[5] // 10))
    lis.extend(bcdLis(tpl[4] % 10))
    lis.extend(bcdLis(tpl[4] // 10))
    lis.extend(bcdLis(tpl[3] % 10))
    lis.extend(bcdLis(tpl[2] % 10))
    lis.extend(bcdLis(tpl[2] // 10))
    lis.extend(bcdLis(tpl[1] % 10))
    lis.extend(bcdLis(tpl[1] // 10))
    lis.extend(bcdLis(tpl[0] % 10))
    lis.extend(bcdLis(tpl[0] // 10))
    return lis

def printCAL(mes, tpl):
    print("{0}>> {1:02d}-{2:02d}-{3:02d} {4:02d}:{5:02d}:{6:02d}".format(mes, tpl[0], tpl[1], tpl[2], tpl[4], tpl[5], tpl[6]))

def dateTimeNow():
    now = datetime.datetime.now()
    return(now.year-2000, now.month, now.day, now.weekday(), now.hour, now.minute, now.second, 0)   

def main():
    initRTC4543pins()

    tpl = dateTimeNow()
    printCAL("RPi DATETIME ",tpl)
    dataWrite(encode4543(tpl))

    tplR = decode4543(dataRead())
    printCAL("Read from RTC", tplR)
    print("Sleep 10 seconds.")
    time.sleep(10)
    tplR = decode4543(dataRead())
    printCAL("Read from RTC", tplR)    
    GPIO.cleanup()


if __name__ == '__main__':
    GPIO.setwarnings(False)
    main()