先日導入したDS3231モジュール(RTC)のDS3231には温度補正機能付きの水晶発振器が内蔵されていて、I/Oレジスタの設定によって発振周波数を変動させ、より精度の高い実時間の計測が可能となっています。

今回は、DS3231モジュールの時間精度を計測して、内蔵水晶発振器の性能を確かめてみたいと思います。
早速、実験してみましょう。
こちらの実験装置を組み立ててMoscoviumに、こちらのソフトウェアを書き込んで実験します(以下図)。

実験サンプル:【 】内はシリアルモニタからの送信データ
……実験時の日時をtA=2025/10/04 22:09:00とする。
【mset,25,10,04,22,09,00】
OK: mset,25,10,04,22,09,00
この日の作業はこれだけで終了です。【mset】コマンドを実行した日時を忘れない様、控えておいて下さい。
ここから数日(なるべく長い時間)、バッテリバックアップの状態で放置してDS3231モジュール単体で時刻を刻ませておきます。
数日後、再び実験装置を組み立てて、以下の様に続けます。
……実験時の日時をtB=2025/10/08 21:32:00とする。
【show】
OK+ DS3231; 2025/10/08(3) 21:31:59
OK: RTC400; 2025/10/08(3) 21:31:59 +111/250
RTC400Sの時計に追加した、「+111/250」は、秒未満のプリスケーラカウンタ値です。111÷250=0.444が、秒の小数点以下に相当します。
約4日(tB−tA≒95.383h)放置して、読み出した時間はPCの時計と比べて約−0.556sの偏差(遅れ)がありました。
※PCの時計は、拙作iConClockが定期的にNTPサーバと同期しているため、常に正しい時刻を刻んでいると見なします。
正確な放置時間tB−tAを計算して、日差(1日で生じる、実際の日時からの偏差)を計算すると、手元のDS3231モジュールの日差は約−0.140sでした。ええっ?すごいな。昔入手して使っていたDS1302モジュール(下図)は、正確な放置時間を測定していなかったので参考データだけど、日差約7.5sという観測データが残っています。

DS1302モジュールの時間精度(日差約7.5s)に比べると、DS3231モジュールの時間精度(日差約0.140s)は、すごく高く見えます。
RTCの時間精度(日差)の大小は、ひとえに32,768Hz発振器の発振精度に依存します。発振精度が±10ppm異なれば、日差も±10ppm異なる関係にあるのです。ちなみに精度10ppmの場合、1日では以下計算くらいの偏差が出ます。
1日×10ppm=24h×3,600[s/h]×10÷106=0.864s
日差から逆算した発振精度は、以下の通りです。
一般的な水晶発振器の発振精度は50〜100[ppm]だと言われているので、DS1302でも、まあまあ、一般的な発振精度の範囲内では、あります。
DS1302や32kHz発振子が、どんなに頑張って発振精度を上げる努力をしたところで、所詮は全く異なるメーカが各々製造して販売している部品の寄せ集めです。メーカによる固有特性差も、個々の部品毎に存在する個体差も、全てが発振精度を変動させる要因となるわけです。
その合成ベクタが「50〜100[ppm]」なら、ある意味「よく頑張っている」部類ではないでしょうか?何しろ単位がパーセント(%、1/100)ではなく、ppm(Parts Per Million、1/1,000,000)なのですから。
一方のDS3231モジュールはと言うと、発振器も発振子もDS3231に内蔵しているので、以下の様な対処が可能なのです。
発振器を内蔵したDS3231の方が、発振精度を向上できる余地を有しています。
正直、DS1302モジュールでの経験から、日差約7.5sくらいの大きな偏差があるもの、と思っていました。「そうだとすれば、何らかの対処が必要」と言うのが実験の趣旨でした。
実測したところ、その予想は良い意味で裏切られ、DS3231モジュールの時間精度は日差約−0.140s(1.62ppm)と言う事で、実用上全く問題ないレベルだと判りました。
特に対処の必要は無く、現状のまま使い続ける事が可能と判断できます。素晴らしいです。
ですが、折角なので、DS3231に内蔵された温度補正機能付き水晶発振器の機能を確認しておこうと思います。
DS3231には、Aging Offsetレジスタ(以下、Agingレジスタ👴と略記します)と言うI/Oレジスタが存在しています。Agingレジスタは8ビットの符号付整数(−128〜127)が設定可能です。設定値によって、発振回路(下図)の負荷容量値(CL1,CL2)を変動可能な仕組みになっています。

負荷容量を変動させることによって、発振周波数を変動させることができるみたいです(DS3231データシートより)。
Agingレジスタの主たる目的は、発振周波数の温度補償を実行することです。
発振器の発振周波数は温度特性を持っています。DS3231には、チップ温度を計測可能な回路も内蔵されており、計測したチップ温度に応じてAgingレジスタ値を調整する事で、温度変動に対する発振精度を調整する事が可能なのです(下図)。

※DS3231データシートから引用
複数の曲線は、矢印で記入されたAgingレジスタ設定値に於ける発振周波数(縦軸)の変動を示しています。
恐らく、DS3231出荷時に、室温近辺(10〜35[℃]、横軸)で最高精度を示すような特性に調整されているので、個人が普通に室内で放置する分には、調整の必要は無いと思われます。もし、−10℃を下回る低温や、35℃を上回る高温環境下に設置する場合は、調整しないと時間精度が低下する場合があります。
再び、こちらの実験装置を組み立てて実験してみましょう。
実験サンプル:【 】内はシリアルモニタからの送信データ
【drd1,10】
OK: drd1: [0x10] = 0x00
I2C通信でDS3231のAgingレジスタ(アドレス0x10)を読み出してみました。初期値0x00が読み出せました。
【dwr1,10,7f】
OK: dwr1: [0x10] = 0x7f
【dwr1,0e,3c】
OK: dwr1: [0x0e] = 0x3c
Agingレジスタに0x7f(正の8ビット最大値127)を書き込みました。書き込んだ値を直ちに有効化するため、Controlレジスタ(アドレス0x0e)に0x3cを書き込む必要があります。
こちらのグラフより、Agingレジスタ=0x7fの室温(25℃)近辺では発振周波数が約7〜8[ppm]ほど変化する様に読み取れます。実験を続けます。
……実験時の日時をtA=2025/10/08 23:53:00とする。
【mset,25,10,08,23,53,00】
OK: mset,25,10,08,23,53,00
現在時刻を設定して、バッテリバックアップの状態でDS3231モジュールを放置しましょう。数日後、実験装置を組み立て直して、以下の通り実験を再開します。
……実験時の日時をtB=2025/10/11 20:08:00とする。
【show】
OK+ DS3231; 2025/10/11(6) 20:07:58
OK: RTC400; 2025/10/11(6) 20:07:58 +69/250
約3日(tB−tA≒68.250h)放置して、読み出した時間はPCの時計と比べて約−1.724sの偏差(遅れ)がありました。日差約−0.606sです。ppmを計算して見ると約−7.017ppmと言う事で、データシートのグラフと矛盾在りませんでした。加えて、Agingレジスタ値の正値(+)は遅れ、負値(−)は進みを表していることも判明しました。
従って、Agingレジスタの設定方法は以下の通りです。
えっと、Agingレジスタに0x80(−128、負の絶対値最大値)を書き込む実験もしたいところですが、恐らく、正負(進み/遅れ)の反転した同等な結果が観測されるはずだし、この実験は放置する時間が長くて面倒なので省略しますm(1^_^)m。
最初の実験で、DS3231モジュールは実用上、問題ない時間精度を有していることが判っています。もし、設置環境や経年劣化などによって、時間精度(日差)に無視できない程度の偏差がある様なら、Agingレジスタを個別に設定する事で日差±0.6sくらい(@室温)の調整ができると判りました。
蛇足ですが、温度センサの機能も確かめておきたいと思います。DS3231に内蔵された温度センサは、Temperatureレジスタ(アドレス0x11,0x12の16ビット、ビッグエンディアン)から読み出すことができます。
【drd1,11】
OK: drd1: [0x11] = 0x1b
【drd1,12】
OK: drd1: [0x12] = 0xc0
読み出し値は16ビット符号付整数で0x1bc0でした。有効なビット数は、符号ビットを含む上位10ビットで左詰め、10ビットの最下位2ビットは小数点以下の数値になっています。簡単には16ビット符号付整数0x1bc0を256で除すと、現在の温度を摂氏温度(℃)で読み取る事ができます。
0x1bc0÷256=27.75℃
上計算の通り、室内温度として矛盾在りませんでした(※)。DS3231は、VCCに有効な電源が供給されている間、64s毎に温度測定を自動実行しています。上の測定値は、VCCに電源を供給してから64s経過する前に読み出したので、何時か最後に使用した時の室温だったと思われます。
※温度センサの指示値は、正確にはチップのジャンクション温度Tjだと思われます。室温Ta、熱抵抗θja、チップの消費電力Pdと置くと、Tj−Ta=Pd×θjaの関係式が成り立ちます。触るとアチチってなるPCのCPUやGPU等と違って、RTCチップのPdは十分小さく、Pd→0と見なすことができるので、Tj≒Taと考えて差し支えありません。
少なくとも64s以上待てば自動的に温度測定が実施されますが、時間待ちしなくてもControlレジスタ(アドレス0xe0)のCONVビット=1を書き込むと、手動で温度測定を実行できるので試してみましょう。
【dwr1,0e,3c】
OK: dwr1: [0x0e] = 0x3c
【drd1,0e】
OK: drd1: [0x0e] = 0x1c
CONVビット=1を書き込みました(CONVビット以外のビットは初期値のまま)。すぐに読み出したところ、CONV=0に戻っており、測定が終了したことが判ります。Statusレジスタ(アドレス0x0f)にBSYフラグが存在しますが、こちらは、64s毎の自動温度測定状態を検知するためのフラグです。
【drd1,11】
OK: drd1: [0x11] = 0x1c
【drd1,12】
OK: drd1: [0x12] = 0x40
Temperatureレジスタを読み出したところ、最初とは異なる値が読み出せました。今度は0x1c40÷256=28.25℃と言う事で、こちらも、たった今計測した、実験当時の室内温度とみて矛盾在りません。
Temperatureレジスタの最上位ビットは符号ビットなので、負の温度も計測されます。計測した温度が−10℃を下回ったり、35℃を超えていた場合は、こちらのグラフ等を参考にAgingレジスタを設定して発振周波数を調整する事ができるという、至れり尽くせりです。
中々よく考えられていますね。
DS3231モジュール基板上には、I2C EEPROMチップも実装されているので、なんと、DS3231モジュールだけ接続すれば、日時+気温データを観測してEEPROMへログを記録する、なんていうアプリケーションが完成してしまいます。「一定時間毎に部屋の温度変化データを取得する」等の応用ができますね。


