Wio LTEと7セグLEDで電波時計を作成する

#初めに
Wio LTE for Arduinoでは、LTEモジュールを用いた時刻補正・時刻取得の関数が提供されています。
それらの関数と7セグメントLEDを用いて電波時計を作りました。
#使用ハードウェア
* [Grove IoT スターターキット for SORACOM(Wio LTE JP Version)](https://soracom.jp/store/5228/ "Grove IoT")
* [Grove - 4-Digit Display](https://wiki.seeedstudio.com/Grove-4-Digit_Display/ "4 Digit Display")

#事前準備
* Wio LTEの開発環境のセットアップはこの[ページ](https://seeedjp.github.io/Wiki/Wio_LTE_for_Arduino/Setup-ja "セットアップ")を参考にしてください。
* 7セグメントLEDを使用するにはライブラリのインストールが必要です。ライブラリは[こちら](https://github.com/Seeed-Studio/Grove_4Digital_Display/archive/master.zip "ライブラリ")。ライブラリのインストール方法は[こちら](https://wiki.seeedstudio.com/How_to_install_Arduino_Library/ "インストール")。
* 7セグメントLEDはWio LTEのD38, D39ポートに接続してください。コネクタの写真は[こちら](https://seeedjp.github.io/Wiki/Wio_LTE_for_Arduino/Hardware-ja "コネクタ")。
* あらかじめSIMを挿入して、通信モジュールがLTEのネットワーク網に接続できるようにしておいてください。

# 完成イメージ
![写真.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125043/a7a1ccbc-272d-755a-eabe-c436d328d03f.png)
完成イメージです。写真の通り、左上のコネクタ（D38,D39）に接続された7セグLEDに現在時刻が表示されます。中ポチは0.5秒周期で点滅を繰り返します。
今はボードむき出しなので、何かの筐体に入れたらもう少し時計っぽくなると思います。。

#プログラム
```c++:wio_radioclock.ino
#include <WioLTEforArduino.h>
#include <TM1637.h>

// 7 seg LED
#define DIO (WIOLTE_D39)
#define CLK (WIOLTE_D38)
TM1637 tm1637(CLK, DIO);

// UTC -> JST
#define UTC2JST(t) (t + 9 * 60 *60)

WioLTE Wio;
HardwareTimer Timer1(TIM1);
time_t SecTimer  = 0;
int    MsecTimer = 0;

void timer1_cb(HardwareTimer *HT)
{
  MsecTimer++;
  if (MsecTimer >= 10) {
    SecTimer++;
    MsecTimer = 0;
  }
  DisplayTime(UTC2JST(SecTimer), MsecTimer);  // JST
}

void DisplayTime(time_t sec, int msec)
{
  int8_t d[] = {0x00, 0x00, 0x00, 0x00};

  boolean p = (msec < 5) ? POINT_ON : POINT_OFF;
  struct tm *t = localtime(&sec);
  d[0] = t->tm_hour / 10;
  d[1] = t->tm_hour % 10;
  d[2] = t->tm_min / 10;
  d[3] = t->tm_min % 10;

  tm1637.point(p);
  tm1637.display(d);
}

void setup()
{
  delay(1000);

  SerialUSB.println("");
  SerialUSB.println("--- START ---");
  SerialUSB.println("### I/O Initialize.");
  Wio.Init();

  // 7seg LED
  SerialUSB.println("### Set 7seg LED");
  tm1637.set(BRIGHT_TYPICAL); //BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7;
  tm1637.init();

  // LTE
  SerialUSB.println("### Power supply ON.");
  Wio.PowerSupplyLTE(true);
  delay(500);

  SerialUSB.println("### Turn on or reset.");
  if (!Wio.TurnOnOrReset()) {
    SerialUSB.println("### ERROR! ###");
    return;
  }
  delay(3000);

  SerialUSB.println("### Sync time.");
  if (!Wio.SyncTime("ntp.nict.jp")) {
    SerialUSB.println("### ERROR! ###");
    return;
  }

  // startup
  SerialUSB.println("### Get time.");
  struct tm now;
  time_t pre, cur;
  if (!Wio.GetTime(&now)) {
    SerialUSB.println("### ERROR! ###");
    return;
  }
  pre = cur = mktime(&now);
  while (pre == cur) {
    if (!Wio.GetTime(&now)) {
      SerialUSB.println("### ERROR! ###");
      return;
    }
    cur = mktime(&now);
  }
  SecTimer = cur;
  MsecTimer = 0;
  SerialUSB.print("UTC:");
  SerialUSB.println(asctime(&now));

  // timer
  Timer1.setOverflow(100 * 1000, MICROSEC_FORMAT); // = per 100 msec
  Timer1.attachInterrupt(&timer1_cb);
  Timer1.resume();

  SerialUSB.println("### Setup completed.");
}

void loop()
{
  struct tm *now = localtime(&SecTimer);

  if ((now->tm_hour == 0) && (now->tm_min == 0) && (now->tm_sec == 0)) {
    SerialUSB.println("### Sync time.");
    if (!Wio.SyncTime("ntp.nict.jp")) {
      SerialUSB.println("### ERROR! ###");
    }
  }

  if ((now->tm_min == 0) && (now->tm_sec == 30)) {
    tm tmp;
    time_t pre, cur;
    if (Wio.GetTime(&tmp)) {
      pre = cur = mktime(&tmp);
      while (pre == cur) {
        if (Wio.GetTime(&tmp)) {
          cur = mktime(&tmp);
        } else {
          SerialUSB.println("### ERROR! ###");
        }
      }
      SecTimer = cur;
      MsecTimer = 0;
      SerialUSB.println("### Get time.");
      SerialUSB.print("UTC:");
      SerialUSB.println(asctime(&tmp));
    } else {
      SerialUSB.println("### ERROR! ###");
    }
  }

  delay(1000);
}
```
#プログラムの説明
## 時刻の取得
### NTPサーバからの時刻の取得
以下の関数でNTPサーバ（ntp.nict.jp）と通信量モジュールが時刻同期します。

```C++
Wio.SyncTime("ntp.nict.jp")
```
関数内部では、通信量モジュールに対してAT+QNTP コマンドを発行しています。
サンプルプログラムのメインループでは1日1回、00時00分00秒(UTC)に関数実行しています。

### 通信モジュールの時刻の取得
以下の関数で通信モジュールが保持している時刻を取得します。

```C++
Wio.GetTime(&now)
```
関数内部では、通信モジュールに対してAT+CCLK コマンドを発行しています。
サンプルプログラムのメインループでは1時間ごと、00分30秒に関数実行しています。
## 7セグメントLEDの制御
### 接続ピンの指定 (D38を使用する場合)
```C++
#include <TM1637.h>

// 7 seg LED
#define DIO (WIOLTE_D39)
#define CLK (WIOLTE_D38)
TM1637 tm1637(CLK, DIO);
```
### 7セグLEDの初期化 (setup()内)
```C++
  // 7seg LED
  SerialUSB.println("### Set 7seg LED");
  tm1637.set(BRIGHT_TYPICAL); //BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7;
  tm1637.init();
```
### 中ポチのON/OFF
```C++
  tm1637.point(POINT_ON);
  tm1637.point(POINT_OFF);
```
### 数字の表示(1234を表示したい場合)
```C++
  int8_t d[4];
  d[0] = 1;
  d[1] = 2;
  d[2] = 3;
  d[3] = 4;
  tm1637.display(d);
```
## 割り込み
通信モジュールのマニュアルによると、AT+QNTP コマンドのレスポンスはワースト125秒（ネットワークに依存）、AT+CCLKコマンドのレスポンスはワースト300m秒かかります。
そのため、表示更新が止まらないように時刻のカウントとLED表示はタイマ割り込みのコールバック関数内で実行しています。
WioLTEのタイマ割り込みに関する情報は下記。
[Wio LTE JP Version(STM32F405RG)で HardwareTimer のタイマー割り込みを行う](https://qiita.com/ma2shita/items/f943af9112dbbde6c27a "タイマー")
## 時計の精度について
こだわりで？、通信量モジュールの時計とLED表示との誤差を小さくするために、通信量モジュールの時刻取得でポーリングして、値が変化したタイミングで時刻とカウンタをリセットしています。
ただ実用上は単に値を読み込んで設定するだけで充分かと思います。

また、どうも通信モジュールの時計自体に数秒ほど誤差があるようで、時報と完璧に同期させるのは無理そうです。。。

# その他
本記事ですが、ソラコムさんのSORACOM IoT DIYレシピに登録していただきました。
下記のリンクの『IoTで作るどこでも正確なデジタル時計』です。
[SORACOM IoT DIYレシピ](https://soracom.jp/iot-recipes/)

#参考リンク
[Wio LTE for Arduino リファレンスマニュアル](https://seeedjp.github.io/Wiki/Wio_LTE_for_Arduino/Reference-ja "リファレンスマニュアル")
[Wio-LTE で現在時刻を取得する](https://qiita.com/kwi/items/954471595df573b304d1 "時刻取得")