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

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

mbedによるGPS同期時計の製作

久々のブログ更新・・・しかも、電子工作の記事が少ないぞ、と言われ続けようやくの工作ネタで更新になりました(汗)。
お空のcondxもイマイチだし無線運用も飽きてきたなと思い、チョット気分を変えるために、購入したけど手を付けられず放置されていた電子工作キット(いわゆる「積みキット」)を消化しておりました。その中にあった秋月電子から出ている時計のキット2種を作ってみました。なんだかんだ時計が一番実用的ですからね(汗)。

下がNTP同期時計で上がGPS同期時計です。この通り、1秒以下できっちり同期しており時刻のズレなど心配せず使えるのですが、このLCD表示では字が小さくて見えないし(寝室に置きたいのでメガネがない状態でも読めないと困る)、バックライトが妙に明るいなど不満が多いものでした。一応シリアルの他桁7セグLEDに接続できる仕様となっているのですが、LEDが選べないのが不満(汗)。どちらも1万円近いお値段で、これならマイコン使って数千円程度で自分で作ってみようと考えました。太陽誘電製GPSモジュールGYSFDMAXBが安価で部屋の中でも衛星が常に受信できるという高性能なので使いこなしたいとも思いました。

表示機はLCDよりは7セグLEDがFBです。しかも最近の眩しい高輝度タイプではなくぼんやりと光る古いタイプを使ってみたいと部品箱を漁ると、古い東芝のTLR324が多数出てきました。2桁の赤LEDでカソードコモンタイプのものです。これを3つ並べて6桁にすればOK。TLR324のピン配置はこちら。古い部品なのでデーターシートを見つけるのに苦労しました。ちなみにコレの緑も在庫があるので2台目は緑にしたいです。

6桁のLEDをダイナミック点灯するので、信号線は8+6で14本。他にGPSモジュールから出ている1PPSの信号とLED点滅を同期させるために2本、合計16本のGPIOと1つのUSART(シリアル)が必要となります。設計当初はRaspberry Pi3でPythonを使って設計したのですが、たかが時計のためにRPiを組み込んでしまうのはもったいないので、普通のマイコンにすることにしました。mbedで安価のLPC1114を使ってみたいところだったのですが、ピン数がギリギリ足りないのでNG。手持ちのSTmicroから出ているNucleoが安価なのでこれを使うことにしました。ここ最近Pythonばかりだったので、C++はかなり抵抗があります・・・だけどだんだん慣れてきました(笑)。

ピン数が多くて組みやすいNucleo F746ZGを使ってで試作。ARM32-bitのCortex-M7が搭載されておりEthernetもなんでもあり最大クロック216MHzというバケモノクラスのボードですね(汗)。GPSのNMEAパーサーとしてTinyGPSというライブラリを利用しました。
全体のプログラムはごく簡単で、mainループ内でtime(NULL)関数で現在時刻を得ながら、GPSからの受信を割り込みで時刻を取得し、一定時間間隔でset_time関数を使って現在時刻をGPSの時刻に設定(補正)していくだけのものです。GPSモジュールの1PPS出力を立ち下がり・立ち上がり割り込みでLEDを点滅させたりなどもしています。一つ注意点としては、LEDのダイナミック点灯の素早いループがmainで走っているのですが、waitを掛けないとLEDが完全に消灯しないうちに点灯するので、全桁「8」の字に見えてしまいます。これもTLR324のような古いLEDを使っているせいでしょうか、2〜5msくらいのwaitで塩梅よくLEDが点いてくれました。それと、ピンあたりの電流量が不足しがちなので、GPIOに直接LEDを繋げるのではなく、アノード側にソースドライバTD62783AP、カソード側にシンクドライバTD62083AFNPを使いました。これでアノード側もカソード側も正論理でスイッチすることができるので、ソースコードのロジックが気持ち悪くなくなります(苦笑)。

ここまでは順調に行ったのですが、次に安価なNucleo F401REに載せ替えようとしたところ、急に動かなくなりました。mainループでハングアップ。どうやらシリアル割り込みするとハングアップするようです。F401REには2つのUSARTポートがあり、USART2(tx:D1, rx:D0)であったところを(ちなみにD0, D1ピンは裏面のハンダブリッジをしないと使えないので注意。これもしばらくハマった)。USART1(tx:D10, rx:D2)にすると、mainが動き出しました。しかしLEDの表示がヘンです。そこで割り込みの優先度を上げるためのおまじないとして、"NVIC_SetPriority(USART1_IRQn, 0);"を記述したり、ダイナミック点灯のwaitを微調整しました。それでも安定しません。最後に、GPSからの受信割り込みのコードを一番最初に書くと、完璧に治りました。この正解にたどり着くまで2日費やしまった(大汗)。mbedでもボードを変えるといろいろ不具合が出るものなんですね。Nucleo F401REでの試作の様子がコレ。

後はユニバ基板に取り付けケーシングし、視認性がよい安価なGPS同期時計が完成です。
以下にソースコードを示しますが、上記のようにmbedプラットフォームによってそのままでは動かないことがあるので注意して下さい。

/*    GPS同期時計 mbed版 by JF1DIR 2017
 *    Nucleo F401RE
 */

#include "mbed.h"
#include "TinyGPS.h"

DigitalOut led[8]={D1, D3, D4, D5, D6, D7, D8, D9}; // nucleo F401RE
DigitalOut digit[6]={D14, D15, D12, D13, D0, D11};
DigitalOut pps(PC_0);
InterruptIn pps_in(PC_1);
Ticker ticker_set_time;

int number[11][8]={
                    {1,1,1,1,1,1,0,0},          //zero
                    {0,1,1,0,0,0,0,0},          //one
                    {1,1,0,1,1,0,1,0},          //two
                    {1,1,1,1,0,0,1,0},          //three
                    {0,1,1,0,0,1,1,0},          //four
                    {1,0,1,1,0,1,1,0},          //five
                    {0,0,1,1,1,1,1,0},          //six
                    {1,1,1,0,0,0,0,0},          //seven
                    {1,1,1,1,1,1,1,0},          //eight
                    {1,1,1,1,0,1,1,0},          //nine
                    {0,0,0,0,0,0,0,1}          //dot
                  };

Serial serial_gps(D10, D2);  // UART1: tx, rx なぜかF401REのUART2では割り込み不能
TinyGPS gpsr;

int year;
byte month, day, hour, minute, second;

void led_off()
{
    pps = 0;
}

void led_on()
{
    pps = 1;
}

void setup()
{
    pps = 0;
    serial_gps.baud(9600);
    NVIC_SetPriority(USART1_IRQn, 0);
}

void getgps()
{
    if (serial_gps.readable()) {
        char c = serial_gps.getc();
        bool gps_avairable = gpsr.encode(c);
        if (gps_avairable) {
            (void)gpsr.crack_datetime(&year, &month, &day, &hour, &minute, &second);
        }
    }
}

void gps_to_nowtime()
{
    struct tm t;
    t.tm_sec = (int)second;
    t.tm_min = (int)minute;
    t.tm_hour = (int)((hour + 9) % 24);
    t.tm_mday = 1;
    t.tm_mon = 1;
    t.tm_year = 100;
    
    time_t sec2 = mktime(&t);
    set_time(sec2);
}

int main() {
    int h, m, s, v, j, k = 0;
    int x[6] = {0, 0, 0, 0, 0, 0};
    
    time_t seconds;
    struct tm *ts;

    setup();
    serial_gps.attach(getgps, Serial::RxIrq);
    ticker_set_time.attach(&gps_to_nowtime, 10.0);
    pps_in.fall(&led_on);
    pps_in.rise(&led_off);

    while (1) {
        seconds = time(NULL);
        ts = localtime(&seconds);
        h = ts->tm_hour;
        m = ts->tm_min;
        s = ts->tm_sec;
        
        x[0] = (int)(h / 10);
        x[1] = (int)(h - x[0] * 10);
        x[2] = (int)(m / 10);
        x[3] = (int)(m - x[2] * 10);
        x[4] = (int)(s / 10);
        x[5] = (int)(s - x[4] * 10);

        for (v = 0; v < 8; v++) { led[v] = 0; }
        for (v = 0; v < 6; v++) { digit[v] = 0; }
        
        digit[k] = 1;
        j = x[k];

        if ((k == 1) or (k == 3)) {
          for (v = 0; v < 7; v++) { led[v] = number[j][v]; }
          led[7] = 1;
        } else {
          for (v = 0; v < 8; v++) { led[v] = number[j][v]; }
        }
 
        wait(0.005);
        k++;
        if (k > 5) { k = 0; }
    }
}