2
Help us understand the problem. What are the problem?

posted at

updated at

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

初めに

Wio LTE for Arduinoでは、LTEモジュールを用いた時刻補正・時刻取得の関数が提供されています。
それらの関数と7セグメントLEDを用いて電波時計を作りました。

使用ハードウェア

事前準備

  • Wio LTEの開発環境のセットアップはこのページを参考にしてください。
  • 7セグメントLEDを使用するにはライブラリのインストールが必要です。ライブラリはこちら。ライブラリのインストール方法はこちら
  • 7セグメントLEDはWio LTEのD38, D39ポートに接続してください。コネクタの写真はこちら
  • あらかじめSIMを挿入して、通信モジュールがLTEのネットワーク網に接続できるようにしておいてください。

完成イメージ

写真.png
完成イメージです。写真の通り、左上のコネクタ(D38,D39)に接続された7セグLEDに現在時刻が表示されます。中ポチは0.5秒周期で点滅を繰り返します。
今はボードむき出しなので、何かの筐体に入れたらもう少し時計っぽくなると思います。。

プログラム

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)と通信量モジュールが時刻同期します。

Wio.SyncTime("ntp.nict.jp")

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

通信モジュールの時刻の取得

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

Wio.GetTime(&now)

関数内部では、通信モジュールに対してAT+CCLK コマンドを発行しています。
サンプルプログラムのメインループでは1時間ごと、00分30秒に関数実行しています。

7セグメントLEDの制御

接続ピンの指定 (D38を使用する場合)

#include <TM1637.h>

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

7セグLEDの初期化 (setup()内)

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

中ポチのON/OFF

  tm1637.point(POINT_ON);
  tm1637.point(POINT_OFF);

数字の表示(1234を表示したい場合)

  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 のタイマー割り込みを行う

時計の精度について

こだわりで?、通信量モジュールの時計とLED表示との誤差を小さくするために、通信量モジュールの時刻取得でポーリングして、値が変化したタイミングで時刻とカウンタをリセットしています。
ただ実用上は単に値を読み込んで設定するだけで充分かと思います。

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

その他

本記事ですが、ソラコムさんのSORACOM IoT DIYレシピに登録していただきました。
下記のリンクの『IoTで作るどこでも正確なデジタル時計』です。
SORACOM IoT DIYレシピ

参考リンク

Wio LTE for Arduino リファレンスマニュアル
Wio-LTE で現在時刻を取得する

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
2
Help us understand the problem. What are the problem?