こんにちは、ENGかぴです。
Arduino環境においてWireライブラリを使用してRTCモジュールに時刻の書き込みと読み込みができます。LCDとボタン(DI)を使って任意のデータをRTCモジュールに書き込みを行ってLCDに現在時刻を表示するようにします。
LCDはQAPASS1602(スターターキットに付属)を使用しています。RTCモジュールはRX8900を搭載したAE-RX8900(秋月電子)使用しています。過去記事のRTCを使用した例の応用編になります。
ArduinoでRTCを使用してファイル管理とAPIの実装する
ボタンを押して変数を変更しながらRTCモジュールに時刻を書き込みますが、モードを切り替えて書き込むようにしています。この方法は他のモジュールにデータをセットする場合にも使えるテクニックです。
Arduino UNOを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
RTCモジュールの情報をLCDを使って更新する
RX8900は温度補償発振器(DTCXO)を内蔵しており高精度で最大で月差13秒で時刻を管理できます。時刻データはI2C通信(Wireライブラリ)を使用することで取得することができます。
LCDは「LiquidCrystal.h」ライブラリを使用することで文字を表示することができます。使い方については下記を参考にしてください。
全体の構成

ArduinoとAE-RX8900(RTC)とLCDを組み合わせた配線例を示しています。I2CのSCLとSDAはプルアップする必要がありますがAE-RX8900モジュール内でプルアップ抵抗を有効にすると10kΩでプルアップすることができます。
SW1はRTCの時刻を書き換えるモードに遷移するボタンとして使用します。SW2は時刻設定するときのカウントをプラスする際に使用します。SW3はカウントをマイナスする際に使用します。
スタートすると時刻表示モード(通常モード)でスタートしSW1を長押し(2秒)すると時刻設定モードに遷移するようにします。時刻設定モードでは年→月→日→曜日→時→分→秒の順で設定できるようにします。
PR:RUNTEQ(ランテック )- マイベスト3年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール
LCDで表示する項目

LCDに表示するモードについて説明します。
- 現在時刻表示と温度
- 設定(年、月、日、曜日)
- 設定(時、分、秒)
1については1段目に2020/10/04 SUNのように年・月・日・曜日を表示します。2段目には20:30:00 22.63のように時・分・秒・温度を表示します。
2については設定モードに遷移したときに表示します。1段目にSET DATEを表示します。2段目に2020/のみを最初に表示し年の設定後モードを進めると2020/10/のように月まで表示します。月の設定後モードを進めると2020/10/04となり日の設定になります。モードを進めると曜日の設定になります。
3については2を設定後モードを進めると1段目にSET TIMEを表示します。2段目に20:と時を表示し設定後モードを進めると20:33:のように分まで表示します。分の設定後モードを進めると20:33:00となり秒の設定になります。
モードを進めると2に戻って年からの設定に戻りますが、モード遷移ボタンを長押し(2秒)すると時刻表示モード(1)に戻ります。
モード遷移による管理と時刻の設定
時刻表示モードでは1秒毎に時刻を更新しますが、言い換えると1秒ことにRTCの時刻データを読み込んでいると言えます。RTCの時刻設定は書き込みたい任意の時刻の値を準備しておきRTCにデータを送信することで行えます。
モード遷移を実装する

SW1とSW2、SW3の関係を示しています。SW1は長押しすると表示モードと設定モードの切り替えを行います。長押ししない場合は設定モードにおいて設定項目の選択に使用します。SW2は各設定項目においてカウントを+1する際に使用し、SW3はカウントを-1するときに使用します。
/*** モード切替処理 ***/
void ModeCng(){
if( difilt.di1 == 0 ){ //SW1が押されたか
if( timbtn1 == TIME_OFF ){
timbtn1 = MODE_CNG; //2分の時限をセット
}
}else{
timbtn1 = TIME_OFF;
btn1hold = false;
}
if( timbtn1 == TIME_UP ){
timbtn1 = TIME_OFF;
if( mainmode == MD_DATE ){
mainmode = MD_SET; //設定モードに遷移
//LCD初期化処理など
btn1hold = true; //SW1が押されたままかを判断するために使用
}else if(mainmode == MD_SET){
mainmode = MD_DATE; //表示モードに戻す
RX8900.setDateTime(&setdata); //RTCに設定データを書き込み
}
}
}
SW1を長押しするとタイマをスタートし2秒経過したときモードを切り替える処理を行っています。
表示モードと設定モードの切り替えと項目切り替えのボタンを区別している場合は特に意識する必要はありません。
RTCモジュールに時刻を設定する(サブモードの遷移)
RTCモジュールに時刻を設定するために変数を準備する必要があります。変数を任意の値にするためにSW2とSW3を押してカウントを更新していきます。設定項目をサブモードで管理しSW1が押されるまで同一のサブモードで変数の更新ができるようにします。
enum SET_MODE{
SET_YEAR = 0,
SET_MONTH,
SET_DAY,
SET_WEEK,
SET_HOUR,
SET_MINUTE,
SET_SECOND,
SET_MAX
};
SET_MODE submode; //サブモードを管理する変数
設定する項目を番号順に並べてSET_MODEを定義し、submodeの変数をSET_MODEの型で宣言しています。SET_YEARで年の設定が完了しSW1を押すとSET_MONTHにサブモードを進めて月の設定を行います。SET_SECONDが終わるまで繰り返していきます。
/*** サブモード(時刻設定)処理 ***/
void SetSubMode(){
char strbuf[2];
if( btn1hold == false ){ //SW1のONが解除されたか
switch(submode){
case SET_YEAR:
sprintf(strbuf,"%02d",SetDate(submode)); //文字を変換2桁の10進数
//年のLCD表示
if( difilt.di1 == 0 ){
//SW1を押された時下の処理
btn1hold = true;
submode = SET_MONTH; //次のモードに遷移する
}
break;
case SET_MONTH:
sprintf(strbuf,"%02d",SetDate(submode)); //文字を変換2桁の10進数
//月のLCD表示
if( difilt.di1 == 0 ){
btn1hold = true;
submode = SET_DAY;
}
break;
/*--------省略------------*/
case SET_SECOND:
sprintf(strbuf,"%02d",SetDate(submode)); //文字を変換2桁の10進数
//秒のLCD表示
if( difilt.di1 == 0 ){
btn1hold = true;
submode = SET_YEAR; //先頭の年に戻る
lcd.clear(); //表示を切り替えるためクリアする
}
break;
}
}
}
各サブモードにおいて変数を変更しながらLCD表示します。SW1を押してモードを進めた時SW1が解除されたのを確認して次のサブモードの処理を行うためにbtn1holdでボタンを押された状態を保持しています。
SW1のONが解除されたのを確認してから内部のサブモードによる処理を行うようにすることで誤判定によってサブモードが進みすぎるのを防ぐことができます。
表示モードの切り替えとサブモードの切り替えを別のSWで管理している場合は特に意識する必要はありません。
PR:(即戦力のスキルを身に着ける:DMM WEBCAMP 学習コース(はじめてのプログラミングコース))
時刻設定のカウントを管理する
SW2とSW3を押してカウント値を更新しながら年~秒の値を設定するため各サブモードに対応して値の上限と下限を決定するようにします。
/***年の更新の例***/
switch(date){
case SET_YEAR:
if( difilt.di2 == 0){//カウント+
if( btn2hold ){
btn2hold = false; //クリアするのでSW2が押されている間は処理しなくなる
if( ++setdata.year > 99 ){
setdata.year = 0;
}
}
}else{
btn2hold = true;
}
if( difilt.di3 == 0){//カウント-
if( btn3hold ){
btn3hold = false; //クリアするのでSW3が押されている間は処理しなくなる
if( setdata.year == 0 ){
setdata.year = 99;
}else{
--setdata.year;
}
}
}else{
btn3hold = true;
}
/*うるう年の判定*/
ret = setdata.year;
break;
}
SW2を押すとカウントアップするようにしています。マイコンは高速で処理しているため少しボタンを押しただけでも複数回押された判断をするためカウントが進んでしまいます。この対策のため一度ボタンを押すとONが解除されるまでカウントアップしないようにする必要があります。
SW2がOFFしたときbtn2holdをセットしておきONしたときにbtn2holdをクリアすることで一度だけカウントアップする処理を実施することができます。
SW3を押すとカウントダウンするようにしています。SW3もSW2と同様にbtn3holdを使ってSW3がONしたときに一度だけカウントダウンするようにしています。
年を設定するときにうるう年の計算をする必要があります。うるう年の定義は以下の通りです。
- 4で割り切れる
- 上記において100で割り切れる場合は平年とする
- 400で割り切れる場合はうるう年とする
ycnt = 2000 + setdata.year;
if( (ycnt % 4) == 0 ){ //4で割り切れる
if( (ycnt % 400)== 0){ //400で割り切れる
uruflg = true;
}else if( ( ycnt % 100 )== 0 ){ //100で割り切れる(平年)
uruflg = false;
}else{
uruflg = true;
}
}else{
uruflg = false;
}
4で割り切れるのを判定するために%(余りを求める)で計算を行い余りが0であれば割り切れています。4で割り切れると基本的にうるう年になりますが、100で割り切れる場合は平日とする条件や400で割り切れるときはうるう年とする条件があるので4で割り切れることを判定した後にこれら2つの条件によってうるう年の判断をしています。
/***日の更新の例(日付の上限の考え方)**/
const uint8_t daymax[4]={28,29,30,31};
uint8_t dcnt; //日の上限値を設定する
switch(setdata.month){
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
dcnt = daymax[3]; //31を上限とする
break;
case 4:
case 6:
case 9:
case 11:
dcnt = daymax[2]; //30を上限とする
break;
case 2:
if(uruflg ){ //うるう年
dcnt = daymax[1]; //29を上限とする
}else{ //平年
dcnt = daymax[0]; //28を上限とする
}
break;
}
日付の上限に関する考え方の一例を示しています。2月は28日または29日があるためうるう年の判定を行って日の上限を決めます。1,3,5,7,8,10,12月は31日あり4,6,9,11月は30日なので変数dcntにこれらを格納してカウント値の上限として処理するようにします。
/***WEEKの設定例***/
//SW2による処理(一部省略)
if( ++wcnt > 6 ){
wcnt = 0;
}
//SW3による処理(一部省略)
if( --wcnt < 0 ){
wcnt = 6;
}
setdata.week = ( 1 << wcnt); //曜日の値はシフト値で決まるため曜日が進む毎に左にシフト
WEEK(曜日)の設定のみ値の設定の仕方が異なります。SUN→MON→・・・→SATと進むにつれてビットをシフトさせて表現するようになっています。SW2、SW3でシフトする回数を更新し、値を確定する際にシフト演算して曜日を設定しています。

動作確認

電源を入れると初期画面が2秒表示されて時刻表示モードになります。電源を入れた場合はコールドスタートになるためRTCに初期値が書き込まれ、その値から時刻をカウントします。
SW1を2秒間長押しするとモード2に遷移し任意の日付を設定します。曜日の設定が完了すると次にモード3に遷移し任意の時刻を設定します。
設定モードから抜けると設定した各種データをRTCに書き込んでモード1に遷移し時刻を表示します。
ソースコード全体
ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。
リンクからZIPファイル形式のファイルをダウンロードし、任意の場所に展開していただくとテキストファイルが生成されます。
RTC(RX8900)のAPIのソースファイルは下記記事のソースコード全体でご確認ください。
ArduinoでRTCを使用してファイル管理とAPIの実装する
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
PR:アクセンチュアの転職なら【コンサルアクシスコンサルティング】
最後まで、読んでいただきありがとうございました。
時刻データをLCDで確認しながら設定することができるようになったことで温度計を搭載した時計を作ることができました。時刻の設定画面やモードの遷移のさせ方は様々な方法がありますが、ボタンを使った方法で今回の例は方法は有効だと思います。