前回に引き続き、FPGAボード(Sipeed Lichee Tang Primer)でRTCを作ります。実機での動作確認を含め、完成させたいと思います。
以下に実時間時計RTC400ユニットのブロック図を示して、各サブブロックの詳細設計を進めます。
※薄黄色の箱はCPUからアクセス可能なI/Oレジスタです。
システムクロック(24MHz)や主電源が遮断された時、32kHzクロックとバックアップバッテリなどから給電して動き続ける必要があるのは、32kHzドメインのみ。
※上図の構成だと、バックアップ電源は、クロック選択回路を持つclkgenブロックにも供給しておかないとダメ。でも、24MHzクロックは停止していてOK(外部クロック入力rtc_clkinで動作する前提)。clkgenブロックを通らずに、直接clk32kが32kHzドメインへ供給されるような構成になっていれば、clkgenブロックも電源遮断しておいて良い。
以下にRTC400ユニットのI/Oレジスタ表を示し、解説を続けます。
Addr | Name | R/W | Bit:15〜0 | Memo | |||||||||||||||
0xf120 | rtcctl | RW | − | − | − | − | − | − | − | − |
EA VL |
ES EL |
LE AP |
− | − | − |
RT CW |
SN AP |
clock control |
0xf122 | rtcintc | RW | − | − | − | − | − | − | − | − | − |
HO UE |
MI NE |
SE CE |
− |
HO UF |
MI NF |
SE CF |
interrupt control |
0xf124 | rtcyear | RW | 0 | 0 | 1 | 0 | − | − | YEAR[9:0] | year counter | |||||||||
0xf126 | rtcmody | RW | − | − | − | MONTH[4:0] | − | − | DAY[5:0] | month and day counter | |||||||||
0xf128 | rtchrmi | RW | − | − | HOUR[5:0] | − | MINUTE[6:0] | hour and minute counter | |||||||||||
0xf12a | rtcscps | RW | − | SEC[6:0] | PSC[7:0] | second and prescaler counter | |||||||||||||
0xf12c | rtcweek | RW | − | − | − | − | − | − | − | − | − | − | − | − | − | WEEK[2:0] | day of week counter | ||
0xf12e | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | − | reserved |
BUSC出力のセレクト信号はbcs_rtcu_nです。上表の範囲でアサートされるので、RTC400ユニット内では、アドレスbadr[3:0]を復号してレジスタを特定しています。このI/Oユニットはバイトアクセス禁止です。必ずワードアクセスしてください。
I/Oレジスタ仕様:
初期値が「不確定」となっているカウンタレジスタにはリセットがかかりません。電源投入後の初期値は、不明です。その後、CPUから現在時刻を32kHzドメインのカウンタレジスタへ書き込んだ場合、リセットしても、その値を保持したまま時間を刻んでいます。
SNAPビットを1→0に変更した時、最大約30.5μs(clk32kの1周期)の間は、カウンタ値が最新の値に更新されません。
範囲外のBCD値や不正な日付、時刻などを書き込むと、そのまま(AS IS)動作します。恐らく、正しい動作にはなりません。整合の取れた正しい値を書き込む責任は、ソフトウェア(ユーザ)側にあります。
日時(カウンタレジスタ)の書き込み/読み出しには、以下の手順を踏む必要があります。
カウンタレジスタの書き込み手順:
カウンタレジスタの読み出し手順:
RTC400ユニットが動作するための32kHzクロックを、外部クロック入力(rtc_clkin)と内部クロック(24MHz→32,768Hz分周器)の何れかから選択する手順は以下の通りです。
外部クロック(rtc_clkin)選択手順:
内部クロック(疑似32kHz)選択手順:
FPGAボードの特性により、起動時ESELビット=0で初期化されることを期待していますが、万一、ESEL=1の状態で起動した場合に備えて、RTC400ユニットを使用開始する前に、ESELビットの値を確認することを推奨します。
尚、内部クロック(疑似32kHz)⇔外部クロック(rtc_clkin)の切り替えを行うと、その瞬間、32kHzクロックclk32kに不正な波形が発生するため、カウンタ値は保証されません。クロックを切り替えたら、必ず、全てのカウンタレジスタに現在時刻を設定し直してください。
新規設計したRTC400ユニットを搭載したマイコンを、MCOC115RT0408として作成しました。以下リンクより、デザインをダウンロードできます。
FPGAボード(Sipeed Lichee Tang Primer)への書き込み手順はこちらを参照してください。
以下に、TD IDEでのコンパイル(論理合成・配置配線)結果をまとめておきます。
なお、新規設計したRTC400ユニットを単体でコンパイルしたところ、438LUT程の回路でした。回路はクロックドメイン毎のIPネットリストとなっていて、詳細は以下の通りです。
ご参考として、システムタイマSYSTIMユニットと、汎用16ビットタイマTIM162ユニットのLUT数も併記しました。
システムクロックや主電源が遮断された時、32kHzクロックとバックアップバッテリ(電池など)で動作し続ける必要があるのはrtc400_subブロックだけですので、LUT数で180LUTとなりました。秒カウンタから年カウンタまでのBCDリプルキャリインクリメンタが、そこそこ大きいみたいですが、まあ、だいぶ小さく作れたのではないでしょうか?
何しろRTC400ユニットは、秒カウンタ〜年カウンタまで、41ビット+プリスケーラ15ビットの、計56ビットカウンタですから、16ビットカウンタ+プリスケーラ16ビット(計32ビット)のTIM162と比べても、×1.75倍ものビット数があります(しかも、BCDカウンタ)。
まずは、実験に使うプログラムを以下にリンクしておきます。
実験用プログラム:test_rtc.asm
つい先日も「天気時計」を作ったばかりだし、ただの時計をOLEDディスプレイ等に表示するだけのプログラムを作っても仕方がないので、あくまで、RTC400ユニットのテスト用と言う事で、シリアルモニタ専用の軽いプログラムになっています。
アセンブルしてFPGAボードに書き込むと、以下のコマンドをシリアルモニタから受け付けています。FPGAボードへの書き込み手順についてはこちらをご確認ください。
シリアルモニタコマンド仕様:
プログラム(test_rtc.asm)とデザインを書き込んだFPGAボードにFTDI等のUSB⇔UART変換モジュールを接続して、電源を投入してください。
以下図は、実験中の様子です(写真は使い回し)。
実験サンプル:【 】内はシリアルモニタからの送信データ
RDY
【show】
OK: 2000/00/00 00:00:28 .0xe7/0x100
いきなりRTC400ユニットのカウンタ初期値を読み出しました。やっぱりFPGAボードのF/Fは、初期起動後0に初期化されている様です。秒カウンタだけは、書き込み終了から【show】コマンド実行までの秒数をカウントしていますが、その他のカウンタからは全て0が読み出せました。
小数点の後に表示された「.0xe7/0x100」は、1秒未満のプリスケーラ値(rtcscpsレジスタの下位8ビット)です。
【mset,2022,09,08,22,15,25】
OK: mset
【show】
OK: 2022/09/08 22:15:30 .0x0e/0x100
現在の日時を設定しました。ここで、しばらく放置します。
本プログラム(test_rtc.asm)では、内部24MHzから分周した疑似32,768Hzクロックで動作させているので、分周比は732+27÷64です。仮に、非整数比分周器が上手く動作していなくて、分周比=732(固定)で動作していたと仮定すると、その誤差は1−732÷(732+27÷64)≒0.0576%です。そのまま1時間放置すると、約2秒の誤差が生じます。
一時間以上放置して、実験を継続します。
【show】
OK: 2022/09/08 23:55:09 .0xf9/0x100
ちゃんと、今の時間が表示されましたか?放置していた時間も正しく時計を刻んでいたらOKです。当方の実験では、正しい時間が表示されました。
神としたPCの時計が、【show】コマンドで表示された時間となった瞬間にリターンキーを押下しているので、1秒未満のプリスケーラ値(1/256[s]カウンタ)も、ほぼ同じくらいの値が表示されています。1時間くらいでは、ちょっと短い気もしますが、まあ、疑似32,768Hzクロックの分周器も、想定通りに動作していると思って良い様です。
※プリスケーラ値に少〜し、偏差があるようにも見えますが、人間がリターンキーを押すタイミングと、そもそもの源発振24MHzクロックの精度にも関係するので、ちょっと、何とも言えません……。
今度は、リセット中にもRTCが時間をカウントし続けているかどうか確かめるため、暫く(数10秒間)リセットを掛けてみました。FPGAボードの全体リセットではなくて、Moscoviumマイコンにリセットを掛ける、ユーザリセットスイッチの方です(下図)。
※USBコネクタを挟んで、ユーザリセットスイッチの上に見えているのが、FPGAボードのリセットスイッチ。そっちじゃなくて、ユーザリセットスイッチの方を押す。
再起動したら、実験を続けます
RDY
【show】
OK: 2022/09/08 23:59:45 .0x27/0x100
再起動後、時間設定【mset】コマンドは必要ありませんよ。さっき、リセットする前に設定した時計をずっとカウントし続けているはずですから。いきなり【show】コマンドで時計を確認してください。日時がクリアされてしまっていたり、リセットスイッチを押していた時間遅れていたりしたら、アウトです。
正しく時間を刻んでいましたか?当方の実験では、リセットスイッチを押下していた10秒間に対する遅れも進みも無く、正しい時間が表示されました。
続いては、Moscoviumマイコンが異なる動作モードに遷移している間も、RTCが時間をカウントし続けているかどうか確かめるため、ブートモードに遷移してみます。
ブートリセットスイッチを押します(下図)。
FPGAボード上のRGB LEDが青色に点灯して、ブートモードに遷移したことを示しています。これで、全く異なるプログラムである、ブートローダが実行開始しました。
それでも、RTCはクロックと電源が供給されている限り、カウントを続けているはずです。
もう一度、今回のプログラム(test_rtc.asm)をダウンロード(上書き)してみます。詳細はこちら「#16-7 Moscoviumにブートローダを」を参照してください。
プログラム(test_rtc.asm)のダウンロードが終了したら、ユーザリセットスイッチを押して、実験を続けます。
RDY
【show】
OK: 2022/09/09 00:02:15 .0x03/0x100
正しく時間を刻んでいましたか?当方の実験では、問題なく、現在時刻が表示されました。ついでに、ちょうど日をまたいだ🕛ので、日カウンタへの桁上げも正しく実行されたことを確認できました。
夜も更けて来たので、実験は以上で終了します。
実験により、リセットしても、他のモードに遷移して戻って来ても、正しく時間を刻んでいることを確認できました。
今までソフトウェアで実時間時計を実装していたのですが、リセットしたり、割り込みを長時間禁止したりしてしまうと、その間、時計のカウントが止まってしまうし、そもそも、リセットしたことでカウンタが0に戻ってしまう等の制限がありました。
RTC400ユニットは、クロックと電源が供給されている限り実時間を刻み続けます。ソフトウェアよりは使い易い実時間時計を提供してくれるでしょう。
後日、別途時間を取って、更に長い時間のロードテストを実行してみました。
【mset】コマンドで日時を設定してから約10時間後、時計を確認したところ、秒カウンタまで正確な時を刻んでいることを確認しました。
う〜ん、これはこれで少し出来過ぎ……?
32,768Hz÷24MHzの非整数比分周器の論理が正常に動作している、と言う自信を持つには心強い結果だったのだけど、そもそもの「24MHz」源発振クロックの精度ってものがあります。気温や電圧、ノイズによる影響などもあるはずです。
一般に水晶発振子の表示周波数からの偏差は50〜100ppm(MAX)だそうです。ならば、「24MHz」の発振子には50〜100ppmの偏差があってもおかしくありません。→ +100ppmの偏差だと、24,002,400Hz。
仮に、偏差を100ppmと見積もると、1時間カウントした時に100ppmの誤差となるので、その誤差は以下となります。
1時間×100ppm=0.36s
10時間も放置したら、0.36s×10時間=3.6sの誤差が出てもおかしくない。にもかかわらず、実験結果は秒カウンタまで狂いなく表示されていました。
出来過ぎ、なのだけど、そういう結果なので仕方ないです。スペックとして50〜100ppm(MAX)の保証値なら、TYPでもっと精度の良い水晶発振子が使われていたとしても矛盾しないし、たまたま精度良く「24,000,000Hz」の水晶発振子に当たったのだ、と理解しておこうと思います。
ちなみに、神としたPC(デスクトップ)の時計は、拙作iConClockが1時間毎にSNTP問い合わせで時刻合わせしているPCなので、こっちの精度に間違いはありません。
PCに搭載されたRTCではありますが、iConClockのログで、とある1日のSNTP時刻合わせ状況を確認したところ、1時間当たりの平均で30ms程狂っていました。1sの狂いとして見えるためには、約33時間かかる計算です。FPGAボードの24MHz発振器が同等の精度なら、10時間で30ms×10時間=0.3s(1s未満)しか狂わない計算なので、あながち「あり得ない」結果ではなさそうです。
※別なPC(ノート)でもiConClockのログを調べたところ、1時間当たり約100ms程の狂いがありました。こちらの精度だと、約10時間で1sの狂いとして見えてきます。
本当は、うるう年判定とか、月の最終日なんかの桁上げ判定、秒カウンタ→年カウンタまでの全桁上げ、等が正しく機能しているか?確認すべきですが、そっちはシミュレーションで確認済みなので、割愛しました。