Arduinoで遊ぶページ

Arduinoで遊んだ結果を残すページです。
garretlab
電波時計モジュール

概要

電波時計モジュールを利用してJJYの信号を受信し、時刻情報を取り出します。

D606Cという電波時計モジュールを利用します。

データシート

今回利用したD606Cという電波時計モジュールは、40kHz/60kHzの両方の電波を受信できるモジュールです。私が購入したときは、モジュールと一緒にデータシートが入っているわけではなく、ウェブページを参照する必要がありました。以下に簡単にまとめます。

項目 値など
電圧 1.5-3.5V
消費電流(動作時) 450-650μA
動作周波数 40kHz, 60kHz

また、このモジュールの各ピンの意味は以下の通りです。

ピン名 意味 詳細
P Power on Input HIGHでパワーダウンモード、LOWで動作モード
TP ポジティブ信号出力 信号出力
TN ネガティブ信号出力 信号出力(反転)
G GND アース
V VCC 電源
F 動作モード HIGHで40kHzモード、LOWで60kHzモード

このモジュールが利用しているチップのデータシートを見ると、動作電圧は、1.2Vから5.0Vと書いてありますが、このモジュール自身は1.5Vから3.5Vとなっているので、Arduino Dueを利用しています。

タイムコード

日本では、NICT(情報通信研究機構)日本標準時を長波で送信しています。標準電波の出し方もウェブで公開されています。送信される情報(タイムコード)は、通常時(毎時15分、45分以外)のタイムコードと、呼出符号送出時(毎時15分、45分)のタイムコードの2種類があります。今回は、通常時のタイムコードから時刻情報を取り出します。

通常時のタイムコードの構成を以下に示します。1秒間に1つの情報を送信するので、1bps(0と1以外にマーカーと呼ばれる情報も含まれるので、正確にはbit per secondとは異なるかもしれませんが)という転送速度です。

データの種類

送信されるデータには、以下の3種類があります。

種類 パルス幅
マーカー、ポジションマーカー 0.2s ± 5ms
2進の0 0.8s ± 5ms
2進の1 0.5s ± 5ms

タイムコード

毎時15分と45分以外に送信されるタイムコードは以下の通りです。2進の1のときの意味に数字が入っている場合は、それらの値をすべて合計した値が実際の値です。また、Mはマーカー、Pxはポジションマーカーです。

0 1 2 3 4 5 6 7 8 9
意味 - -
2進の1のときの意味 - 40 20 10 - 8 4 2 1 -
固定値 M - - - "0" - - - - P1
10 11 12 13 14 15 16 17 18 19
意味 - - -
2進の1のときの意味 - - 20 10 - 8 4 2 1 -
固定値 "0" "0" - - "0" - - - - P2
20 21 22 23 24 25 26 27 28 29
意味 - - 1月1日から通算日
2進の1のときの意味 - - 200 100 - 80 40 20 10 -
固定値 "0" "0" - - "0" - - - - P3
30 31 32 33 34 35 36 37 38 39
意味 1月1日から通算日 - - パリティ 予備 -
2進の1のときの意味 8 4 2 1 - - PA1 PA2 SU1 -
固定値 - - - - "0" "0" - - - P4
40 41 42 43 44 45 46 47 48 49
意味 予備 年(西暦下2桁) -
2進の1のときの意味 SU2 80 40 20 10 8 4 2 1 -
固定値 - - - - - - - - - P5
50 51 52 53 54 55 56 57 58 59
意味 曜日 うるう秒 - - - - -
2進の1のときの意味 4 2 1 LS1 LS2 - - - - -
固定値 - - - - - "0" "0" "0" "0" P0

パリティは、PA1が「時」の、PA2が「分」にそれぞれ対応し、1ビットの偶数パリティ(パリティビットとあわせて、1を表すビットが偶数個になる)です。曜日は、日曜日が0で、土曜日が6です。

それぞれの分にその分の情報が送信されるため、実際にこの情報をもとに時計を合わせたりするときは、1分ずれることに注意する必要があります。

Arduinoからの操作

Arduinoとの接続

今回のスケッチでは、以下に示す通りの電波時計モジュールとArduinoの接続を前提としています。

電波時計モジュール Arduino
P 2
TP 13
TN 接続なし
G GND
V 3.3V
F 3

電波時計モジュールのTPをArduinoの13番に接続することにより、TPがHIGHになるとArduinoのLEDが点灯するので、電波を正しく受信しているときは、1秒周期で点滅します。

時刻情報の取得

初期設定

私は東日本に住んでいるので、FをHIGHにして、福島から送信している40kHzの電波を受信するようにしています。西日本の方は、FをLOWにして、九州から送信している60kHzの電波を受信するようにしてください。

次に、PをLOWにして、電波時計モジュールを動作させます。

情報の取得

各分の情報を取得するためには、当然、1分を要します。Arduinoは、基本的には、シングルスレッドで動作するので、例えば、時計を動作させているときに、時刻情報を取得するのに1分間他の処理を止めてしまっては本末転倒です。このため、割り込みを使うことにしました。loop()の中で見張ってもいいかも問題はないかもしれません。

今回利用しているArduino Dueは、どのピンも割り込みに対応しています。Arduino Unoは、2番ピンと3番ピンだけしか割り込みに対応していないので注意してください。

割り込みの設定モードには、LOW、CHANGE、RISING、FALLING、HIGHの5種類あります(Arduino Unoの場合はHIGHを除いた4種類)。今回は、HIGHになっている時間を調べる必要があるので、CHANGEを利用しました。

割り込みハンドラの中でdigitalRead()を実行するのがいいのかはよくわかりませんが、Arduinoのサイトでもロータリーエンコーダの例で利用しているのでおそらく大丈夫だと思います。組み合わせるアプリケーションによっては、割り込みハンドラの中ではフラグを設定するだけにして、実際の処理はloop()の中で実行するのがいいかもしれません。

開始位置の取得

JJYは、常時電波を出しているので、まず、情報の開始位置(毎分0秒)を取得する必要があります。前述の表を見ると、59秒と0秒とで、P0とMという(ポジション)マーカーが連続していることがわかります。このため、マーカーが連続している場所を見つけた後、1秒から情報を取得していくこととしました。

情報の保持

1分間分の情報を格納するには、60ビットあれば十分です。このため、64ビットの幅を持つlong longを利用して、ビットフィールドを定義してみました。

情報の取得

JJYでは、パルス幅が厳密に定義されていますが、実際に取得してみると、誤差が大きく出てきます。このため、かなり幅を持たせることとしました。

実際には、TPがLOWからHIGHになった時刻をtimeHighという変数に保存しておき、HIGHからLOWになった時刻から引くことで、パルス幅を求めています。

情報のチェック

固定的な値が送信される秒がいくつかある(ポジションマーカー等)ため、プログラムでカウントしている秒にそれらの値が送信されていないときは、エラーとしています。

スケッチ

Arduinoのスケッチは以下の通りです。

pinFについては、受信する周波数に合わせて設定してください。

#define F40KHZ HIGH
#define F60KHZ LOW
#define POWER_ON LOW
#define POWER_OFF HIGH

long markerMin = 50, markerMax = 350;
long highMin = 350, highMax = 650;
long lowMin = 650, lowMax = 950;

struct timeCode_t {
  unsigned long long code:60;
};

static int pinF = 3;
static int pinTP = 13;
static int pinP = 2;

volatile long timeHigh = 0;
volatile long timeLow = 0;

int getMinute(long long code) {
 return ((code >> 57) & 0b111) * 10 + ((code >> 52) & 0b1111);
}

int getHour(long long code) {
 return ((code >> 47) & 0b1111) * 10 + ((code >> 42) & 0b1111);
}

int getDay(long long code) {
  return ((code >> 37) & 0b11) * 100 + ((code >> 32) & 0b1111) * 10 + ((code >> 27) & 0b1111);
}

int getYear(long long code) {
  return ((code >> 16) & 0b1111) * 10 + ((code >> 12) & 0b1111);
}

int getDayOfWeek(long long code) {
  return ((code >> 8) & 0b111);
}

int getBits(unsigned char value) {
    value = (value & 0x55) + ((value >> 1) & 0x55);
    value = (value & 0x33) + ((value >> 2) & 0x33);
    return (value & 0x0f) + ((value >> 4) & 0x0f);
}

void intChange () {
  char buf[128];
  static char previousCode = '\0';
  char currentCode = '-';
  static int currentPosition = 59;
  static int sync = 0;
  static struct timeCode_t timeCode;
  int interval;
  
  switch (digitalRead(pinTP)) {
    case HIGH:
      timeHigh = millis();
      return;
    case LOW:
      interval = millis() - timeHigh;
      if (markerMin < interval && interval <= markerMax) {   // Marker
        currentCode = 'M';
      } else if (highMin < interval && interval <= highMax) {  // HIGH
        currentCode = 'H';
      } else if (lowMin < interval && interval <= lowMax) {    // LOW
        currentCode = 'L';
      } else {
        return;
      }
      break;
  }
  
  sprintf(buf, "Value = %d, %c", interval, currentCode);
  Serial.println(buf);
  
  if (sync) {
    switch (currentCode) {
      case 'M':
      case 'L':
        timeCode.code &= ~(1LL << currentPosition);
        break;
      case 'H':
        timeCode.code |= 1LL << currentPosition;
        break;
    }
    
    switch (currentPosition--) {
      // Position Marker
      case 51: case 41: case 31: case 21: case 11: case 1:
        if (currentCode != 'M') {
          Serial.println("Position Marker Error");
          sync = 0;
        }
        break;
      // Fixed to 0
      case 56: case 50: case 49: case 46: case 40: case 39: 
      case 36: case 26: case 25: case 5: case 4: case 3: case 2:
        if (currentCode != 'L') {
          Serial.println("Fixed 0 Error");
          sync = 0;
        }
        break;
      // Parity of hour
      case 24:
        if (((getBits(timeCode.code >> 42) & 0xff) % 2) != ((timeCode.code >> 24) & 1)) {
          Serial.println("Hour Parity Error");
          sync = 0;
        }
        break;
      // Parity of minute
      case 23:
        if (((getBits(timeCode.code >> 52) & 0xff) % 2) != ((timeCode.code >> 23) & 1)) {
          Serial.println("Minute Parity Error");
          sync = 0;
        }
        break;
      case 0:
        sprintf(buf, "%02d:%02d, %03ddays, %2dyear, %1d Day of wees", 
          getHour(timeCode.code), getMinute(timeCode.code), getDay(timeCode.code), getYear(timeCode.code), getDayOfWeek(timeCode.code));
        Serial.println(buf);
      
        for (int i = 59; i >= 0; i--) {
          if (timeCode.code & (1LL << i)) {
            Serial.print("1");
          } else {
            Serial.print("0");
          }
        }
        Serial.println("");
        currentPosition = 59;
        break;

    }
    
  } else {
    if (previousCode == 'M' && currentCode == 'M') {
      sync = 1;
      currentPosition = 59;
    }
  }
  previousCode = currentCode;
}

void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);

  pinMode(pinF, OUTPUT);
  pinMode(pinTP, INPUT);
  pinMode(pinP, OUTPUT);

  digitalWrite(pinF, F40KHZ);
  digitalWrite(pinP, POWER_ON);

  attachInterrupt(pinTP, intChange, CHANGE);
}

void loop() {
}

実行結果を以下に示します。いつ実行したかがわかってしまいますね。

その他

家の中だと、窓際に設置しないと電波をうまく受信できせんでした。時刻を取得するのであれば、GPSのほうが確実かもしれません。

長波を受信する必要があるため、けっこう大きいアンテナが付属しています。電波時計に対応した腕時計もありますが、よく小さいところにいろいろ押し込めているなと感心してしまいます。

バージョン

Arduino 1.5.5/Arduino Due



メニューを表示するためにJavaScriptを有効にしてください。

Arduinoで遊ぶページ
Copyright © 2014 garretlab all rights reserved.
inserted by FC2 system