前回導入したDS3231モジュール(RTC)と、Cmod A7ボード(FPGA)で設計した実時間時計RTC400Sユニットとを組み合わせて、SPI接続TFT表示器(SPI-TFT)へ時計を表示するアプリケーションを完成させたいと思います。
時計アプリケーションを、以下のハードウェア構成で実現して行こうと思います。
こうして見ると、所謂RTC(Real Time Clock、実時間時計)とするハードウェアが2種類(RTC400Sユニットと、DS3231モジュール)存在しています。何で、RTCが2種類も要るのでしょうか?
DS3231モジュールはI2C接続の外部接続モジュールです。アクセスするには、毎回I2C通信プロトコルに従ったアクセスが必要です。通信速度は400kHzと速くはありませんし、毎回7バイト程のバーストアクセスが発生します。他のI2C通信モジュールも接続するケースを考えると、高々時計表示くらいな事で、I2Cバスのアクセストラフィックを増加させるのは得策ではありません。
毎秒、I2C経由で外部RTCモジュールから日時を読み出して表示するのは無駄な事です。
そこで、FPGA内部回路としてのRTC400Sユニットが活きてくるのです。
RTC400Sユニットであれば、秒カウンタの変化を検知して割り込みを発生することができますので、時計の表示更新を割り込みで処理することも可能です。何より、FPGA内部のユニットなのでアクセスも高速で容易です。
一方、バッテリバックアップによる実時間の計測に対処できないFPGA上のRTC400Sユニットでは、電源を投入する度に、手動で日時を合わせる必要が生じます。
DS3231モジュール等バッテリバックアップ付きの外部接続RTCモジュールを使い、FPGAボード上の回路が起動後に一度だけ、外部RTCモジュールから日時を読み出して内蔵RTC400Sユニットに同期しておくと、以降、外部RTCモジュールへアクセスする必要がなくなります。
これが、2種類のRTCを使い分ける理由です。
ハードウェアがギブンでMCU内部にRTCを搭載していないArduino UNO等では、これまで、汎用タイマの割り込みなどソフトウェアを駆使して実時間を刻んできました。例えばこちらを参照してください。こんな風にソフトウェアで作り込むより、RTC400Sユニットの方が手軽に使えるはずです。
Moscoviumの開発環境は、Blockly for Moscoviumです。完成したプログラムを以下にリンクしておきます。
完成プログラム:rtclock.bxml
※ブラウザによっては、XMLファイルをレンダリングして表示してしまうため、拡張子を.bxmlとしてあります。
Blockly for MoscoviumのXMLタブに貼り付けて、ご利用ください。アセンブルに必要なINCLファイルは、こちらからダウンロード可能です。
DS3231モジュールの日時を主時計として、DS3231モジュール → RTC400Sユニットへ同期し、SPI-TFTへと表示するアプリケーションとなっています。
コンパイルしてMoscoviumマイコンへ書き込むと、SPI-TFTに時計が表示され、同時にシリアルモニタから以下のコマンドを受け付けています。
シリアルモニタコマンド仕様:
プログラムが起動した時、I2Cバス上のDS3231モジュールから日時を読み出して、内部のRTC400Sユニットへ同期しています(【set】コマンド相当)。よって、使い始める最初にDS3231モジュールへ手動で日時を設定すると(【mset】コマンド)、後はシリアルモニタ無しで自立します。
なお、SPI-TFTに表示される時計は、RTC400Sユニットが刻む時刻です。
【set】コマンドを実行すると、DS3231モジュールから日時データを読み出しながら(ds3_read手続き)、秒カウンタ(ds3_sec変数)の更新タイミングを探します(下コード参照)。
秒カウンタが更新されたタイミングで、読み出した日時データをRTC400Sユニットへ設定しています(rtc_set手続き)。ちょっとした事ですが、これをしないと、DS3231モジュールとRTC400Sユニット間の同期に於いて最大で1sの誤差を生じてしまいます。
仮に主時計DS3231モジュールが、きっかり正しい日時を刻んでいたとしても、副時計RTC400Sユニットとの同期に於いて1sもの誤差が出るようでは時計アプリケーションとしては不合格でしょう。主時計が正確という保証もありませんが、せめて主時計と精密に同期しようとするコードであるべきです。
参考までに、JavaScriptで記述して見ると、以下の通りです。
// get date and time from DS3231 module
ds3_read();
// detect second counter update timing
for (var count = 0; count < 2; count++) {
rtc_sec = ds3_sec;
do {
ds3_read();
} while (ds3_sec == rtc_sec);
}
// set date and time to RTC400S unit
rtc_set(ds3_yea, ds3_mon, ds3_day, ds3_dow, ds3_hou, ds3_min, ds3_sec);
Blocklyのコードを引用したい場合は、画面写真を撮ってグラフィックデータとして貼り付けないとならないけど、JavaScriptだとテキストのコピー&ペーストで済むから楽だったりします。
C言語ではなくてJavaScriptな理由は、次回のお話です。
Cmod A7ボード、SPI-TFT、実時間時計DS3231モジュールを、以下表の様に接続してください。
SPI-TFTアダプタ | 接続 | Cmod A7ボード | 接続 | DS3231モジュール |
PMODピンヘッダ | ⇔ | PMODコネクタ | ||
3V3 | → | VCC | ||
GND | ⇔ | GND | ||
stws_scl | ⇔ | SCL | ||
stws_sda | ⇔ | SDA |
Cmod A7ボードには、こちらのデザイン(MSOC115RT1608)を書き込んでおいてください。以下図に実験中の様子を示します。
上写真の様に、Cmod A7ボードからの端子引き出しがし易くなるように使用している400タイポイントブレッドボードの空き領域へ挿して使える用、U字ジャンパ線で配線して見ました。こうして見ると、3.3V電源端子がPMODコネクタからしか引き出せないCmod A7ボードの仕様が残念です(3.3V電源だけ空中配線……)。
手始めに、こちらのプログラム(smbus_master.bxml)を書き込んで、I2Cバスの状態を確認しておきます。Blockly for Moscoviumのコンパイル手順については、こちらでご確認ください。
実験サンプル:【 】内はシリアルモニタからの送信データ
【scan】
OK+ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- 57 -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
OK: scan
DS3231(RTC)本体のスレーブアドレス0x68と、DS3231モジュール基板上に実装されたI2C EEPROM(24C32)のスレーブアドレス0x57(※)が検出されました。
こちらの24C256を使った実験では本来のスレーブアドレス0x50〜0x57の他に、シャドウアドレス0x58〜0x5fも検出されたのですが、今回の24C32では、0x57だけが検出できました。マークから「AT24C32」と読めたので、どうやらAtmel社(現在のMicrochip社)製のI2C EEPROMチップが搭載されているみたいです。AT24C32のデータシートを探して見てみたところ、シャドウアドレスに関する記述は全く無く、これが仕様である様です。
なお、24C32のスレーブアドレスは、DS3231モジュール基板上のはんだジャンパで変更可能です(0x50〜0x57)。
※0x57と言う事は、24C32のスレーブアドレス設定端子A2〜A0が全てプルアップされています。
続いて、こちらで作成したプログラムをコンパイルしてMoscoviumマイコンに転送してください。お待ちかね、時計表示を試します。転送後、システムリセットを実行すると、DS3231モジュールから読み出した日時をRTC400Sユニットへ同期してSPI-TFTに表示するアプリケーションになっています。
従って、SPI-TFTに表示している日時はRTC400Sユニットから読み出した日時です。シリアルモニタの【set】コマンドを実行しない限り、DS3231モジュールの日時はプログラムの起動時に一回しかアクセスしないので、起動後、DS3231モジュールを取り外しても、時計は表示(更新)され続けます。
以下の様にシリアルモニタでも確認しておきましょう。
実験サンプル:【 】内はシリアルモニタからの送信データ
OK+ 2000/01/01(0) 00:00:33
OK: set
RDY
【show】
OK+ DS3231; 2000/01/01(0) 00:00:43
OK: RTC400; 2000/01/01(0) 00:00:43
手元のDS3231モジュールで実行した結果を示しています。
手元に届いた時点で、バッテリバックアップ用電池は付属していなかったため、昔購入してあった電池(CR2025)をセットして、そのままCmod A7ボードへ接続しました。ですので、ご覧の通りDS3231モジュールからはパワーオンリセットによる初期値と思われる日時が読み出せました。
プログラム起動時に【set】コマンド相当の処理が実施され、DS3231モジュール → RTC400Sユニットへ同期されるため、RTC400SユニットからもDS3231モジュールと同じ日時が読み出せています。
※丸カッコ内は曜日コードSun0〜Sat6です。2000/01/01は土曜日(6)なので、値は正しくありません。単に、パワーオンリセットで初期化された値(0)が読めた結果です。
【mset,2025,09,23,09,55,00】
OK: mset,2025,09,23,09,55,00
PCから読み取った実験時点の日時を設定しました。SPI-TFTの表示も直ちに変わった筈です。
【show】
OK+ DS3231; 2025/09/23(2) 09:55:06
OK: RTC400; 2025/09/23(2) 09:55:06
今しがた【mset】コマンドでセットした日時が読み出せました。OKです。
プログラムの動作としては、起動時に一度だけ、DS3231モジュールから読み出した日時をRTC400Sユニットへ同期しています。その後はDS3231モジュール → RTC400Sユニット間の同期を実行していないため、各々のクロック周波数の公差等によって少しづつ時間がずれて行きます。
そのまま7時間ほど稼働させて、もう一度日時を読み出してみました。
……約7時間後。
【show】
OK+ DS3231; 2025/09/23(2) 17:00:00
OK: RTC400; 2025/09/23(2) 16:59:59
ちょうど、PCの時計表示が17:00:00を指した瞬間に上のコマンドを実行しました。
※PCの時計は、拙作iConClockが定期的にNTPサーバと同期しているため、常に正しい時刻を刻んでいると見なします。
上の結果では、RTC400Sユニットの方に1sの遅れが見られますが、SPI-TFTの表示時刻とPC時計の更新タイミングを目視確認した限り、1sの偏差はありませんでした。数100msくらいでしょうか、多少の偏差は認知できました。その程度です。
一般的に水晶発振器に於ける発振周波数の公差は50〜100[ppm](MAX)程度らしいので、7時間だと最大2.5s程の偏差が出る計算ではあります。どちらの水晶発振器も、「公差」(仕様)ほどの偏差は無かった、と言う事でしょう。
この実験によって、以下の結果を得ることができました。
DS3231モジュールはバッテリバックアップ付きなので、I2Cバスにぶら下げておきさえすれば、いつでも実時間を取得することができます。実時間が必要なアプリケーションには欠かせぬアイテムとなってくれることでしょう。