ESP32 を使って LC共振回路の理解を深め、電波時計をガッツリ合わせてみる実験

記事公開日:2019年1月16日

スポンサーリンク

電波時計を合わせるためのプログラム(スケッチ)入力

やっとここまで来ました。

では、Arduino – ESP32 を使って、JJY信号を発信させて、電波時計を合わせるプログラム(スケッチ)を入力していきます。

2年前のこちらの記事を変更して、以下のようにしました。

#include <WiFi.h>
#include <WiFiUdp.h>
#include <TimeLib.h> //Arduino time library ver1.5-

const char* ssid = "xxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください
const char* password = "xxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください

const uint8_t gpio_high = 17;
const uint8_t gpio_low = 16;

uint8_t min_now, hour_now, year_now, weekday_now;
uint16_t day_total;
uint8_t parity1, parity2; //パリティ定義
//-----------NTP server Get 関連初期化 --------------------------
unsigned int localPort = 2390;
IPAddress timeServer;
const char* ntpServerName = "time.windows.com";
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
byte timeZone = 9; //Tokyo
WiFiUDP Udp;
uint32_t get_ntp_last_time = 0;

//*****************セットアップ******************************
void setup() {
  Serial.begin(115200);
  delay(10);

  Serial.println();
  Serial.print(F("Connecting to "));
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println();
  Serial.println(F("WiFi connected"));
  delay(1000);

  Serial.println(WiFi.localIP());
  delay(10);

  pinMode(gpio_high, OUTPUT);
  pinMode(gpio_low, OUTPUT);

  WiFi.hostByName(ntpServerName, timeServer); //サーバーネームをIPアドレスに変換

  Udp.begin(localPort);

  setTime( getNtpTime() ); //NTPサーバーから時刻を取得し、ESP32内蔵時計にセットする
  delay(2000); //安定するまで待つ

  setTime( now() + 1 ); //標準時とキッチリ合わせるため、1秒早める。

  get_ntp_last_time = millis();

  TaskHandle_t th; //マルチタスクハンドル定義
  //マルチタスク実行
  xTaskCreatePinnedToCore(Task1, "Task1", 4096, NULL, 5, &th, 0);
}
//************* メインループ ****************************************
void loop() {
  if((millis() - get_ntp_last_time) > 1800000){ //30分毎にNTPサーバーから時刻取得
    setTime( getNtpTime() );
    getNowTime(); //現在時刻表示
    get_ntp_last_time = millis();
  }
}
//************* マルチタスク ****************************************
void Task1(void *pvParameters){
  uint32_t last_time = 0;

  if(second() == 0){
    delay(1500); //ゼロ秒の場合は次のゼロ秒までやり過ごすために、1.5秒待つ。
  }
  while(second() != 0){ //ゼロ秒になるまで待つ
    delay(1); //ウォッチドッグタイマを動作させるために必要
  }
  Serial.println();

  for(;;){
    last_time = millis();
    getNowTime();                   //現在時刻初期化
    sendMerker();                     //M マーカー送信
    sendMinuteTimeCode();             //タイムコード(分)送信
    sendMerker();                     //P1 ポジションマーカー送信
    sendHourTimeCode();            //タイムコード(時)送信
    sendMerker();                     //P2 ポジションマーカー送信
    sendDayTotalParityTimeCode(); //タイムコード(通算日)送信
    sendMerker();                     //P4 ポジションマーカー送信
    sendYearTimeCode();            //タイムコード(年)送信
    sendMerker();                     //P5 ポジションマーカー送信
    sendWeekdayUruuTimeCode();    //タイムコード(曜日、うるう秒)送信
    sendMerker();                     //P0 ポジションマーカー送信

    Serial.printf("\r\n[Total = %ld ms]\r\n", millis() - last_time);

    while(second() != 0){ //もし、時間がズレた場合、ゼロ秒になるまで時間調整する
      Serial.print('.');
      delay(1); //ウォッチドッグタイマを動作させるために必要
    }
  }
}
//******** マーカーおよびポジションマーカー送信 ************************
void sendMerker(){
  Serial.print(".M.");
  oscillator(gpio_high, 200);
  oscillator(gpio_low, 800);
  delay(1); //ウォッチドッグタイマを動作させるために必要
}
//******** タイムコード(分)送信 **************
void sendMinuteTimeCode(){
  uint8_t min10 = (uint8_t)floor((double)min_now / 10.0);
  uint8_t min01 = min_now % 10;
  uint8_t m[7] = {0}; //分のパリティ parity2 計算用配列初期化

  //---------10の位-------------
  m[6] = bitRead( min10, 2 );
  Serial.print( m[6] );
  oscillatorTimeCodeBit( m[6] );
  m[5] = bitRead( min10, 1 );
  Serial.print( m[5] );
  oscillatorTimeCodeBit( m[5] );
  m[4] = bitRead( min10, 0 );
  Serial.print( m[4] );
  oscillatorTimeCodeBit( m[4] );
  Serial.print('0');
  oscillatorTimeCodeBit(0);
  //---------1の位--------------
  m[3] = bitRead( min01, 3 );
  Serial.print( m[3] );
  oscillatorTimeCodeBit( m[3] );
  m[2] = bitRead( min01, 2 );
  Serial.print( m[2] );
  oscillatorTimeCodeBit( m[2] );
  m[1] = bitRead( min01, 1 );
  Serial.print( m[1] );
  oscillatorTimeCodeBit( m[1] );
  m[0] = bitRead( min01, 0 );
  Serial.print( m[0] );
  oscillatorTimeCodeBit( m[0] );

  //-------分パリティ計算-------
  parity2 = (m[6] + m[5] + m[4] + m[3] + m[2] + m[1] + m[0]) % 2;
}
//******** タイムコード(時)送信 *************
void sendHourTimeCode(){
  uint8_t hour10 = (uint8_t)floor((double)hour_now / 10.0);
  uint8_t hour01 = hour_now % 10;
  uint8_t h[6] = {0}; //時のパリティ parity1 計算用配列初期化

  Serial.print('0');
  oscillatorTimeCodeBit(0);
  Serial.print('0');
  oscillatorTimeCodeBit(0);

  //---------10の位-------------
  h[5] = bitRead( hour10, 1 );
  Serial.print( h[5] );
  oscillatorTimeCodeBit( h[5] );
  h[4] = bitRead( hour10, 0 );
  Serial.print( h[4] );
  oscillatorTimeCodeBit( h[4] );
  Serial.print('0');
  oscillatorTimeCodeBit(0);
  //---------1の位-------------
  h[3] = bitRead( hour01, 3 );
  Serial.print( h[3] );
  oscillatorTimeCodeBit( h[3] );
  h[2] = bitRead( hour01, 2 );
  Serial.print( h[2] );
  oscillatorTimeCodeBit( h[2] );
  h[1] = bitRead( hour01, 1 );
  Serial.print( h[1] );
  oscillatorTimeCodeBit( h[1] );
  h[0] = bitRead( hour01, 0 );
  Serial.print( h[0] );
  oscillatorTimeCodeBit( h[0] );

  //-------分パリティ計算-------
  parity1 = (h[5] + h[4] + h[3] + h[2] + h[1] + h[0]) % 2;
}
//******** タイムコード(その年の1月1日からの通算日)送信 ***********
void sendDayTotalParityTimeCode(){
  uint8_t t_day100 = (uint8_t)floor((double)day_total / 100.0);
  uint8_t t_day10 = ((uint8_t)(floor((double)day_total / 10.0))) % 10;
  uint8_t t_day01 = day_total % 10;
  uint8_t b;

  Serial.print('0');
  oscillatorTimeCodeBit(0);
  Serial.print('0');
  oscillatorTimeCodeBit(0);

  //---------100の位-------------
  b = bitRead( t_day100, 1 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( t_day100, 0 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  Serial.print('0');
  oscillatorTimeCodeBit(0);
  //---------10の位-------------
  b = bitRead( t_day10, 3 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( t_day10, 2 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( t_day10, 1 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( t_day10, 0 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );

  sendMerker(); //P3 ポジションマーカー

  //---------1の位-------------
  b = bitRead( t_day01, 3 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( t_day01, 2 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( t_day01, 1 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( t_day01, 0 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );

  Serial.print('0');
  oscillatorTimeCodeBit(0);
  Serial.print('0');
  oscillatorTimeCodeBit(0);

  //---------パリティビット---------
  Serial.print(parity1);
  oscillatorTimeCodeBit(parity1);
  Serial.print(parity2);
  oscillatorTimeCodeBit(parity2);
  //---------予備ビット-------------
  Serial.print('0');
  oscillatorTimeCodeBit(0); //SU1 予備ビット
}
//**********タイムコード(年)下2桁送信**********************
void sendYearTimeCode(){
  uint8_t year10 = (uint8_t)(floor((double)year_now / 10.0));
  uint8_t year01 = year_now % 10;
  uint8_t b;

  //---------予備ビット---------
  Serial.print('0');
  oscillatorTimeCodeBit(0); //SU2 予備ビット
  //---------10の位-------------
  b = bitRead( year10, 3 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( year10, 2 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( year10, 1 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( year10, 0 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  //---------1の位-------------
  b = bitRead( year01, 3 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( year01, 2 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( year01, 1 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( year01, 0 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
}
//********** タイムコード(曜日、うるう秒)送信 ************
void sendWeekdayUruuTimeCode(){
  uint8_t b;

  //---------曜日-------------
  b = bitRead( weekday_now, 2 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( weekday_now, 1 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  b = bitRead( weekday_now, 0 );
  Serial.print( b );
  oscillatorTimeCodeBit( b );
  //---------うるう秒---------
  Serial.print('0');
  oscillatorTimeCodeBit(0); //LS1:うるう秒
  Serial.print('0');
  oscillatorTimeCodeBit(0); //LS2:うるう秒
  //--------------------------
  Serial.print('0');
  oscillatorTimeCodeBit(0);
  Serial.print('0');
  oscillatorTimeCodeBit(0);
  Serial.print('0');
  oscillatorTimeCodeBit(0);
  Serial.print('0');
  oscillatorTimeCodeBit(0);
}
//********** タイムコードビット送信 *****************
void oscillatorTimeCodeBit(uint8_t b){
  if( b ){ //2進数の1
    oscillator(gpio_high, 500);
    oscillator(gpio_low, 500);
    delay(1); //ウォッチドッグタイマを動作させるために必要
  }else{ //2進数の0
    oscillator(gpio_high, 800);
    oscillator(gpio_low, 200);
    delay(1); //ウォッチドッグタイマを動作させるために必要
  }
}
//*********** 40kHz High レベル発信******************
void oscillator(const uint8_t pin, uint16_t time_osc){
  uint32_t last_time = millis();
  for(;;){
    long startCount;

    startCount = getCpuCcount();
    digitalWrite(pin, HIGH);
    while(getCpuCcount() - startCount < 2960) NOP();

    startCount = getCpuCcount();
    digitalWrite(pin, LOW);
    while (getCpuCcount() - startCount < 2700) NOP();

    if(millis() - last_time == time_osc) return;
  }
}
//*********CPUクロックでカウントする*****************
static inline unsigned getCpuCcount(void){
  unsigned r;
  asm volatile ("rsr %0, ccount" : "=r"(r));
  return r;
}
//*********** その年の1月1日からの通算日計算 ******
uint16_t calcDayTotal(){
  //Arduino Timeライブラリ関数を使う
  tmElements_t tm = {0, 0, 0, 0, 1, 1, (uint8_t)(year()-1970)};
  time_t t = makeTime(tm);
  return (now() - t) / SECS_PER_DAY + 1;
}
//*********** 現在時刻代入 ************************
void getNowTime(){
  min_now = minute();
  hour_now = hour();
  year_now = year() - 2000;
  if(year_now > 100) year_now = 0; //有り得ない数値は0にする
  weekday_now = weekday() - 1;
  day_total = calcDayTotal();
  Serial.printf("\r\nYear=%d / TotalDay=%d / Weekday=%d / %02d:%02d\r\n", year_now, day_total, weekday_now, hour_now, min_now);
}
//********* NTP time GET ***************************
time_t getNtpTime(){
  while (Udp.parsePacket() > 0) ;
  Serial.println("Transmit NTP Request");
  sendNtpPacket(timeServer);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * 3600UL;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}
//******** Send NTP Packet *************************
void sendNtpPacket(IPAddress &address){
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;         
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

5-6行目でご自分のルーターの SSID とパスワードに書き換えてください。

先に紹介したように、以前のこちらの記事のコメント欄で、Kat-Kaiさんに教えていただいた、マイクロセコンド単位でパルス幅を変えられる関数を入れています。

2年前のこちらの記事と殆ど同じ構成ですが、JJY のHIGHレベル信号関数とLOWレベル関数を同じ関数で統合しています。

メインloop関数内で、ネット上の NTPサーバーから30分毎に時刻を取得して、マルチタスクループ内で、JJY信号を GPIO から発信させています。

重要なのは、マルチタスクループ内では、約1秒くらいの間に最低1ms以上の休止時間を設けねばなりません。
ESP32 のウォッチドックタイマを動作させるためです。
ですから、109-114行と、318-328行のところに delay を入れて、トータルで 60000ms に近づくようにしています。
要するに、JJY のLOWレベル信号が終わる時に 1ms の休止時間を設けています。
LOWレベル信号は無くても電波時計は受信できるので、影響の少ない LOWレベル信号に delay を入れています。
オシロスコープで波形を眺めていると、休止時間がわかると思います。
40kHzの信号内で、1ms の休止時間っていうのは結構長いですね。

コンパイル書き込み実行

では、Arduino IDE でコンパイル書き込み実行させてみて下さい。
そして、インターネットにつながった Wi-Fi ルーターを起動して、シリアルモニターを 115200 bps で起動しておいてください。

しばらくして、標準時刻がゼロ秒になったところで、LED が点滅し始めると思います。

シリアルモニターには以下のように表示されます。

esp32_resonance50.jpg

Total 時間が 60000 ms になるように、スケッチ上の oscillator 関数の時間や、ウォッチドッグタイマ動作用の delay を調整してください。
その他調整方法は、以前の記事を参照してください。

アンテナ用ワイヤーと電波時計の設置方法

今回は、以前の記事とは別の設置方法を紹介します。
何重にも巻いたループアンテナは作りません。

要するに、電波は 10㎝ も飛びませんが、電流の流れているコードワイヤー単線のみで近くに電波時計が置いておけば、しっかり受信してくれます。

esp32_resonance51.jpg

交流なので、電流の方向は図の方向ではなく、40kHzで交互になりますが、流れる電流の方向を意識してワイヤーを配置すると良いと思います。
下図の様に逆方向の電流同士のワイヤーを近づけてしまうと、電流磁界が相殺されて電波が飛ばないので気を付けてください。

esp32_resonance52.jpg

このように電流の逆方向の電路をピッタリ付けることによって、放射ノイズを減らすことになります。
その部分を撚ってツイストさせると、さらに電波が飛びづらくなります。
それがツイストペアーと言われているやつです。
LANケーブルなどの高周波ケーブルの中は、ツイストペアー線だらけですね。

また、大きい円でコイル状に巻くと、今度は同じ方向の電流が集まるので、その付近の電波は多くなると思われます。
以前の記事はそれを使っています。
これはいろいろと実験してみて下さい。

いずれにしても、パッシブパーツだけの構成なので、大して電波は飛びません。
コイル状に巻かなくても、単線だけで、ワイヤーに電波時計を近づければ結構十分受信してくれますよ。

というわけで、ここまで電波送信回路が何となく分かって来たので、いつかアマチュア無線免許でも取って、ダイポールアンテナとかで電波発信してみたいという欲が出て来てしまいますね。

いつになることやら・・・。

まとめ

どうでしょうか?

AD9833 のようなサイン波発生チップを使わずとも、パッシブパーツだけで構成した LC 共振回路だけで、十分キレイなサイン波形ができることが分かりました。

いままでインダクターの挙動が大の苦手だったのですが、これで好きなパーツになりましたね。

私個人的なLC共振回路の発見は、一旦、スイッチで瞬間的に電圧をかけてやれば、その後電圧をかけ続けているのに、勝手に自然とサイン波振動してしまうことです。
これに気付いて共振回路の利用方法をひらめいたことが大きな収穫でした。
後は、電波発信機にしたければ、この振動を維持して増幅すればいいだけのことです。

でも、電圧だけ増幅しても飛ばないので、電波を出すには電流を流すことということもわかりました。

このことが実際に体感で理解できたので、グランドループノイズなどの対策もイメージできますし、モーターやソレノイドなどのノイズ対策もイメージできそうな気がして、なんでも作れそうな気がしました。

それにしても、コイルって改めてスゴイですね・・・。

ということで、自己満足ですが、以前指摘された共振回路のリベンジを果たすことができたのではないかと思います。
これで心置きなく次の工作やプログラミングに進めそうです。

因みに、何か間違えていたらコメント投稿欄等でご連絡いただけると助かります。
何しろド素人ですから。

今回はここまでです。
そろそろディープラーニング勉強しないと・・・。

ではまた・・・。

このブログの維持運営にご支援いただけると助かります。
支援方法はこちらの記事をご覧ください。
(管理人:mgo-tec)

 

スポンサーリンク

mgo-tec電子工作 関連コンテンツ ( 広告含む )

 

mgo-tec電子工作ブログ管理人おすすめ

コメントを残す

メールアドレスが公開されることはありません。

*画像の文字を入力してください。(スパム防止の為)

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください