前回(「#2-4 二代目のスタータキット購入」)、二代目のスタータキットを購入した話をしました。今回は、その中に含まれていた、RTC(Real Time Clock)モジュールを動かしてみます。リアルタイムクロックと言うのは、日付と時刻を刻む時計のことで、2018/11/09 10:42:21などと、現在の日時を設定しておくと、明日には2018/11/10が読み出され、5時間後には、15:42:21が読み出せる機能のことです。
キットの中にRTCモジュールが含まれていることは、わかって買ったのですが、予想では、「バックアップ用の電池は含まれていない(別売り)」だったんですね。ところが、です。電池がついていたのです。もしかして、ちゃんと日時も設定されているでしょうか?書き込みは後回しにして、まずは、現在の時間を読み出してみたいと思います。
しかし、ですよ、前回「#2-4 二代目のスタータキット購入」ても注意喚起したように、キットの中にはデータシートの類が見つかりません。
まずは、RTCモジュールをじっくり観察します。
小さな基板には、バックアップ用のボタン電池(CR2032)と、DS1302とマークされた8ピンDIPのIC、おそらく32.768kHzと思われる発振子が載っているだけ。DS1302がRTCコントローラICですね。インターネットで検索したら、すぐにデータシートが見つかりました。
データシートを検索する(別ウインドウが開いて検索します)
Vccが2系統(Vcc1とVcc2)あって、Vcc1がバックアップ用で電池(CR2032、3V)につながっています。Vcc2は、マイコンから供給してもらう電源で、2〜5.5[V](TYP 3.3V)と言う仕様でした。TYP 3.3Vですが、Arduinoから3.3Vを繋いでしまうと、出力されてくる信号レベルも3.3Vになってしまうので、5VのArduinoと直結できなくなってしまいます。仕様範囲内ですので、Vcc2=5VをArduinoから供給します。
Vcc1+0.2V<Vcc2となると、DS1302はVcc2で駆動され、Vcc1>Vcc2となると、Vcc1から電源が供給されるように、内部の電源回路が構成されているようです。
MCUとの通信信号線は3本で、CE、I/O、SCLKの3本です。
レジスタの構成は、以下のレジスタが各8ビット(1バイト)づつ、存在しています。
各レジスタは、BCD(Binary Coded Decimal)で表現されています。1バイトを上位4ビットと下位4ビットに分割し、各4ビットで0〜9までの十進数を表現する方法です。従って、8ビットで表現可能な数値は、00〜99の100通りです。
レジスタ以外に、バッテリバックアップ付きのRAMが31バイトあります。用途は、目覚まし時計のようなアプリケーションを作るときのアラーム時間の記録用だとか、データロガを作る時の最後にログを取得した日時の記録用とか、でしょうか?
EEPROMやFlashメモリと異なり、制約が少なく、容易に読み・書きできる不揮発(バッテリバックアップ)RAMなので、色々と使えそうです。
アクセス方法には、シングルバイトリード/ライトと、バーストリード/ライトが存在します。
一種のクロック同期シリアル転送になっていますが、I2Cとか、SPIとか言った規格物のプロトコルに則っていないみたいなので、例によって、digitalWrite()でI/Oピンを操作して、アクセスすることになります。
プロトコルとしては、以下のような感じです。
シングルバイトリードの関数は以下の様になります。
int rtc_read_byte (
int adr)
{
int idx;
int dat;
int plv;
int bset;
if ((adr&1)==0)
{
// not read command
return (ErrBadCmnd);
}
// CE=1
digitalWrite (pin_CE, HIGH);
// Addr/cmnd byte
pinMode (pin_DAT, OUTPUT);
// Addr/cmnd byte
for (idx=0; idx<8; idx++)
{
digitalWrite (pin_DAT, (adr&1)? HIGH: LOW);
adr >>= 1;
digitalWrite (pin_CLK, HIGH);
if (idx==7)
pinMode (pin_DAT, INPUT);
digitalWrite (pin_CLK, LOW);
}
// read data byte
dat=0;
bset=1;
for (idx=0; idx<8; idx++)
{
plv=digitalRead (pin_DAT);
digitalWrite (pin_CLK, HIGH);
digitalWrite (pin_CLK, LOW);
dat |= (plv==LOW)? 0: bset;
bset <<= 1;
}
// CE=0
digitalWrite (pin_CE, LOW);
return (dat);
}
さっそく読んでみましょう。
void setup (void)
{
// Serial
Serial.begin (9600);
while (!Serial)
;
// RTC read
Serial.println (rtc_read_byte (0x81), HEX); // 秒レジスタ
Serial.println (rtc_read_byte (0x83), HEX); // 分レジスタ
Serial.println (rtc_read_byte (0x85), HEX); // 時レジスタ
Serial.println (rtc_read_byte (0x87), HEX); // 日レジスタ
Serial.println (rtc_read_byte (0x89), HEX); // 月レジスタ
//Serial.println (rtc_read_byte (0x8b), HEX); // 曜日レジスタ
Serial.println (rtc_read_byte (0x8d), HEX); // 年レジスタ
}
実行サンプル:
13
25
9
10
11
18
2018/11/10 09:25:13でしたね。
えっと、初めて読んだときは、日付は合っていました。時間は、1時間くらいずれてたかな?正確なずれ量はメモしてませんでした。少なくとも、日付が合っていたということは、日時を設定して出荷したのでしょうね。しかも、電池が取り付けられた状態だったので、Amazonの倉庫の中で、ひたすら時を刻んでいたようです。
記憶が定かではないので、はっきりしたことは言えませんが、もしかしたら、時間のズレは時差だったのかも知れません。Iduinoって、香港の会社ですから……。
レジスタアドレスについては、データシートをご覧ください。コマンド&アドレスのLSBがread(=1)/write(=0)になっているので、読み出すときは常に奇数アドレスになります。
基本的に、シングルバイトリードと同じです。書き込みを示すコマンド&アドレスバイトを送信した後、入出力が切り替わらず、そのまま、Arduinoが書き込みデータを駆動し続けるので、pinMode()でINPUTに切り替える件が無いくらいなものです。
リードと逆に、コマンド&アドレスのLSBは0(=write)なので、書き込みは常に偶数アドレスになります。
完成した関数は、完成版(rtc_ds1302.ino)でご確認ください。
さて、問題は、シングルリードと言うものが、はたして使えるのか?と言うことです。考えてみましょう。
まず、秒レジスタを読んで、59というデータを読み出したとします。次に、分レジスタを読んで、13というデータを読み出しました。今は、13分59秒でしょうか?
そうかもしれないし、そうではないかもしれません。
秒レジスタを読み出してから、分レジスタを読み出すまでの間に、秒カウンタが+1されてしまっていたとしたら?分レジスタの読み出し値が13でしたので、その時の時間は、12分59→13分00秒に変化する、ちょうどそのタイミングであって、運悪く秒レジスタから59、分レジスタから13を読み出してしまったケースです。
当然、分レジスタを読み出す前に、秒カウンタが変化していなかったとしたら、それは純然たる13分59秒だったことになり、どちらのケースもあり得てしまいます。
同じことが、分と時、時と日、日と月、月と年で、ひたすら起きまくります。
シングルバイトリードの実行結果に示したような日時であれば、精々、読み出した秒レジスタの値が±1程度の誤差を持っているくらいのことですが、12月31日23時59分59秒のタイミングだと、最悪なわけです。
そこで、DS1302では、CS端子の立ち上がりエッジで、日時カウンタの値を、セカンダリ(ユーザ)レジスタに取り込んで、そこからリードするような機構になっています。つまり、読み出しの途中で秒がカウントアップして、キャリが上がっていくようなことが起きないようになっています。
しかし、シングルリードでは、毎回CS端子が、LOW→HIGH→LOW→…と変化してしまい、毎回の立ち上がりエッジでカウンタ値を取り込み直してしまうため、先ほど提起したような問題が起きてしまいます。
セカンダリレジスタを正しく活用するためには、一回のCS端子立ち上がりエッジで、秒〜年レジスタまで一気に読み出すことのできる、バーストリードを使うしかありません。
バーストリードのコマンド&アドレスは、0xbfですので、シングルリードの時と同様に、コマンド&アドレスバイト(最初の8ビット)に0xbfを送り、後は8バイト分のデータを受信できるまで、SCLKの立ち下がりエッジを生成してやります(8バイト×8回=64回)。
バーストリード関数では、8バイト分の配列にデータを読み込んで返すようになっています。また、バーストリードは日時レジスタリード専用にしてあるので、バッテリバックアップRAMの読み出しは、シングルバイトリード関数を使ってください。
関数は、完成版(rtc_ds1302.ino)でご確認ください。
書き込みの方は、リードとは事情が違っていて、シングルバイトライトでも、正しく日時を設定することができます。よって、バーストライトは必ずしも必要ないので、実装していません。
RTCには、秒カウンタの下に、32.768kHzでカウントダウンされるカウンタが存在しています。このカウンタが0になると、秒カウンタが+1されます。
「秒レジスタにデータを書き込むと、このカウントダウンカウンタがリセットされる」と、データシートに記載されています。つまり、一度、秒レジスタに書き込むと、そこから1s経過するまでは、絶対に秒カウンタは+1されないことが保証されます。なので、日時をセット(書き込む)する場合には、「秒レジスタを最初に書き込み、続いて、残りの分、時、日、月、年レジスタを1s以内に書き込みなさい」と、データシートに記載されています。
つまり、1s以内に書き込むことが可能なら、シングルバイトライトで全日時レジスタを書き込んでも問題ないということです。
あ、面倒だったので、実装しませんでしたが、バーストライトでも、もちろん、問題なく日時を設定できますので、心配な方は、バーストライトを作って使用してくださいm(1__)m。
完成したプログラムをリンクしておきます。
RTC制御プログラム:rtc_ds1302.ino
Arduinoへコンパイル&書き込みしたら、シリアルモニタを表示してください。以下のようなコマンドを受け付けるようになっています。
RTCモジュールとの接続表を示しておきます。本当につなぐだけですので、実物回路ではデュポンワイヤで接続して完了です。モジュールの端子名と、DS1302の端子名が異なっていますが、CLK⇔SCLK、DAT⇔I/O、RST⇔CEが対応しています。端子名は変わっていますが、信号の意味は変わりません。
Arduino | 接続 | RTCモジュール | (DS1302) |
5V | → | VCC | |
GND | ⇔ | GND | |
D4 | → | CLK | SCLK |
D3 | ⇔ | DAT | I/O |
D2 | → | RST | CE |
実行サンプル:【 】内はシリアルモニタからの送信データ
【who】
OK: DS1302 ver.1.00
【time】
OK: 2018/11/03 14:05:46
【set,18,11,03,14,00,00】
OK: 2 …… OKの後の数字は、書き込みにかかった時間[ms]
もし、書き込み(【set】コマンド)に1,000ms以上かかると、エラーステータスが返ります。上の実行例では、2msですので、余裕ですね。
倉庫の中でも黙々と時を刻み続けていたRTCモジュールに感動しました(1T〜T)。健気です。