でもセンサ情報の記録(特に1Hz以上で)するんだったら、ミリ秒管理も必要だよね?
秒毎のinterruptとmillis()を組み合わせて、ミリ秒管理も可能なRTCライブラリを作ろう。
まずは・・・
ベースとして以下のライブラリを拝借させてもらう:
このライブラリはbegin()を呼ぶだけでRTCの初期化を全部やってくれて、available()を呼ぶと最新の時刻を取得、そしてyear(), month(), etcで年,月,...をそれぞれ取得出来る。
これを改造して、ms()でミリ秒も取得できるようにしよう。
ついでに、available()という命名が気に入らないので、updateTime()に変更。
秒毎の頭のmillis()を覚える
基本的な考え方は:
- 1秒に一回、秒が変わったタイミングをinterruptで受け取って、現在の秒の頭時点でのmillis()(=システム起動後の経過時間)を覚える(lastSecUpdateMS)
- 時刻取得を要求(updateTime())されたら、現在のmillis()からlastSecUpdateMSを引いて、ミリ秒を計算する。
まずはヘッダ:
class RTC8564ms { public: //time (in millis()) of the start of current second unsigned long lastSecUpdateMS; uint8_t data[7]; int _ms; RTC8564ms(int); void updateTime(); void setTime(long years, uint8_t months, uint8_t weekdays, uint8_t days, uint8_t hours, uint8_t minutes, uint8_t seconds); int ms(); uint8_t seconds(); uint8_t minutes(); uint8_t hours(); uint8_t days(); uint8_t weekdays(); uint8_t months(); long years(); bool century(); String toString(); };
メンバ変数の説明:
- lastSecUpdateMSに、現在の秒の頭millis()を格納
- data[]に、最後にupdateTimeを呼んだ時刻のyears〜secondsを格納
- _msに、最後にupdateTimeを呼んだ時刻のミリ秒を格納
メンバ関数の説明:
- ms(): _msを返す
- seconds()〜century(): そのまんま
- toString(): フレンドリーな文字列を返す
次に、コンストラクタ:
RTC8564ms::RTC8564ms(int interruptPin) { currentRTC=this; lastSecUpdateMS =millis(); _ms=0; //make sure we're in input mode pinMode(interruptPin,INPUT); //attach input for input handler attachInterrupt(interruptPin-2, interruptHandler,FALLING); for(int i=0;i<7;i++) data[i]=0; Wire.begin(); Wire.beginTransmission(RTC8564_SLAVE_ADRS); Wire.write((byte)0x01); //Control2 address Wire.write((byte)0x11); // TI/TP:1(繰り返し割り込みモード),TIE:1(定周期割り込み時、Lレベルの割り込み信号を発生させる) Wire.endTransmission(); Wire.beginTransmission(RTC8564_SLAVE_ADRS); Wire.write((byte)0x0E); // Timer Control address Wire.write((byte)0x82); //countdown period to 1Hz Wire.write((byte)0x01); //interrupt every second Wire.endTransmission(); }interruptを受け取るピン番号を引数に取る。Arduino Unoでは2か3のはず。
こちらを参考に、RTCモジュールのピン3をD2かD3に繋げる。
3行目: currentRTCに自分を覚えさせる。interruptのハンドラ(interruptHandler())はグローバル関数しか渡せないので、処理の対象をcurrentRTCで覚える。だったらそもそもsingletonとして設計しろって話だが、そのうち配列で覚えるよ。
7-9行目: 渡されたinterruptピンを念のためINPUTモードにし、interruptHandler(後述)を電圧降下時のinterruptとして登録する。
以降: 1秒に一回interruptが呼ばれるように設定
次は、interruptのハンドラ:
void interruptHandler(){ //record millis() of current second.000 currentRTC-> lastSecUpdateMS =millis(); }
これが呼ばれたということは秒が変わったということなので、currentRTCで登録されたオブジェクトのlastSecUpdateMSに今のmillis()を格納する。
次に、RTCの最新時刻を取得するupdateTime() (旧available())
void RTC8564ms::updateTime(){ Wire.beginTransmission(RTC8564_SLAVE_ADRS); Wire.write((byte)0x02); // write reg addr 02 Wire.endTransmission(); Wire.requestFrom(RTC8564_SLAVE_ADRS, 7); for(int i=0; i<7; i++){ if(Wire.available()){ currentRTC->data[i] = Wire.read(); } } long current=millis(); if(current<lastSecUpdateMS) _ms= current; else _ms=millis()-lastSecUpdateMS; }↑prettifyでコード内に「<」があるとバグるのがとてもウザい。なんとかならんのですかね?
RTCから年〜秒情報を読み込む。
更に、今のmillis()から、現在秒の頭で記憶したmillis()を引いて、差分を現在時刻のミリ秒として格納。
millis()は数ヶ月?ぐらいでオーバーフローするので、オーバーフロー時は適当に処理する。誤差の範囲だ誤差の。
ms(), years(), などはそのまんまの実装。
RTCに時刻を設定するための関数setTimeも、大して難しいことはない。詳しくは下記添付参照。
ライブラリ
RTC8564ms [zip]
以上ファイルをダウンロードして、Arduinoのlibrariesフォルダに置く。
実行してみる
projects: Biotope: Arduinoとリアルタイムクロック -1: 定周期タイマー割り込み & スリープで公開されている回路図に基づいて組んで、以下のサンプルコードを動かしてみる:
#include <Wire.h> #include <RTC8564ms.h> RTC8564ms *rtc; void setup(){ Serial.begin(9600); rtc=new RTC8564ms(2); //initialize using interrupt pin 2 rtc->setTime(2011,12,3,25,10,30,20); } void loop(){ //update rtc rtc->updateTime(); //print! Serial.println(rtc->toString()); delay(10); }
実行;
RTC8642ms実行結果 |
ちゃんと動いてるね。
まとめ
まとめ
- millis()を別管理してRTSと同じ簡便さでミリ秒も楽々管理出来るライブラリを作った
- interrupt中はmillisは止まるので、正直そこまで正確というわけではない。同じ秒のindexを管理するよりはマシってレベルだろうきっと。
- RTCへのクエリーはどれぐらいのスピードで回せるんだろう?