でもセンサ情報の記録(特に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へのクエリーはどれぐらいのスピードで回せるんだろう?
