JF1DIR業務日誌(はてなblog版)

アマチュア無線局JF1DIRのアクティビティをつづっています。

WiFi NTP時計の製作

また更新をサボってしまいました。無線に対するモチベーションが低下し特に書くことがない、というのが正直なところ。しかし、電子工作はほそぼそと続けています。

以前、安価なGPSモジュールでGPS信号を受信し、その中の正確な時刻データで校正する置き時計をARMマイコンで作ってみました。明かりのない部屋でも確認できるので寝室用の置き時計として使っていましたが、部屋の模様替えに伴い、この時計の置き場所を少し窓から遠いところへ変えたら、GPS信号を安定に受信しなくなってしまいました。GPSモノは鉄筋のマンション住まいにはやっぱり苦しいところです。GPSアンテナを外に出してケーブルで引っ張ってくるという手もありますが、いろいろ面倒なのでやるつもありはありません(以前、ThunderBolt DOを制作したときGPSアンテナを外に設置しましたが、すぐに引っ込めました)。そこで、WiFiを使ったNTP時計を作ってみることにしました。作例はたくさんあるので、あちこちのブログを参考にしながら作ってみました。

Wifiバイスには、秋月で売っているESP-WROOM-02Dip化モジュールを使ってみました。開発時はリセットスイッチや電源周りが付いている、開発キットを使っていましたが、実際の時計には上記キットを使いました。このモジュールの中にすべてのプログラムを入れてしまってもよいのですが、7セグLEDのドライバを使いたかったりしてピン数が足りないので、WiFiモジュールでNTPで校正した時刻を生成し、シリアルで別のARMマイコンと通信して、そのARMマイコンでLEDを表示するという方式にしました。

まずはESP-WROOM-02のプログラミング。ArduinoIDEでプログラミングして書き込みました。ソースはこちら。ESP8226WiFiというライブラリを入手してIDEにインストールしてください。ソースのとおり、3時間おきにNICTのNTPサーバーに接続し時刻を校正し、成功したら先頭に"-"の文字を、それに続いて時刻をUSART経由で送信します。その後、1秒毎時刻を同様に送信し続けます。

#include <ESP8266WiFi.h>
#include <time.h>

#define JST     3600*9

const char* ssid = "◯◯◯◯";
const char* password = "◯◯◯◯";

void set_ntp() {
  Serial.begin(115200);
  delay(100);
  Serial.print("\n\nStart\n");

  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");
}

void setup() {
  set_ntp();
}

void loop() {
  time_t t;
  struct tm *tm;
  int time_count = 0;
  
  while(1) {
    t = time(NULL);
    tm = localtime(&t);
    Serial.printf("-%02d:%02d:%02d\n", tm->tm_hour, tm->tm_min, tm->tm_sec);
    time_count++;
    if (time_count >= 3600 * 3) {
      set_ntp();
      time_count = 0;
    }
    delay(1000);
  }
}

次にARM側のプログラミングです。石は手元に大量にあるNXPのLPC1114FN28/102を採用しました。外部クリスタル不要で、DIP品でいろいろと使いやすいです。開発環境はmbedを使いました。書き込みはとても便利なLPCISPを使いました。表示は6桁の7セグLEDですが、ベッドの上で(つまりメガネを外した状態)はっきりと見えるくらいの大きさ(文字高20mmほど)、色はグリーンにしてみました。なかなか見つからず、RSコンポーネンツでアノードコモンのものをようやく見つけました。LEDドライバはMAX7219を採用。カソードコモン用なので、トランジスタアレイでロジックを反転させました。カソード側にシンクタイプのドライバ、アノード側にはインバータロジックを搭載して、電流アンプも兼ねることにしました。WiFiとARMマイコンには3.3V、LEDドライバと反転ドライバには5Vを使用しています。
WiFiモジュールとの通信は、UART受信割り込みで時刻をARM側にコピーするという感じになります。MAX7219を動かすには、各桁にどんなどこエレメントを光らせるか1バイトで表現してそれを書き込むだけとシンプル。
基板の様子がこちら。


LPC1114FNのソースはこちら。

// Wifi NTP Clock for LPC1114 and ESP-ROOM-02
// by JF1DIR 2018/11
//
#include "mbed.h"
#include "max7219.h" // 本家MAXIMからライブラリを入手 https://os.mbed.com/teams/Maxim-Integrated/code/MAX7219/

Max7219 max7219(dp2, dp1, dp6, dp4); // SPI0_MOSI, SPI0_MISO, SPI0_SCK, SPI0_SS
static const uint8_t num[10] = {0x7e, 0x30, 0x6d, 0x79, 0x33, 0x5b, 0x5f, 0x72, 0x7f, 0x7b};
Serial rx_time(dp16, dp15); // tx, rx
uint8_t h, m, s;

// ESPからシリアルで文字列受信し時刻(時分秒)データを格納
// 文字列の先頭に'-'があるデータだけを有効とする
void update_time()
{
    char data[256];
    uint8_t data_index = 0;
    while(1) {
        if (!rx_time.readable()) continue;
        char c = rx_time.getc();
        data[data_index++] = c;
        if(data_index > 255) break;
        if(c == '\n') {
            data[data_index] = '\0';
            data_index = 0;
            if(data[0] != '-') return;
            char h_str[3], m_str[3], s_str[3];
            h_str[0] = data[1]; h_str[1] = data[2]; h_str[2] = '\0';
            m_str[0] = data[4]; m_str[1] = data[5]; m_str[2] = '\0';
            s_str[0] = data[7]; s_str[1] = data[8]; s_str[2] = '\0';
            h = atoi(h_str); m = atoi(m_str); s = atoi(s_str);
            break;
        }
    }
    return;
}
 
int main() {
    // setting for LED driver
    max7219_configuration_t cfg = {
        .device_number = 1,
        .decode_mode = 0,
        .intensity = Max7219::MAX7219_INTENSITY_8,
        .scan_limit = Max7219::MAX7219_SCAN_8
    };
    max7219.init_device(cfg);
    max7219.enable_device(1);
    max7219.set_display_test();
    wait(0.1);
    max7219.clear_display_test();    
    
    // setting for serial port    
    rx_time.baud(115200);
    NVIC_SetPriority(UART_IRQn, 1); // UART割り込み優先の設定
    rx_time.attach(update_time, Serial::RxIrq);
    
    while(1) {
        // send to driver
        max7219.write_digit(1, 6, num[h/10]);
        max7219.write_digit(1, 5, num[h%10]);
        max7219.write_digit(1, 4, num[m/10]);
        max7219.write_digit(1, 3, num[m%10]);
        max7219.write_digit(1, 2, num[s/10]);
        max7219.write_digit(1, 1, num[s%10]);
        wait(0.1);
    }
}

7セグLEDは別基板にして2階建て構造にしました。後ほどスチロール製の透明ケースに入れます。

暗闇で見るとこんな感じ。もう少し鮮やかなミドリがよかったのですが・・・