Seeeduino XIAOでRTCモジュールの情報を更新する

組み込みエンジニア

こんにちは、ENGかぴです。

Arduino環境においてWireライブラリを使用してRTCモジュールに時刻の書き込みと読み込みができます。Seeeduino XIAOを使ってシリアルモニタから時刻設定の電文を送出し現在時刻を確認した方法についてまとめました。

RTC時刻のバックアップを行い時刻データが保持する方法についてもまとめています。

Seeeduino XIAOを使って動作確認を行ったことを下記リンクにまとめています。

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

RTCモジュールの時刻データの更新

RTCモジュールはRX8900を搭載したAE-RX8900(秋月電子)使用しています。温度補償発振器(DTCXO)を内蔵しており高精度で最大で月差13秒で時刻を管理できます。時刻データはI2C通信(Wireライブラリ)を使用することで取得することができます。

RX8900用のライブラリは過去記事で紹介したものを使用しています。

ArduinoでRTCを使用してファイル管理とAPIの実装する

全体の構成

Seeeduino XIAOとRX8900の配線例
Seeeduino XIAOとRX8900の配線例

Seeeduino XIAOとAE-RX8900(RTC)を組み合わせた配線例を示しています。I2CのSCLとSDAはプルアップする必要がありますがAE-RX8900モジュール内でプルアップ抵抗を有効にすると10kΩでプルアップすることができます。

RX8900モジュールは1秒経過(変更可能)する毎に/INTを出力するため時刻が更新されたタイミングを取得することができます。

RX8900モジュールの7ピン(VBAT)は時刻情報をバックアップしたい場合に電気二重層コンデンサなどのバックアップ電源を接続します。D1はSeeeduino XIAOの電源が切れた場合にバックアップ電源からSeeeduino XIAOに電源をバイパスしないように接続しています。

R3はC1のインピーダンスが低くなっている区間(電荷が空の状態)に発生する突入電流防止のため実装しています。RTCへの時刻書き込みはシリアルモニタによる電文の送信で行います。

時刻データ書き込みの電文

電文の構成
電文の構成

RTCに時刻データを書き込むための電文を自作して構成します。電文はシリアルモニタを使用するため文字コードになります。文字コードになるため2バイトデータになります。

例えば月の部分に10月を指定する場合は10をシリアルモニタに入力しますがシリアル通信のデータは0x31、0x30の2バイトデータに変換されています。電文の構成と内容をまとめています。

内容説明
ヘッダ(固定)文字コードでスタートを表すためS(大文字)を指定します。
年の下2桁を指定します。2021年なら21を指定する。
RTCが2099年まで対応しているため99が最大値となる。
1月から12月を指定します。
1月の場合は01のように2桁の文字になるように指定します。
1~31日を指定します。
通常あり得ない日を指定するとRTCの動作の保証ができなくなります。
例)2月の日指定で31日など
0時から23時を指定します。
1時の場合は01のように2桁の文字になるように指定します。
0分から59分を指定します。
5分の場合は05のように2桁の文字になるように指定します。
0秒から59秒をしてします。
5秒の場合は05のように2桁の文字になるように指定します。
曜日日曜日:00 月曜日:01 火曜日:02 水曜日:03
木曜日:04 金曜日:05 土曜日:06
エンド(固定)文字コードでエンドを表すためE(大文字)を指定します。
電文の内容と説明

例)2021年3月21日10時30分30秒-土曜日を指定する場合
S21032110303006E
と電文をシリアルモニタに入力して送信します。

電文構成に従わない場合はRTCのデータの更新を受け付けないようにしています。

電文の受信処理

リングポインタの説明図
リングポインタの説明図

シリアルモニタの送信を使用するためSerial.begin()をコールしておく必要があります。シリアルモニタで送信した文字を受信するためにSerial.read()を使います。

void setup() {

    Serial.begin(115200);
}

void loop() {

    while( Serial.available()){
        rxseeeduino.buf[rxseeeduino.wp] = Serial.read();
      
        if( ++rxseeeduino.wp >= RING_SZ ){ //次にデータを入れる場所を更新
            rxseeeduino.wp = 0;
        }
    }
    RxDataChk(); //電文チェック
}

Serial.available()でシリアル通信データが受信していることを確認し変数に一時保管しています。RxDataChk()で一時保管した電文が有効かの判断を行っています。

/* Data Rx check function add */
void  RxDataChk(){

    if( rxsz >= sizeof(rtcset) ){ //電文の長さ分のデータを受信したか
        adrs = &rtcset.header;
        for(uint8_t i=0; i< sizeof(rtcset); i++ ){ //rtcsetにデータを移す
            *adrs = rxseeeduino.buf[rxseeeduino.rp];
            adrs++;
            if(++rxseeeduino.rp >= RING_SZ ){
              rxseeeduino.rp = 0;
            }
        }

        if( rtcset.header == HEADER && rtcset.headend == END){ //ヘッダーとエンドを確認
            rxoutcnt = 0;
            RtcSetChk(); //RTCデータを生成して書き込む
        }
    }
    else{
        if( ++rxoutcnt >= 10 ){ //受信タイムアウト
            rxoutcnt = 0;
            rxseeeduino.rp = rxseeeduino.wp;
        }else{
            delay(10);
        }
    }
}

RxDataChk()は電文を受信するとデータを変数rtcsetに移しヘッダーとエンドを確認します。受信データがデータ長にならずに一定時間(100ms)経過するとタイムアウトとみなしてリングポインタの値を更新してデータを破棄します。

/* Rtc Data Set function add */
void  RtcSetChk(){
    dateTime settim;

    settim.year = AscBin(rtcset.tim[0]) * 10 + AscBin(rtcset.tim[1]); //yearの例
    settim.week = 1 << AscBin(rtcset.tim[13]); //weekシフトでデータを書き込む(RTCの仕様)

    if( ( settim.year > 99 ) ||
        ( settim.month > 12 || settim.month == 0 ) ||
	( settim.day > 31 || settim.day == 0 ) ||
        ( settim.hour > 23 ) ||
        ( settim.minute > 59 ) ||
	( settim.second > 59 ) ||
	( AscBin(rtcset.tim[13]) > 6 )){// 範囲外
        //処理なし
    }
    else{
        RX8900.setDateTime(&settim); //RTCにデータを書き込む
    }
}

文字コードを受信するためRTCに書き込むデータをバイナリデータに変換する必要があります。yearのデータが21年の場合を考えると文字コードとして0x32、0x31の2バイトとなります。

これらの値をバイナリに変換すると0x32は2になり0x31は1になります。バイナリデータで21を構成するために1バイト目のバイナリ変換値を10倍しています。曜日を除く他のデータも同様に変換します。

曜日のデータはRTCモジュールの仕様上ビットが立っている箇所によって曜日を判定するためビットシフトで書き込むデータを生成しています。

RTCデータの範囲チェックを行って問題がなければRTCモジュールにデータを書き込みます。

簡易的なチェックとしているためあり得ない日時の組み合わせにおいても書き込みを行う可能性があります。矛盾した日時を設定した場合のRTCの動作は保証できません。

バックアップ時間は?

引用:RX8900のデータシート(消費電流)
引用:RX8900のデータシート(消費電流)

RX8900のデータシートを確認すると消費電流は最大で1.4uAであり時刻データが保持できる最小の電圧が1.6Vとなっています。またFOUT出力はOFFを前提にしています。

電気二重層コンデンサ1Fで何時間データが保持できるかについて検討します。下記記事に考え方をまとめています。電気二重層コンデンサの漏れ電流は参考記事の9.26uAを採用して計算します。

電気二重層コンデンサの静電容量によるバックアップ時間の考え方

電気二重層コンデンサの漏れ電流は9.26uAでRTCは最大で1.6uAが必要になるので合計10.66uAが消費されることになります。電圧が3.3Vから1.6Vになるまでのバックアップ時間を求めます。$$s = \frac{1\times1.7}{10.66\times10^{-6}}= 159474秒 = 2657分 = 44時間$$

最悪な条件で44時間程度のバックアップが可能になります。電気二重層コンデンサは温度特性によって漏れ電流や容量が変化することがありますが、室温程度に設置する場合において通常最悪なケースになる可能性はかなり低いと考えてよいでしょう。

動作確認

Seeeduino XIAOとRX8900モジュールの動作確認
Seeeduino XIAOとRX8900モジュールの動作確認

電源を入れると初期の日付がシリアルモニタに表示されて1秒ごとに更新されていますが、電文によって日付を更新して送信すると変更した日時を更新しています。

更新した日時は電源のUSBを抜いて電源をOFFした後に再び電源をONしてシリアルモニタでRTCの時刻を確認すると電気二重層コンデンサによるバックアップによりデータが保持されていました。

夜の10:30から翌日の8:30までの12時間においてのバッテリ電源の変化は3.28V→3.10Vでした。著しい温度変化などない限り4日程度のバックアップが可能であると言えます。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

Seeeduino XIAOのスケッチ:

#include <Wire.h>
#include "RX8900.h"

#define RING_SZ 256
#define HEADER  'S'
#define END     'E'

struct RING_MNG{
  uint8_t wp;
  uint8_t rp;
  uint8_t buf[RING_SZ];
};

struct TIMESET{
    byte    header;
    byte    tim[14];
    byte    headend;
};

// application use
const byte INT_PIN = 0;
dateTime tim;
uint8_t temp;
dateTime date =  {0, 30, 20, 1, 2, 10, 20}; //初期値-秒-分-時-曜日-日-月-年
TIMESET rtcset;
RING_MNG rxseeeduino;
byte  rxoutcnt; 

/*** Local function prototypes */
void  RxDataChk();
void  RtcSetChk();
byte  AscBin(byte asc);

void setup() {

    pinMode(INT_PIN, INPUT_PULLUP); //RX8900の時刻更新割り込みを受けるDI
    Serial.begin(115200);
    //RX8900.begin(); //初期値を指定しない場合(2020/10/01/00:00:00)
    RX8900.begin(&date); //初期値を指定する場合
}

void loop() {

    while( Serial.available()){
        rxseeeduino.buf[rxseeeduino.wp] = Serial.read();
      
        if( ++rxseeeduino.wp >= RING_SZ ){ //次にデータを入れる場所を更新
            rxseeeduino.wp = 0;
        }
    }
    RxDataChk(); //電文のチェック

    if (digitalRead(INT_PIN)) {
        if(rtcmode == RTC_MODE_TYP::RTC_RX_END ){
            rtcmode = RTC_MODE_TYP::RTC_IDLE;
        }
    }else{
        if(rtcmode == RTC_MODE_TYP::RTC_IDLE){
          rtcmode = RTC_MODE_TYP::RTC_RX;
        }
    }

    switch(rtcmode ){
        case RTC_MODE_TYP::RTC_IDLE:
            //処理なし
            break;
        case RTC_MODE_TYP::RTC_RX:
            RX8900.getDateTime(&tim);
        
            Serial.print("20");
            if(tim.year < 10){
                Serial.print('0');
            }
            Serial.print(tim.year);
            Serial.print("/");

            if(tim.month < 10){
                Serial.print('0');
            }
            Serial.print(tim.month);
            Serial.print("/");
        
            if(tim.day < 10){
                Serial.print('0');
            }
            Serial.print(tim.day);
            Serial.print("/");

            if(tim.hour < 10){
                Serial.print('0');
            }
        Serial.print(tim.hour);
        Serial.print(":");

        if(tim.minute < 10){
          Serial.print('0');
        }       
        Serial.print(tim.minute);
        Serial.print(":");

        if(tim.second < 10){
          Serial.print('0');
        }   
        Serial.print(tim.second);
        Serial.print(" ");
   
        switch (tim.week)
        {
            case SUN:
                Serial.print("SUN");
                break;
            case MON:
                Serial.print("MON");
                break;
            case TUE:
                Serial.print("TUE");
                break;
            case WED:
                Serial.print("WED");
                break;
            case THU:
                Serial.print("THU");
                break;
            case FRI:
                Serial.print("FRI");
                break;
            case SAT:
                Serial.print("SAT");
                break;
            default:
                Serial.print("NG");
                break;
        }

        Serial.print(" Temperature:");
        RX8900.getTemp(&temp);
        Serial.print(((float)temp * 2 - 187.19)/ 3.218);
        Serial.println("deg");
        rtcmode = RTC_MODE_TYP::RTC_RX_END;
        break;
        case RTC_MODE_TYP::RTC_RX_END:
            //タイムアップで異常を検出する場合の処理
            break;
    }
}
/* Data Rx check function add */
void  RxDataChk(){
    int rxsz;
    uint8_t sz;
    uint8_t allsz;
    uint8_t *adrs;
    uint8_t rp = rxseeeduino.rp;

    rxsz = rxseeeduino.wp - rxseeeduino.rp; //受信データ数の算出
    if( rxsz < 0 ){
      rxsz = rxsz + RING_SZ;
    }

    if( rxsz >= sizeof(rtcset) ){

        adrs = &rtcset.header;
        for(uint8_t i=0; i< sizeof(rtcset); i++ ){ //rtcsetにデータを移す
            *adrs = rxseeeduino.buf[rxseeeduino.rp];
            adrs++;
            if(++rxseeeduino.rp >= RING_SZ ){
              rxseeeduino.rp = 0;
            }
        }

        if( rtcset.header == HEADER && rtcset.headend == END){
            rxoutcnt = 0;
            RtcSetChk(); //RTCデータを生成して書き込む
        }
    }
    else{
        if( ++rxoutcnt >= 10 ){ //受信タイムアウト
            rxoutcnt = 0;
            rxseeeduino.rp = rxseeeduino.wp;
        }else{
            delay(10);
        }
    }
}
/* Rtc Data Set function add */
void  RtcSetChk(){
    dateTime settim;

    settim.year = AscBin(rtcset.tim[0]) * 10 + AscBin(rtcset.tim[1]);
    settim.month = AscBin(rtcset.tim[2]) * 10 + AscBin(rtcset.tim[3]);
    settim.day = AscBin(rtcset.tim[4]) * 10 + AscBin(rtcset.tim[5]);
    settim.hour = AscBin(rtcset.tim[6]) * 10 + AscBin(rtcset.tim[7]);
    settim.minute = AscBin(rtcset.tim[8]) * 10 + AscBin(rtcset.tim[9]);
    settim.second = AscBin(rtcset.tim[10]) * 10 + AscBin(rtcset.tim[11]);
    settim.week = 1 << AscBin(rtcset.tim[13]);

    if( ( settim.year > 99 ) ||
	( settim.month > 12 || settim.month == 0 ) ||
        ( settim.day > 31 || settim.day == 0 ) ||
	( settim.hour > 23 ) ||
	( settim.minute > 59 ) ||
	( settim.second > 59 ) ||
	( AscBin(rtcset.tim[13]) > 6 )){// 範囲外
        //処理なし
    }
    else{
        RX8900.setDateTime(&settim);
    }
}
/* Ack → Bin change function add */
byte  AscBin(byte asc){
    byte ret;

    ret = asc - 0x30;

    if( ret < 0){
        ret = 0;
    }

    return ret;
}

RTC(RX8900)のAPIのソースファイルは下記記事のソースコード全体でご確認ください。

ArduinoでRTCを使用してファイル管理とAPIの実装する

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

RUNTEQ-プログラミングで自由を手に入れる

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました