リアルタイムクロックRTC-4543SA /WSN366 ボード (4)

今回は、シリアル通信の簡易的なコマンド処理を追加して、任意の日時が設定できるようにしました。

●シリアルコマンド

コマンドは2種類で、日時設定は12桁(固定長)の数字と[Enter]入力、現在日時の表示は文字は何も入力せずに[Enter]だけ入力、の2つだけです。コマンドは数字の桁数で判断しています。

具体的には、13年11月5日の21時30分0秒を設定する場合、”131105213000″+[Enter]と入力します。日時の数値が1桁の場合は必ず10の桁は必ず0を入れて12桁にする必要があります。数値12桁と[Enter]を順に入力すると、実際にRTCに設定された値をすぐに読み出して表示します。このとき間違いが無いか確認できます。

[Enter]だけ入力すると、現在の日時が読み出せます。

なお、ターミナル側は[Enter]入力でCRまたはCR+LFが出力されるようにしてください。コマンド文字列はCR(“\r”)が入力されたときにコマンド文字列の終端と判断し、続けてコマンドの処理を実行します。動作確認にはArduino-IDEのSerialMonitorも利用できます。

●シリアルコマンド処理

コマンド入力処理に関係する部分を抜粋して説明します。


#define TOINT(x) (x-'0')            // 0~9の数字を数値に変換

//
// メインループ
//
void loop(void) {
  char dat;

  if(Serial.available() > 0) {
    dat = Serial.read();
    cmdProc(dat);
  }
}

// ==================================================

//
// シリアルコマンド処理
//
void cmdProc(char dat) {

  if(dat == '\r') {   // CR
    cmdStr[cmdPtr] = 0;         // コマンド文字列終了 null挿入
    cmdPtr = 0;

    if(strlen(cmdStr) == 0) {
      // 現在日時の読み出し
      getString();                        // 日時取得
      printBcdDate(dYear, dMon, dDay);
      printBcdTime(dHour, dMin, dSec);

    } else {
      // 日時設定
      if(strlen(cmdStr) == 12) {
        //
        sYear = (TOINT(cmdStr[0]) * 16) + TOINT(cmdStr[1]);
        sMon  = (TOINT(cmdStr[2]) * 16) + TOINT(cmdStr[3]);
        sDay  = (TOINT(cmdStr[4]) * 16) + TOINT(cmdStr[5]);
        sHour = (TOINT(cmdStr[6]) * 16) + TOINT(cmdStr[7]);
        sMin  = (TOINT(cmdStr[8]) * 16) + TOINT(cmdStr[9]);
        sSec  = (TOINT(cmdStr[10]) * 16) + TOINT(cmdStr[11]);

        putString(0);                       // 日時設定

        // 確認のためすぐに読み出す
        getString();                        // 日時取得
        printBcdDate(dYear, dMon, dDay);
        printBcdTime(dHour, dMin, dSec);

      } else {
        // コマンド該当なし
        Serial.print(cmdStr);
        Serial.println(" ?");
      }
    }
  } else if(dat != '\n') {
    cmdStr[cmdPtr++] = dat;     // コマンド文字列蓄積
  }
}

1行目は”0″~”9″のASCII文字の数字を0~9の数値に変換するマクロです。

6行目からのloop関数(メインループ)では、”Serial.available()”でシリアルで文字を受信しているか調べ、10行目の”Serial.read()”で取り出しています。またその文字を”cmdProc()”へ渡して、そこでコマンドに応じた処理を実行します。

20行目の”cmdProc()”は1文字ずつ入力した文字列を蓄積してコマンドの文字列に組み立て、それを設定値として利用します。コマンド文字列の入力はCR(“\r”)で終了とみなします。なお、”\n”は読み捨てています。

22行目で入力文字がCR(“\r”)の場合、コマンド文字列の入力の完了とみなして、cmdStr[]の終端へNULLを追加し、コマンドに応じた処理を実行します。コマンド種別はコマンド文字列の文字数で判断します。今回は0文字か12文字の2通りです。

26行目でコマンド文字列の文字数が0のときはRTCより現在日時を読み出して表示します。

34行目で文字が12文字なら、36~41行目で文字列を数値に変換して日時設定のワーク変数へ設定し、43行目でRTCへ書き込みます。確認のため続けて46行目でRTCから日時を読み出して表示します。

36~41行目の処理は、2桁のBCDコードを一時的にヘキサコードに変換しています。16倍しているのは左に4ビットシフトするのと同じことです。

56行目で入力文字がCR(“\r”)でもLF(“\n”)でもない場合は数字とみなして、cmdStrのコマンド文字列の末尾へ挿入して数字列を蓄積させます。

●スケッチ(“Rtc4543P2.ino”)

前回の固定の日時設定、読み出しプログラムの固定値設定部分を削除して、前述のようなシリアル通信のコマンド処理を加えました。

おもな変更箇所は”loop()”にシリアル通信のバイト受信処理処理を追加し、コマンド関係の処理関数 “cmdProc()”を追加しました。それ以外のRTC制御関係の関数はそのまま使用しています。

/*
  リアルタイムクロック RTC-4543 評価
    copyright (c) 2013   www.wsnak.com

    13/11/03 新規作成
    13/11/05 シリアルコマンドで日時設定
*/

/*
    ディジタル・ポート・アサイン
      RTC-4543制御ポート
        D2: SCK
        D3: DATA
        D4: RW
        D5: CE

    シリアルコマンド
      >yymmdd hhMMss(Enter) ... 日時設定
      >(Enter)              ... 日時読み出し
*/

#define ON HIGH
#define OFF LOW

#define SCK_H digitalWrite(2, HIGH)
#define SCK_L digitalWrite(2, LOW)

#define WR_W digitalWrite(4, HIGH)
#define WR_R digitalWrite(4, LOW)

#define CE_ENA digitalWrite(5, HIGH)
#define CE_DIS digitalWrite(5, LOW)

#define DIR_IN  pinMode(3, INPUT)   // DATA (I/O)
#define DIR_OUT pinMode(3, OUTPUT)

#define GET_BIT    digitalRead(3)
#define PUT_BIT(x) digitalWrite(3, x)

#define TOINT(x) (x-'0')            // 0~9の数字を数値に変換

// 日時変数(読み出し用)
byte dSec;
byte dMin;
byte dHour;
byte dWeek;
byte dDay;
byte dMon;
byte dYear;

// 日時変数(書き込み用)
byte sSec;
byte sMin;
byte sHour;
byte sWeek;
byte sDay;
byte sMon;
byte sYear;

char strBuf[16];
char cmdStr[16];
byte cmdPtr;

//
// 初期化
//
void setup(void) {

  Serial.begin(19200);    // シリアル初期化

  pinMode(2, OUTPUT);   // SCK
  pinMode(3, OUTPUT);   // DATA (I/O)
  pinMode(4, OUTPUT);   // RW
  pinMode(5, OUTPUT);   // CE

  cmdPtr = 0;

  if(getFDTBit()) {
    // FDTビット=1(日時不正)
    // デフォルトの日付設定
    sSec  = 0x0;
    sMin  = 0x0;
    sHour = 0x0;
    sWeek = 0x1;
    sDay  = 0x1;
    sMon  = 0x1;
    sYear = 0x1;

    putString(0);                     // 日時設定
  }
}

//
// 年月日表示(BCD入力)
//
void printBcdDate(byte yr, byte mo, byte dy) {
  sprintf(strBuf, "%d%d/%d%d/%d%d ",
    yr>>4, yr&0x0F, mo>>4, mo&0x0F, dy>>4, dy&0x0F);
  Serial.print(strBuf);
}

//
// 時刻表示(BCD入力)
//
void printBcdTime(byte hr, byte mn, byte sc) {
  sprintf(strBuf, "%d%d:%d%d:%d%d",
    hr>>4, hr&0x0F, mn>>4, mn&0x0F, sc>>4, sc&0x0F);
  Serial.println(strBuf);
}

//
// メインループ
//
void loop(void) {
  char dat;

  if(Serial.available() > 0) {
    dat = Serial.read();
    cmdProc(dat);
  }
}

//
// 8ビット取得
//
byte get8Bit(void) {
  byte i, dat = 0;

  for(i = 0; i < 8; i++) {
    dat >>= 1;
    SCK_H;              // SCKパルス
    SCK_L;
    if(GET_BIT) {       // SCK立ち下がりで読み出し
      dat |= 0x80;      // b7
    }
  }

  return dat;
}

//
// 4ビット取得
//
byte get4Bit(void) {
  byte i, dat = 0;

  for(i = 0; i < 4; i++) {
    dat >>= 1;
    SCK_H;              // SCKパルス
    SCK_L;
    if(GET_BIT) {       // SCK立ち下がりで読み出し
      dat |= 0x08;      // b3
    }
  }

  return dat;
}

//
// 54ビット全データ読み出し
//
byte getString(void) {
  byte dat, fdt;

  DIR_IN;
  WR_R;
  CE_ENA;
  delayMicroseconds(1);   // セットアップタイム

  dat = get8Bit();
  if(dat & 0x80) {
    fdt = 1;
  } else {
    fdt = 0;
  }
  dSec  = dat & 0x7F;     // FDTビットをクリア
  dMin  = get8Bit();
  dHour = get8Bit();
  dWeek = get4Bit();
  dDay  = get8Bit();
  dMon  = get8Bit();
  dYear = get8Bit();

  CE_DIS;

  return fdt;             // FDTビットの状態を返す
}

//
// FDTビットの状態を読み出す
//
byte getFDTBit(void) {
  byte dat, fdt;

  DIR_IN;
  WR_R;
  CE_ENA;
  delayMicroseconds(1);   // セットアップタイム

  dat = get8Bit();        // 最初の8bit取得
  if(dat & 0x80) {
    fdt = 1;
  } else {
    fdt = 0;
  }

  CE_DIS;
  return fdt;             // FDTビットの状態を返す
}

//
// 8ビット出力
//
void put8Bit(byte dat) {
  byte i;

  for(i = 0; i < 8; i++) {
    PUT_BIT(dat & 0x01);    // DATAビット出力 (LSB)
    delayMicroseconds(1);   // セットアップタイム
    SCK_H;                  // SCKパルス
    SCK_L;
    dat >>= 1;
  }
}

//
// 4ビット出力
//
void put4Bit(byte dat) {
  byte i;

  for(i = 0; i < 4; i++) {
    PUT_BIT(dat & 0x01);    // DATAビット出力 (LSB)
    delayMicroseconds(1);   // セットアップタイム
    SCK_H;                  // SCKパルス
    SCK_L;
    dat >>= 1;
  }
}

//
// 54ビット全データ書き込み
//
byte putString(byte fdt) {
  byte dat;

  DIR_OUT;
  WR_W;
  CE_ENA;
  delayMicroseconds(1);   // セットアップタイム

  dat = (fdt << 7) | sSec;
  put8Bit(dat);
  put8Bit(sMin);
  put8Bit(sHour);
  put4Bit(sWeek);
  put8Bit(sDay);
  put8Bit(sMon);
  put8Bit(sYear);

  CE_DIS;
}

//
// シリアルコマンド処理
//
void cmdProc(char dat) {

  if(dat == '\r') {   // CR
    cmdStr[cmdPtr] = 0;         // コマンド文字列終了 null挿入
    cmdPtr = 0;

    if(strlen(cmdStr) == 0) {
      // 現在日時の読み出し
      getString();                        // 日時取得
      printBcdDate(dYear, dMon, dDay);
      printBcdTime(dHour, dMin, dSec);

    } else {
      // 日時設定
      if(strlen(cmdStr) == 12) {
        //
        sYear = (TOINT(cmdStr[0]) * 16) + TOINT(cmdStr[1]);
        sMon  = (TOINT(cmdStr[2]) * 16) + TOINT(cmdStr[3]);
        sDay  = (TOINT(cmdStr[4]) * 16) + TOINT(cmdStr[5]);
        sHour = (TOINT(cmdStr[6]) * 16) + TOINT(cmdStr[7]);
        sMin  = (TOINT(cmdStr[8]) * 16) + TOINT(cmdStr[9]);
        sSec  = (TOINT(cmdStr[10]) * 16) + TOINT(cmdStr[11]);

        putString(0);                       // 日時設定

        // 確認のためすぐに読み出す
        getString();                        // 日時取得
        printBcdDate(dYear, dMon, dDay);
        printBcdTime(dHour, dMin, dSec);

      } else {
        // コマンド該当なし
        Serial.print(cmdStr);
        Serial.println(" ?");
      }
    }
  } else if(dat != '\n') {
    cmdStr[cmdPtr++] = dat;     // コマンド文字列蓄積
  }
}

前回プログラムと異なり、78行目でFDTビットがセットされているときはデフォルト値を書き込むようにしました。FDTビットがセットされていないとき(時計が継続して作動しているとき)は何もしません。

シリアルのコマンド処理関係は前項を参照してください。

●使い方の例

ボタン電池を付けている場合は、このプログラムで日時を設定したあとは、Arduino本体から取り外しても独立した時計として作動します。この状態で他の制御回路に接続すれば、即、時計として使用できます。

1秒のクロック出力がピンから取り出せるため、マイコンの外部割り込み入力へ接続して割り込みを利用するとか、ポーリングするなどして、1秒のタイミングを得ることができます。時計の秒表示などにそのまま使えます。

コメントは受け付けていません。