Spresenseで電波時計を作る

Qiita Advent calendar2021の記事です。

:::note
Spresenseの関係会社に勤務していますが、業務外の個人活動の紹介です。
当記事は会社と関係ありません。
:::

:::note alert
誤った基板上のピン接続により故障する可能性があります。
自己責任でお願いします。
:::

aitendo様のモジュールを使って電波時計を作ってみました。

https://www.aitendo.com/product/9148

####実験環境を作る
おおたかど山やはがね山からの電波を受信する必要がありますが、都市部のビルが多い地域やコンクリートの建物の中の部屋のように電波が届きにくい場所では実験ができません。
回路とプログラムが動くことを確認できるまで、JJY信号を疑似的に生成するスマホのアプリ使い、電波は確実に受信できるようにしました。アプリを起動してスピーカーの音量を大きくしてアンテナに近づけました。
また回路とプログラムが動くことを確認できた後、実際の電波で実験するために似たようなモジュールが入っている電波時計を使って受信できる場所を探しました。

実際の電波で実験する場所を探した卓上電波時計を開けてみた様子
<img width="300" alt="20211021_121704.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103446/6e12186d-37a5-69d9-0d74-eb09081d88db.jpeg">

####電波時計の1ppsモジュールを作る

いったんSpresenseや7segディスプレイをアンテナと一つの箱に入れたのですが、その途端に受信しなくなったので、アンテナをそれらの回路から分離できるようにしました。

<img width="300" alt="20211218_145737.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103446/28ec7104-c955-443d-e41d-e2b29b9788d6.jpeg">

|電波時計モジュール|備考
|---|---|
|V|電池CR2032+|
|G|電池CR2032 - SpresenseのGND|
|F|5kΩ - G 東日本仕様|
|TN|接続せず|
|TP|SpresenseのA0|
|P|5kΩ - G|
|H|5kΩ - V|

GとTPをステレオのオーディオケーブルを使ってSpresense側に接続しました。

####スケッチ

こちらのArduinoの例を参考に作成しましたが、Spresenseと電波時計モジュールの相性の問題でそのままでは動かなかったです。

http://miha.jugem.cc/?eid=74

この電波時計モジュールの出力をSpresenseのデジタル端子につなぐとGNDレベルが高い状態になってしまい判定できなくなったようです。PULLDOWNにしたりいろいろ試したのですがうまくいかなかったです。

ですので、アナログ入力で閾値越えを判定した結果をD3で出力し、問題あるかもしれないですが、D3をD5に直結して状態変化割り込みの信号にしました。

|Spresense|接続先
|---|---|
|GND|7seg GND|
|3.3V|7seg VCC|
|A0|電波時計モジュールTP|
|D3|SpresenseD5|
|D5|SpresenseD3|
|D10|7seg DIN|
|D12|7seg CS|
|D13|7seg CLK|

あとはタイマー割り込みを使って、受信できてないときの対応や受信した時刻情報を反映させる判定処理も入れました。

<details><summary> RadioWatch22c.ino </summary><div>

``` 

// http://miha.jugem.cc/?eid=74
#define ONESEC 1000000  // [us]

#include "LedControl.h"
// single MAX72XX pin 14/12 DataIn,  pin 15/13 CLK,  pin 11/10 LOAD 
LedControl lc2=LedControl(12,13,10,1);

static int pinTp = A0;
static int pinTPout = 3;
static int pinTPin = 5;

long  nOldMillis=0;
int   sig1;
int   sig0 = 3;
long  nIdx = 0;
int   signals[60+1];
int   valThr = 100;

int hour1, min1, sec1;
int hour2, min2, sec2;
long t0, dt0;
long t1;
int state1 = 0;
bool tnstate = false;

bool val1;

unsigned int ticktock1() 
{ 
  sec1++;
    
  if(sec1 > 59)
  {
    sec1 = 0;
    min1++;
    if(min1 > 59)
    {
      min1 = 0;
      hour1++;
      if(hour1 > 23)
      {
        hour1 = 0;
      }
    }
  }

  lc2.setChar(0,7,hour1/10,false);
  lc2.setChar(0,6,hour1%10,true);
  lc2.setChar(0,5,min1/10,false);
  lc2.setChar(0,4,min1%10,true);  
  lc2.setChar(0,3,sec1/10,false);        
  lc2.setChar(0,2,sec1%10,true);

  return ONESEC;
}

void calcTime1(int *signals)
{ 
  int calc1 = 0;

  if (signals[0] == 2) 
  {
    calc1++;
  }
  for (int i = 0; i < 6; i++)
  {
    if(signals[10 * i + 9] == 2) 
    {
      calc1++;
    }
    else
    {
      break;  
    }
  }
  
  if(calc1 < 6)
  {
    state1 = 0;  
    Serial.println("CHECK NG");
    return;
  }
  else
  {
    int pa1, pa2;

    pa1 = (signals[12] + signals[13] + signals[15] + signals[16] + signals[17] + signals[18]) % 2;
    pa2 = (signals[1] + signals[2] + signals[3] + signals[5] + signals[6] + signals[7] + signals[8]) % 2;

    if((signals[36] == pa1) && (signals[37] == pa2))
    {
      state1 = 1;
      Serial.println("CHECK OK");

      // calc hour and min
      hour2 = 10 * (2 * signals[12] + signals[13]);
      hour2 += (8 * signals[15] + 4 * signals[16] + 2 * signals[17] + signals[18]);
        
      min2 = 10 * (4 * signals[1] + 2 * signals[2] + signals[3]);
      min2 += (8 * signals[5] + 4 * signals[6] + 2 * signals[7] + signals[8] + 1); 
      
      if(min2 > 59)
      {
        min2 = 0;
        hour2++;
        if(hour2 > 23)
        {
          hour2 = 0;
        }
      }
      
      // display the calculated hour and min
      char buf[5];
      sprintf(buf, "%02d:%02d", hour2, min2);
      Serial.println(buf);

      // It is hour2 : min2 . 01 now.

      sec2 = 01;

      if(hour2 == hour1 && min2 == min1 && sec2 == sec1)
      {
        state1 = 2;
      }
      else
      {
        hour1 = hour2;
        min1 = min2;
        sec1 = sec2; 
      }

    }
    else
    {
      state1 = 0;  
      Serial.print("CHECK parity NG: ");
      Serial.print("pa1: ");
      Serial.print(pa1);
      Serial.print(", pa2: ");
      Serial.print(pa2);    
      Serial.println("");   
      return;
    }

  }
}

void changeTp(void)
{
  if(digitalRead(pinTPin))
  {
     // low -> high
    t0 = millis();
    tnstate = true;
    digitalWrite(LED_BUILTIN, HIGH);

    if (state1 == 1)
    { 
      sec1 = nIdx + 1; // - 1;
      detachTimerInterrupt();
      attachTimerInterrupt(ticktock1, ONESEC);
      lc2.setIntensity(0,8);
      state1 = 2;

      t1 = t0;
    }   
  }
  else
  {
    // high -> low
    dt0 = millis() - t0;
    digitalWrite(LED_BUILTIN, LOW);
  }
}

void setup() {
  Serial.begin(115200);

  pinMode(LED_BUILTIN, OUTPUT);

  Serial.println("Start ");

  lc2.shutdown(0,false);
  lc2.setIntensity(0,2);      /* Set the brightness */
  lc2.clearDisplay(0);        /* and clear the display */

  t0 = millis();
  t1 = t0;

  sec1 = 0;
  lc2.setIntensity(0,0);
  attachTimerInterrupt(ticktock1, ONESEC);
  lc2.setChar(0,1,'-',false); 
  lc2.setChar(0,0,'-',false);
  
  state1 = 0;

  pinMode(pinTPout, OUTPUT);  // #3
  pinMode(pinTPin, INPUT_PULLDOWN);    // #5
  attachInterrupt(digitalPinToInterrupt(5), changeTp, CHANGE);
}

void loop() 
{
  if (analogRead(pinTp) > valThr)
  {
    digitalWrite(pinTPout, HIGH);
  }
  else
  {
    digitalWrite(pinTPout, LOW);
  }
  
  if(tnstate)
  {
    tnstate = false;
    
    if((nIdx+1)/10 < 6)
      lc2.setChar(0,1,(nIdx+1)/10,false);
    else
      lc2.setChar(0,1,0,false);        
    lc2.setChar(0,0,(nIdx+1)%10,false);

    sig1 = 3; // default
    // M,P : 200ms  , HIGH 1 : 500ms  , LOW  0 : 800ms 
    if((dt0 > 50) || (dt0 > 900))
    { 
      sig1 = 0;
      if(dt0 < 650)
      {
        sig1 = 1;
        if(dt0 < 350)
        {
          sig1 = 2;          
        }
      }
    }

    signals[nIdx] = sig1;
    
    t0 = millis();
    
    char buf[128] = "";
    sprintf(buf, "nIdx: %d, sig0: %d, sig1: %d, dt0: %04d, state1 %d", nIdx, sig0, sig1, dt0, state1);
    Serial.println(buf);

    if(sig0 == 2 && sig1 == 2)
    {
      if(nIdx == 60)
      {
        // finish to get 60 bits       
        Serial.print("signals : ");
        for(int i = 0; i < 60; i++)
        {
          Serial.print(signals[i]);
          if(((i+1) % 10) == 0)
            Serial.print(",");
        }
        Serial.println("");
        calcTime1(signals);

        signals[0] = 2;
        sig0 = -1; 
        nIdx = 1;
      }
      else
      {
        signals[0] = 2;
        sig0 = -1;
        nIdx = 1;
      }
    }
    else
    {
      if(nIdx < 60)
      {
        sig0 = sig1; 
        nIdx++;
      }
      else
      {
        sig0 = -1;
        nIdx = 0;
      }
    }
 }
 else
 {
    if (state1 == 1)
    {
      lc2.setChar(0,1,'a',false);
      lc2.setChar(0,0,'c',false);
    }
    else
    {
      if ((state1 == 2) && ((millis() - t1) > 1000))
      {
        lc2.setChar(0,1,'-',false);
        lc2.setChar(0,0,'-',false);
      }
    }
 }

}

``` 

</div></details>


####動作の様子

https://youtu.be/NU4zv-QtDDI

電波時計で時刻情報を取得する仕組みを勉強できてよかったです。
時刻と日時、付加情報を毎秒ごと送られる60個の信号のパルス幅で表すので、受信するのに信頼性を考慮すると最低2-3分はかかることの理由を理解できました。
また、知りうる情報が過去であることを理解できました。