前回までのシフトインジケーターの部分は簡単にまとめると、ポジション信号線を直接マイコンにぶっ込んで、(電気信号が)流れるか、流れないかという2択を判断するだけだった。
意外と簡単にできるなぁ、という印象を持ってもらえただろうか。
今回は日付と時刻を表示させる部分に取り掛かろう。こちらもすごく簡単にできる。チャレンジしてみてほしい。
概要(しくみ)
マルチメーターの本体として使っているSeeeduino XIAOにはRTC(リアルタイムクロック)が搭載されており、最初に日付と時刻を設定してやれば「通電している限りは」時間を刻んでくれる。
しかしスーパーカブに搭載するとなると、電源はカブのバッテリーから取ることになる。もちろんエンジンを切ったら電源は切れてしまう。
そこで内蔵のRTCは使わずに、メインの電源が落ちている状態でも日付や時刻を保持できるように、バックアップ電源にボタン電池を搭載したRTCモジュールを別に用意してやる必要がある。
RTCモジュールにはいろいろな種類があるが、「DS3231」か「DS1307」と表記のあるものを選ぶ。
僕は3種類買ってみたが、最終的に搭載したのは上の真ん中のもので、3つの中では一番サイズが小さい。 画像で見ると大きさが想像できないが、実際の大きさは人差し指の先ほどの大きさしかない。
左端のものはサイズが大きく、今回のマルチメーターには少し厳しかった。 ただ最初からピンが付いておりブレッドボードにさすのに都合が良かったため、テスト段階ではこちらを使用した。
右端のDS1307搭載のものは温度センサーがついておらず、気温が取得 できない。
準備物
- Seeeduino XIAO
- ブレッドボード
- ジャンパー線
- RTCモジュール
ブレッドボード上で配線してみる
RTCモジュールはI2C(IIC, I2Cとも表記される。ここでは以下 I2Cと表記する)という通信方法でマイコンとやり取りをおこなう。
やりとりに必要な配線は4本で、
- VCC
モジュールへの電源供給。Seeeduino XIAOから供給する。 - GND
マイナス配線。Seeeduino XIAOのGNDに返す。 - SDA
Serial Dataの略でデータ用の信号線。Seeeduino XIAOでは4番ピンを使うことが決められている。 - SCL
Serial Clockの略でクロック用の信号線。Seeeduino XIAOでは5番ピンを使うことが決められている。
となっている。
Seeeduino XIAOでは USB Cソケットを真上にして置いた状態で1番右上が5Vの電源供給、その下がGND、さらにその下が3.3Vの電源供給用のピンとなっている。
ブレッドボードに配線すると下の写真のようになる。
この記事の中で使用したRTCモジュールは紹介した3種類のうちで1番大きなサイズのものを使っている。前述した通りこのモジュールには品があらかじめハンダ付けされているのでブレッドボードで使いやすい。1番小さなものを使うときには、配線部分に ハンダ付けされているのがソケットになっているので、そのソケットにジャンパー線を差し込むことになると思う。ピンの配置は下の通り。
日付と時刻を取得するスケッチ
これまではプログラムと呼んできたが、Arduino(およびその互換機)ではプログラムのことをスケッチと呼んでいるので今後はそれに倣ってプログラムの事スケッチと呼ぶことにする。
ライブラリのインストール
まずはICモジュールから簡単に日付と時刻を取得するためにライブラリといわれるものをインストールする。ライブラリとはモジュールから情報を取り出すための命令集みたいなもの。
参考URL【Arduino】リアルタイムクロック(DS3231)で現在時刻の表示
https://101010.fun/iot/arduino-real-time-clock.html
こちらが参考サイトになるが、僕の場合はこの通りにしてもDS3232RTCライブラリがうまく動いてくれなかったので、別のライブラリをインストールして、そちらを使用した。
以下、インストール手順
サンプルスケッチから学ぶ
日付と時刻を取り出すと言う単純な用途なので、 ライブラリの命令系統も単純で種類も少ない。ここはサンプルスケッチから流用して必要なものを取り出せるようにしていく。
まずはサンプルスケッチを開いてみよう。
まずはこれをSeeeduino XIAOに書き込んで実行してみよう。
実行結果とサンプルスケッチの内容を見比べながら必要な部分(下リストの行番号ハイライト部分)だけ抜き出していく。
// Date and time functions using a DS3231 RTC connected via I2C and Wire lib
#include "RTClib.h"
RTC_DS3231 rtc;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
void setup () {
Serial.begin(57600);
#ifndef ESP8266
while (!Serial); // wait for serial port to connect. Needed for native USB
#endif
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
while (1) delay(10);
}
if (rtc.lostPower()) {
Serial.println("RTC lost power, let's set the time!");
// When time needs to be set on a new device, or after a power loss, the
// following line sets the RTC to the date & time this sketch was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}
// When time needs to be re-set on a previously configured device, the
// following line sets the RTC to the date & time this sketch was compiled
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}
void loop () {
DateTime now = rtc.now();
Serial.print(now.year(), DEC);
Serial.print('/');
Serial.print(now.month(), DEC);
Serial.print('/');
Serial.print(now.day(), DEC);
Serial.print(" (");
Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
Serial.print(") ");
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.println();
Serial.print(" since midnight 1/1/1970 = ");
Serial.print(now.unixtime());
Serial.print("s = ");
Serial.print(now.unixtime() / 86400L);
Serial.println("d");
// calculate a date which is 7 days, 12 hours, 30 minutes, 6 seconds into the future
DateTime future (now + TimeSpan(7,12,30,6));
Serial.print(" now + 7d + 12h + 30m + 6s: ");
Serial.print(future.year(), DEC);
Serial.print('/');
Serial.print(future.month(), DEC);
Serial.print('/');
Serial.print(future.day(), DEC);
Serial.print(' ');
Serial.print(future.hour(), DEC);
Serial.print(':');
Serial.print(future.minute(), DEC);
Serial.print(':');
Serial.print(future.second(), DEC);
Serial.println();
Serial.print("Temperature: ");
Serial.print(rtc.getTemperature());
Serial.println(" C");
Serial.println();
delay(3000);
}
上の必要な部分のうち「//」で始まる部分はコメント行なのでプログラムの進行にはなんら関係ないので削除しても良い。
以下、コメント行を省き、少し解説を加えていく。
2〜6行目
#include "RTClib.h"
RTC_DS3231 rtc;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
- 「RTClibライブラリを使いますよー」という宣言みたいなもの。
- DS3231モジュールを「rtc」と呼びますよ、という宣言。
- 1週間の曜日の名称を配列にて定義。配列とは変数のセットのようなもの。
8〜37行目のsetup関数の中身
void setup () {
Serial.begin(57600);
#ifndef ESP8266
while (!Serial);
#endif
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
while (1) delay(10);
}
if (rtc.lostPower()) {
Serial.println("RTC lost power, let's set the time!");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}
- シリアルモニタを開始。
- シリアルモニタの接続待ち
- RTCモジュールが開始できない場合は「見つからない」旨をシリアルモニタに表示する。
- RTCモジュールが電源喪失によって日付時刻を記憶していない場合は、現在の日付と時刻(Seeeduino XIAOにスケッチを書き込んだ時点のものになる)をモジュールに書き込む。
- (最後のコメント行)日付と時刻を指定して書き込む場合のサンプル。
残りのloop関数内
void loop () {
DateTime now = rtc.now();
Serial.print(now.year(), DEC);
Serial.print('/');
Serial.print(now.month(), DEC);
Serial.print('/');
Serial.print(now.day(), DEC);
Serial.print(" (");
Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
Serial.print(") ");
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.println();
Serial.print("Temperature: ");
Serial.print(rtc.getTemperature());
Serial.println(" C");
Serial.println();
delay(3000);
}
この部分がメインで必要な命令が並んでいる。同じような文言が並んでいるが、その中で異なっている部分に注目しよう。そうすると何が「年」を取り出し、何が「分」を取り出しているかがわかるはずだ。
ちなみに「DEC」というのはDecimalの略で「10進数で」という意味合いなのであまり気にしなくて良い。
わかりにくいのは曜日を取り出しているプロセスだが、RTCモジュールは曜日も数字で返してくるので、最初に配列で定義した曜日文字列をその番号で呼び出している。
「daysOfTheWeek」という配列に0〜6までの7つの曜日文字列「{“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”}」が入っているが、このうちの”Tuesday”を取り出すには「daysOfTheWeek[2]」と書く。”Tuesday”は「daysOfTheWeek」組の出席番号2番と思ってもらえばよい。
配列 daysOfTheWeek はモジュールが返してくる曜日の番号に対応した曜日文字を定義してあるものだ。
マルチメーターに必要なものだけを抽出する
サンプルスケッチの中身をおおよそ理解できたところで、自分に必要なものだけを抜き出して、マルチメーターの表示の元となるものを作ってみた。
#include "RTClib.h"
RTC_DS3231 rtc;
// 曜日表示用の文字の配列
char daysOfTheWeek[7][5] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
void setup() {
Serial.begin(57600);
#ifndef ESP8266
while (!Serial);
#endif
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
while (1) delay(10);
}
if (rtc.lostPower()) {
Serial.println("RTC lost power, let's set the time!");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
// ★★★★★ RTCモジュールに日付を書き込むときのみコメント解除 ★★★★★
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
}
void loop() {
// 時刻の取得
DateTime now = rtc.now();
int y = now.year();
int mo = now.month();
int d = now.day();
int h = now.hour();
int m = now.minute();
float t = rtc.getTemperature();
// 時刻文字列の整形
char datestr[6];
sprintf(datestr, "%2d/%2d", mo, d);
char timestr[6];
sprintf(timestr, "%02d:%02d", h, m);
// 温度文字列の整形
char tempstr[5];
sprintf(tempstr, "%2.0f", t);
Serial.print(datestr);
Serial.println();
Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
Serial.println();
Serial.print(timestr);
Serial.println();
Serial.print(tempstr);
Serial.println();
Serial.println();
delay(3000);
}
前回と同じく (笑) 難易度が1段階ジャンプしたようで申し訳ない。サンプルスケッチと異なる部分に少し補足して説明を加えよう。
// 曜日表示用の文字の配列
char daysOfTheWeek[7][5] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
5, 6行目
曜日文字列の配列は曜日の語頭3文字のみとした。
// ★★★★★ RTCモジュールに日付を書き込むときのみコメント解除 ★★★★★
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
26〜28行目
RTCモジュールが電源喪失していない時でも、日付と時刻を書き込む場合にコメントを解除して(//を取り払う)使用できるように書いてある。
// 時刻の取得
DateTime now = rtc.now();
int y = now.year();
int mo = now.month();
int d = now.day();
int h = now.hour();
int m = now.minute();
float t = rtc.getTemperature();
32〜39行目
年月日と時間・分、そして気温をそれぞれ変数に代入している。
// 時刻文字列の整形
char datestr[6];
sprintf(datestr, "%2d/%2d", mo, d);
char timestr[6];
sprintf(timestr, "%02d:%02d", h, m);
// 温度文字列の整形
char tempstr[5];
sprintf(tempstr, "%2.0f", t);
41〜49行目
RTCモジュールから取得された情報(コンピュータの中での扱いは数字=計算できる)を文字(=計算できない)として整形している。
呪文のような「%02d」というのは「0埋め(1桁の数の場合は10のくらいに0を表示する)2桁数字」という意味。同様に「%2.0f」は「2桁の数字で小数点以下0桁」となる。
色々と数字を変えて実行してみて結果を比較してみるとより理解が深まると思う。
次回は電圧計をやろうか、タコメーターをやろうか、ちょっと迷っている。見てのお楽しみということで。
コメント
[…] Arduino互換機を使ったマルチメーターの制作〜その3〜日付と時刻の表示 […]