ArduinoでJJYを受信して日時を取得する

![Title](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/f0efb5a1-f726-19bd-cf79-81bae085aeb0.jpeg)


Arduino と電波時計モジュールを使って JJY の信号を受信し、日付と時刻を取得します。

# 用意するもの

|部品名|画像|はんだ付け|入手先 (参考)|
|---|:--:|---|---|
|**Arduino Nano**<br/>(Arduino UNO でも可)|<img width="240" alt="Arduino Nano" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/ff90ed3f-6be4-a534-6dbd-45beba4906d2.jpeg">|必要なし|[秋月電子](https://akizukidenshi.com/catalog/g/g109059/)<br/>[千石電商](https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-4UWW) など |
|**電波時計モジュール**<br/>(D606C)<br/>(画像は 7ピン)|<img width="240" alt="D606C" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/4dd2d4b2-0e27-43d3-d310-dca93179763d.jpeg">| **必要** | [aitendo](https://www.aitendo.com/product/9148) など |

今回は Arduino ボードに Arduino Nano を使用しています。もちろん Arduino Uno でも構いません。電波時計モジュールについても JJY のパルス信号がが 2値 (H/L) で取得できれば何を使っても構いません。今回は D606C の 7 ピンタイプを使います。

電波を受信する都合上、回路は窓際やノイズ源から離してください。電源も PC 直結ではなく単独の AC アダプタやバッテリー経由がベストです。

## 電波時計モジュール D606C

AMレシーバIC **CME6005** を搭載した電波時計モジュールです。搬送波周波数の 60kHz と 40kHz の両バンドに対応し、バーアンテナが付属します。7 ピンタイプと 6 ピンタイプが存在し、7 ピンタイプは [**AGC**](https://ja.wikipedia.org/wiki/%E8%87%AA%E5%8B%95%E5%88%A9%E5%BE%97%E5%88%B6%E5%BE%A1) のピンが出ていますが **Vcc と直接接続されているため AGC は常に有効です**。機能は両タイプで同一ですがピンの配置が異なるため注意が必要です。

|7ピンタイプ番号|6ピンタイプ番号|ピン名|機能|
|--:|--:|:---:|---|
| 1 | 2 |  V | 電源 Vcc 1.2V～5.5V |
| 2 | 3 | G | GND (0V) |
| 3 | 1 | F | 周波数選択。Vcc = 60kHz, GND = 40kHz |
| 4 | 4 | TN | ネガティブパルス信号出力 |
| 5 | 5 | TP | ポジティブパルス信号出力 |
| 6 | 6 | P | Power。GND = Enable, Vcc = Disable |
| 7 | N/A | H | AGC。Vcc にショートしており変更不可（常に有効） |

# 回路

|![schematic](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/c28787a0-328c-cfd0-1b30-30449f64e7d6.png)|![circuit_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/0b35dfec-aeaf-c961-3117-db85e3842497.jpeg)|![circuit2_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/c5d66e3a-b712-ffd0-485e-268d45785469.jpeg)|
|:--:|:--:|:--:|
|**回路図**|**実装例**|**配線例**|

最低限の動作を確認する場合は Arduino ボードと電波時計モジュールの結線のみで可能です。
今回は検証のため圧電スピーカーと LED を追加で使っています。可変抵抗器は圧電スピーカーの音量調節用です。

# 秒信号との同期

タイムコードを受信する前に JJY を使って可能な限り正確な秒のタイミングを同期させます。秒と同期ができればタイムコードが読み取りやすいためです。

JJY は 1 秒ごとにタイムコードの元になる **パルス信号** を送信しています。そしてそのパルス信号の立ち上がりが 1 Hz の **秒信号** となっています。理想的な状況でパルス信号の立ち上がりを観測できれば秒信号を取得できることになります。実際にはノイズの混入があり、以下の特性を留意しておきます。

- H レベルが L レベルになってしまうことはあるが、その逆は少ない
  - ただし近傍に強いノイズ源がある場合はこの限りではない
- 受信環境により立ち上がり間隔は一定ではないが、大きく変動することはない（±数% 程度）

これを踏まえると、仮に数回のパルス信号の立ち上がりを **連続して** 受信できれば秒信号との同期ができます。

## 実装

秒信号との同期を行う [**Synchronizer**](https://github.com/nanase/JJYReceiver/blob/644287de64d895ba77a316298efaf91e77d0ea09/include/synchronizer.h) を作りました。

`attachInterrupt()` で外部割り込みを使って連続したパルス信号の立ち上がりを捉えます。立ち上がりの間隔が許容できる精度（今回は ±5%）であり、なおかつ連続して 3 回観測できたら Timer1 を使って 1秒 のタイマ割り込みを作り、秒信号との同期成功とします。

Timer1 を使っているのは `tone()` を使うためです。ライブラリは [**TimerOne**](https://github.com/PaulStoffregen/TimerOne) を使っています。

連続観測回数は 2 回が最小値ですが、より正確に秒信号を捉えるために 3 回としました。うまく秒信号と同期すると圧電スピーカーが 1 秒間隔で鳴動し始めます。

```cpp:synchronizer.ino
#include "synchronizer.h"

#define PIN_SPEAKER 19
#define PIN_JJY_SIGNAL 2

void callback() {
  tone(PIN_SPEAKER, 800, 50);
}

void setup() {
  pinMode(PIN_SPEAKER, OUTPUT);
  digitalWrite(PIN_SPEAKER, LOW);
  Synchronizer.begin(PIN_JJY_SIGNAL, callback);
}

void loop() {}
```


# タイムコード受信

![timecode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/2d74420a-953d-78be-258f-4ee4fd1058c5.png)

秒信号と同期ができたので、それを起点としてタイムコードの元になる **パルス信号** を受信します。実際に JJY から受信される信号は AM 変調されていますが、電波時計モジュールがこれを復調して H (Vcc), L (0V) の 2 値信号に変換しています。タイムコードは以下の3種類があります。

- **マーカー または ポジションマーカー** : H レベル 0.2s, L レベル 0.8s
- **2進の `1`** : H レベル 0.5s, L レベル 0.5s 
- **2進の `0`** : H レベル 0.8s, L レベル 0.2s

前述の通りパルス信号は「H レベルが L レベルになってしまうことはあるが、その逆は少ない」という特性があります。H レベルの長さを特定するような方法ではノイズに弱いため今回は用いません。代わりに0.1秒ごとにパルス信号を読み取り、ある程度ノイズが混入した状況でも受信できるようにします。

単なる H or L の読み取りのため `digitalRead` を使います。1 秒間に 0.1 秒間隔で読み取ると 10 ビット（1,024 通り）となりますが、理想的には 0.4, 0.5 秒目の 2 ビット分だけ読めば十分です。

| 0.4秒目 | 0.5秒目 | タイムコード |
|--------|--------|---|
| L | L | マーカー |
| L | H | (判定不能) |
| H | L | 2進の 1 |
| H | H | 2進の 0 |

しかし H → L がピッタリ 0.5 秒目の直前に切り替わる保証はないので、今回は 0.3, 0.4, 0.5, 0.6 秒目の **4 ビット分**だけ読みます。この場合でも H or L の組み合わせは [**16 通り**](https://github.com/nanase/JJYReceiver/blob/644287de64d895ba77a316298efaf91e77d0ea09/src/jjy_receiver.cpp#L106-L111) しかありません。

![read_code.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/ac2d78a8-c509-f2c1-ae7f-b1e1101de098.png)

パルス信号の読み取り方法を上図に示しました。
秒信号を受け取って 0.35 秒経過したのち、0.1 秒間隔で 4 回 `digitalRead` を行って値を読み取ります。青丸部分が読み取りタイミングを表します。0.3 秒ではなく 0.35 秒であるのはレベルが切り替わる瞬間の読み取りを防ぎ、安定的に読み取るためです。

# デコード

仕様に従って日時をデコードしていきます。60 秒分のタイムコードがすべて受信できる必要はなく、日時を求めるならば 31 ビット分だけ読み取れれば十分です。

JJY の場合は毎分 0 秒で必ずマーカーが2連続で来るため、それが受信できるまでは待機します。2つのマーカーがやってきたら受信を開始して 1 ビットずつバッファを埋めていき、デコードを試みます。

状況によっては「分は読み取れなかったが、時は読み取れた」というように部分的に受信・デコードに成功（失敗）することがあります。今回はそのケースを前提としてデコード可能をチェックできるようにしました。

```cpp:jjy_decoder.cpp（抜粋）
// 注: タイムコードの2進の 1 を JJY_H, 2進の 0 を JJY_L と定義しています

#define codeHBit(index, value) (this->timecodes[index] == JJY_H ? (value) : 0)
#define codeInvalid(index) (this->timecodes[index] == JJY_H || this->timecodes[index] == JJY_L)

// 分データのデコード
uint8_t JJYDecoder::getMinutes() {
  return codeHBit(1, 40) + codeHBit(2, 20) + codeHBit(3, 10) +
         codeHBit(5, 8) + codeHBit(6, 4) + codeHBit(7, 2) + codeHBit(8, 1);
}

// 分データがすべて受信されていれば true を返す
bool JJYDecoder::isDecodableMinutes() {
  return codeInvalid(1) && codeInvalid(2) && codeInvalid(3) &&
         codeInvalid(5) && codeInvalid(6) && codeInvalid(7) && codeInvalid(8);
}
```

上記のようにデコード関数とは別にデコード可能かを判定する関数を用意しました。デコード前にこの判定関数が `true` を返すかをチェックします。1ビットでも有効でないデータ（2進の 1 or 0 以外）があれば `false` となります。

詳細なデコード方法は [**こちら**](http://jjy.jp/jjy/trans/index.html) をご参照ください。

# 日時の確定

タイムコードのデコードの時点で日時は求められますが、正しい日時が求められたかはわかりません。

そこで少なくとも 2 回分の日時情報をデコードし、すべて同じデータであることが判定できたものから部分的に確定していく方法をとります（[**実際のコード**](https://github.com/nanase/JJYReceiver/blob/644287de64d895ba77a316298efaf91e77d0ea09/src/jjy_decider.cpp#L37)）。仮にノイズが混入して正しくない日時となっても、それが2連続で発生しない限りは確定しない仕組みです。

候補となる日時情報は 1 分ごとに古い日時になっていくため、1 分ごとに +1 分を加算します。これにより最短で日時の確定ができるのは 2 連続マーカーを受け取ってから **2 分後** となります。

# 実際の動作

|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/2c12ec20-7d9c-950a-1e99-0d844bf2cb8e.jpeg" width="600">](https://youtu.be/nT1CRPgYg0E) |
|:---:|
| [**動作動画（クリックでYouTubeへ）**](https://youtu.be/nT1CRPgYg0E) |

初期状態から日時を確定させるまで最低でも2分はかかるので、動作のようすを動画にしました。以下の流れで進んでいきます。

1. 秒信号との同期
2. ポジションマーカーの検出
3. 1回目のデコード
4. 2回目のデコード
5. 日時の確定

撮影場所は屋内ですが、うまく受信できています。

ただし動画の終盤、JJY のコールサインをポジションマーカーと認識してしまい、秒がおかしくなっています。コールサインは毎時15分、45分の40～50秒と決まっているため、コールサイン受信中はポジションマーカーを検出しない工夫が必要そうです。

さらに1分あたりの秒が変化するのは閏秒の 8時59分（JST） しか起こり得ないため、閏秒を考慮しないならば日時を一度でも確定した場合にポジションマーカーの再検出をしないといった対応が必要です。

# 参考文献

- **標準電波の出し方**<br/>http://jjy.jp/jjy/trans/index.html
- **JJYの秒信号の送出方法についてNICTに聞いてみた: セッピーナの趣味の天文計算**<br/>http://seppina.cocolog-nifty.com/blog/2015/12/jjynict-e242.html
- **JJY - Wikipedia**<br/>https://ja.wikipedia.org/wiki/JJY
- **JJYシミュレータWeb版**<br/>https://shogo82148.github.io/web-jjy/