波形発生器( AD9833 ) と ESP32 でサイン波を発生させ、電波時計を合わせる実験

ESP32 ( ESP-WROOM-32 )

こんばんは。

今回は前回の記事に続いて、 アナログデバイセズの波形信号発生器 ( Wave Generator ) AD9833で純粋なサイン波(正弦波)を発生させて、日本標準電波 JJY を発信して市販の電波時計を合わせてみます。
ただ、AD9833 のサイン波は最大0.65V の微小信号なので、トランジスタで増幅させてみました。

スポンサーリンク

本当は FET が良かったのですが、手持ちで良いものが無かったので、一昔前の電子工作で定番だったトランジスタ 2SC1815 を使ってみました。
東芝セミコンダクター社ではもう生産終了してしまったようで、秋月電子通商では在庫品のみとなってしまいました。
私もパーツケースの底で眠っていたこのトランジスタを成仏させようと思い、使ってみた次第です。

AD9833 で生成したサイン波を 2SC1815 で3.3V電源で増幅しようとすると、電流を流さなければいけないのでかなり無理がありました。
やはり電圧駆動できる FET の方が圧倒的に使いやすいですね。

でも、そこを何とか 2SC1815 で増幅させようとして、手探りでいろいろやってみました。
結局あまり上手くいかず、時間もかかり過ぎてしまったので、回路シミュレータを使ってみようと思い立ちました。
定番中の定番の LTspice を使っても良かったのですが、もっと簡単にできるものが無いかと探していたところ、Webベースのとても素晴らしいシミュレータを見つけました。
以下のリンクにあるものです。

Circuit Simulator Applet

これ、ちょっと使ってみたら、スッゴイ簡単で、しかも電流の流れがリアルタイムで表示してくれるんです。
そして、知りたいポイントの波形もリアルタイムで見られるんです。
こりゃぁ便利ですヨ!
ほんとお勧めです!

トランジスタは、ベースとエミッタ間の電圧や、コレクタとエミッタ間の電圧があるので、3.3V という少ない電源電圧で効果的に増幅しようとするとチョイと難しいです。
ESP32-DevKitC や ESPr Developer 32 には 5V 出力がありますが、それは電圧が安定しないので使えず、安定した 3.3V 電源で使う方が良いのです。
でも、この回路シミュレータを使ったことで抵抗値等の定数が短時間で決定できて、3.3V 電圧で増幅できることが確認できました。
しかも、ちょっと変なコンデンサーループ回路を作ると高周波発信も再現してくれて、とーっても便利です。
ズバラシイ!!

ということで、AD9833 のサイン波の自作トランジスタ増幅回路を説明したいと思います。
LCR共振回路を使うと簡単にできるそうなのですが、ここではあくまで、自己満足的な回路で、JJY信号発信にしてはオーバースペックです。
因みに、今回の増幅回路程度では1mも電波が飛ばないので電波法違反にならないと思います。
ですが、私はアマチュアで趣味程度の範囲で工作しておりますので、動作保証はできません。
また、いかなるトラブルも責任を負いませんのでご了承ください。
ただ、誤りやお気づきの点がありましたら、コメント等でご連絡いただけると助かります。

ここで紹介している記事は、あくまで私の趣味の思い付き実験です。
独断と偏見で、自分のやりたいことをやってみただけです。
電波回路については私は素人です。
ですからここではかなりオーバースペックなモジュールや測定器を使っています。
あくまで個人のオタク的な自己満足工作と思ってください。

また、この記事を書いた当初は LC共振回路というものを良く知りませんでした。
最新記事では、LC共振回路の理解を深めて、LC共振回路を使って、ガッツリ電波時計を合わせる実験もして見ましたので、合わせて以下の記事もご覧ください。
AD9833を使わなくてもパッシブ回路だけでガッツリ電波時計を合わせられました。
(2019/01/17)
https://www.mgo-tec.com/blog-entry-esp32-lc-resonance-radio-watch.html

準備するもの

AD9833 プログラマブル信号発生器モジュール

アナログデバイセズ社のプログラマブル波形発生器チップ AD9833 を使ったモジュールです。
これは中国の販売店で、到着するまでに15日くらいかかりました。

ESP-WROOM-32 ( ESP32 )開発ボード

スイッチサイエンスさんの ESPr Developer 32 は保護機能が充実していてお勧めです。
ピンヘッダは別売りです。

ESPr Developer 32
スイッチサイエンス(Switch Science)

ESPr® Developer 32

これの使い方は以下のページを参照してください。

ESPr Developer 32 ( スイッチサイエンス製 ) を使ってみました

その他、Amazon.co.jpさんでは ESP32-DevKitC があります。

トランジスタ 2SC1815 GR 2つ

直流電流増幅率hFE = 200~400 のものです。

秋月電子通商さんで売っていますが、メーカーでは生産終了したので、在庫品のみとのこと。

http://akizukidenshi.com/catalog/g/gI-00881/

私の物はかなり昔に購入したもので、刻印が 2SC1815 GR IE となっていました。

半固定抵抗

私が使ったものは以下のものです。

●10kΩ半固定抵抗(0.25W)×3個
●1kΩ半固定抵抗(0.5W)×1個
●500kΩ半固定抵抗(0.5W)×1個

0.25W は秋月電子通商さんにありますが、下図の様な 0.5W のものは千石電商さんで買った記憶があります。

コンデンサ

高周波応答性の良い積層セラミックコンデンサが良いです。

●1μF ×1個
●0.1μF ×1個
●0.01μF ( 10nF ) ×3個
●4700pF ( 4.7nF ) ×2個
●2200pF ( 2.2nF ) ×2個
●1000pF ( 1nF ) ×1個
●100pF ×1個

固定抵抗

1/4W~1/2W のカーボン抵抗でOKです。

●100Ω ×2個
● 10kΩ ×2個

普通の砲弾型LED

5V程度の普通の砲弾型 LED で良いです。
10mA以上の電流OKのものにしてください。

ブレッドボード

サンハヤトさんの SAD-101 がお勧めです。

サンハヤト SAD-101 ニューブレッドボード
サンハヤト
¥568(2024/12/26 07:02時点)

その他、超小型ブレッドボードもお勧めです。
SAD-101 と合体できて良いです。

100MHz 級のオシロスコープ

純粋な40 kHz サイン波を作るには、高調波を除去できているか確認する必要があります。
そのためには、100MHz  sampling / sec 程度のオシロスコープが必要です。
私の場合は日本製の以下のものを使っています。
簡易FFT解析もできます。
(プローブは別売り)

USB接続デジタルストレージオシロスコープ メモリ1M搭載モデル PA-S2000/E
ピーアンドエーテクノロジーズ
¥107,700(2024/12/26 13:56時点)
PA-S2000用プローブ x1/x10 PA-S2000/PRB
ピーアンドエーテクノロジーズ
¥37,700(2024/12/26 18:23時点)

電流計(25mA レンジのもの)

私が持っているマルチメーター電流計はこれです。
25mA レンジのものがあれば良いです。

ジャンパーワイヤー、パソコン、USBケーブル等

Webベースの回路シミュレータを使って回路の確認

先に紹介した、Webベースの回路シミュレータCircuit Simulator Applet を使って予め回路の電流電圧を確認しておきます。
私のようなアマチュア電子工作家にとって、これはとても便利なものです。
行き当たりばったりで回路を構成するよりか、抵抗値等の定数決めが格段に速いです。

この操作方法はいつか当ブログでアップしようと思いますが、ザッと簡単に説明します。

●パーツを配置するには、メニューの「DRAW」から選んで、画面上でドラッグしていけばそのドラッグした方向にパーツが配置されます。
●パーツを選択するには、マウスをそのアイテム上に持ってくるだけで水色に変わります。
クリックしなくて良いです。
必ず水色に変わったことを確認してから次の操作をしてください。
●パーツを移動するには、Shift キーを押しながらドラッグします。
●パーツの端子を伸ばすには、Ctrl キーを押しながら端子をドラッグします。
●パーツをコピーするにはそのアイテムを右クリックして Copy を選択します。
●パーツのパラメーターを編集するには、右クリックして Edit を選択します。
●高周波を見るためには、メニューの「Other Options」で設定できます。

その他、詳しい操作方法はいつか当ブログでアップする予定です。

というわけで、私なりに下図のようなサイン波増幅、AM変調用回路を考えてみました。
本当はトランジスタよりか FET が良かったのですが、手元にあったのが2SC1815 だったので仕方ないです。
素人なのでプロの方々から見ればツッコミどころ満載かも知れません。
もし、何かありましたらコメント等でご連絡いただけると助かります。

一応、下図の様に解説も入れておきます。

水色の破線の左側が標準電波 JJY の搬送波を作るサイン波増幅用回路で、右側がAM変調回路です。
AM変調は ESP-WROOM-32 ( ESP32 )の GPIO の電圧レベルを変えるだけです。

理想的なサイン波を増幅させるためには、半固定抵抗器でベース電流やバイアス電圧を微調整します。

電圧値や電流値を見たい場合は、その場所を右クリックして [ View in Scope ]を選択すれば、オシロスコープのように波形を見ることが出来ます。
これは素晴らし過ぎますね。
ほんとに優れたソフトです。

ここでは、高周波の振る舞いを見るために、メニューの「Other Options」で速度を変更しています。
そして、GPIO のパルス周波数の本来は1Hz なのですが、それではあまりにも長すぎるので、ここでは4kHz としています。
これを見ると、電流の流れが分かって、直感的に回路修正できますね。
これはホントに優れたソフトだと思います。
何度も言いますが、スバラシイ!!!

ということで、動画にするとこんな感じです。

どうでしょうか?
搬送波をもう一つのトランジスタでスイッチングして、簡易的にですが AM変調がかけられていることが分かると思います。
トランジスタによるスイッチングがホントによ~く分かりますよね。
感心してしまいます。

本来ならば、もっと本格的なAM変調用回路があるかも知れませんが、私の頭で考えられた回路はこの程度です。
でも、これでも十分電波時計は受信してくれます。

これである程度抵抗値やコンデンサ容量に目途をつけられますね。
これからブレッドボード上に実際の回路を組んでいきます。

接続する

では、ブレッドボード上にパーツを接続していきます。

AD9833 ボードの HSPI 接続方法は前回の記事を参照してください。

今回は AD9833 ボードの OUT 端子から上記のトランジスタへ接続します。
ESP32-DevKitC の GPIO #25 からAM変調パルスを出力するので、それを上記のトランジスタ回路へ接続します。

トランジスタの極性は間違えないように注意してください。

すると、ブレットボード上に組んだ写真ではこんな感じになります。
(2017/09/13修正)

アンテナの制作方法や、標準電波 JJY の出し方等については以下の記事を参照してください。

ESP32 で 市販の 電波時計 を合わせてみた

ESP32 の GPIO から 疑似的に 日本標準電波 JJY を出してみる実験、第1弾

スケッチの入力

では、Arduino IDE にスケッチを入力していきます。
Arduino IDE の設定は以下の記事を参照してください。

Arduino core for the ESP32 のインストール方法

前回の記事のAD9833 からサイン波を発生させるスケッチと、前々回の記事の標準電波 JJY を発信させるスケッチを融合させました。

スケッチ20行目の T_day が uint8_t型になっていたため、1月1日からの起算日が256以上になると0日になってしまいました。
よって、21行目に新たに uint16_t型に変更しました。
失礼しました。m(_ _)m
2017/9/13

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

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

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

const uint8_t SCLK_OLED =  14; //SCLK
const uint8_t MOSI_OLED =  13; //MOSI (Master Output Slave Input)
const uint8_t Fsync_PIN = 17;

const uint8_t AMmod_pin = 25; //AM変調用 ESP32 GPIO

const int SINE = 0b0010000000000000; //0x2000
const float refFreq = 25000000.0;

SPIClass spi(HSPI);

uint8_t Min, Hour, Year, Weekday;
uint16_t T_day;
uint8_t PA1, PA2; //パリティ定義
//-----------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 Ntp_Get_LastTime = 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);

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

  Udp.begin(localPort);

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

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

  spi.begin(SCLK_OLED, 12, MOSI_OLED, Fsync_PIN);
  spi.setFrequency(40000000);
  spi.setDataMode(SPI_MODE2);

  pinMode(Fsync_PIN, OUTPUT);
  digitalWrite(Fsync_PIN, HIGH);

  Control_Resister_Write(0b0000000100000000); //AD9833 Reset
  delay(50);
  AD9833_SetFrequency(40000, SINE); //AD9833 サイン波搬送波40kHz 送信

  Ntp_Get_LastTime = millis();

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

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

  while(1){
    LastTime = millis();
    Now_Time();                   //現在時刻初期化
    Marker();                     //M マーカー送信
    Tcode_Min_Send();             //タイムコード(分)送信
    Marker();                     //P1 ポジションマーカー送信
    Tcode_Hour_Send();            //タイムコード(時)送信
    Marker();                     //P2 ポジションマーカー送信
    Tcode_TotalDay_Parity_Send(); //タイムコード(通算日)送信
    Marker();                     //P4 ポジションマーカー送信
    Tcode_Year_Send();            //タイムコード(年)送信
    Marker();                     //P5 ポジションマーカー送信
    Tcode_Weekday_Uruu_Send();    //タイムコード(曜日、うるう秒)送信
    Marker();                     //P0 ポジションマーカー送信

    Serial.printf("\r\n[Total = %d ms]\r\n", (int)(millis() - LastTime));

    while(second() != 0){ //もし、時間がズレた場合、ゼロ秒になるまで待つ
      Serial.print('-');
      delay(1); //ウォッチドッグタイマを動作させるために必要
    }
  }
}
//******** マーカーおよびポジションマーカー送信 ************************
void Marker(){
  Serial.print(".M.");
  Generator_High(200);
  Generator_Low(800);
}
//******** タイムコード(分)送信 **************
void Tcode_Min_Send(){
  uint8_t Min10 = floor(Min / 10);
  uint8_t Min01 = Min % 10;
  uint8_t m[7] = {0}; //分のパリティ PA2 計算用配列初期化

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

  //-------分パリティ計算-------
  PA2 = (m[6] + m[5] + m[4] + m[3] + m[2] + m[1] + m[0]) % 2;
}
//******** タイムコード(時)送信 *************
void Tcode_Hour_Send(){
  uint8_t Hour10 = floor(Hour / 10);
  uint8_t Hour01 = Hour % 10;
  uint8_t h[6] = {0}; //時のパリティ PA1 計算用配列初期化

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

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

  //-------分パリティ計算-------
  PA1 = (h[5] + h[4] + h[3] + h[2] + h[1] + h[0]) % 2;
}
//******** タイムコード(その年の1月1日からの通算日)送信 ***********
void Tcode_TotalDay_Parity_Send(){
  uint8_t T_day100 = floor(T_day / 100);
  uint8_t T_day10 = ((uint8_t)(floor(T_day / 10))) % 10;
  uint8_t T_day01 = T_day % 10;
  uint8_t b;

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

  //---------100の位-------------
  b = bitRead( T_day100, 1 );
  Serial.print( b );
  Time_Code_Bit_Generate( b );
  b = bitRead( T_day100, 0 );
  Serial.print( b );
  Time_Code_Bit_Generate( b );
  Serial.print('0');
  Time_Code_Bit_Generate(0);
  //---------10の位-------------
  b = bitRead( T_day10, 3 );
  Serial.print( b );
  Time_Code_Bit_Generate( b );
  b = bitRead( T_day10, 2 );
  Serial.print( b );
  Time_Code_Bit_Generate( b );
  b = bitRead( T_day10, 1 );
  Serial.print( b );
  Time_Code_Bit_Generate( b );
  b = bitRead( T_day10, 0 );
  Serial.print( b );
  Time_Code_Bit_Generate( b );

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

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

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

  //---------パリティビット---------
  Serial.print(PA1);
  Time_Code_Bit_Generate(PA1);
  Serial.print(PA2);
  Time_Code_Bit_Generate(PA2);
  //---------予備ビット-------------
  Serial.print('0');
  Time_Code_Bit_Generate(0); //SU1 予備ビット
}
//**********タイムコード(年)下2桁送信**********************
void Tcode_Year_Send(){
  uint8_t Y10 = (uint8_t)(floor( Year / 10 ));
  uint8_t Y01 = Year % 10;
  uint8_t b;

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

  //---------曜日-------------
  b = bitRead( Weekday, 2 );
  Serial.print( b );
  Time_Code_Bit_Generate( b );
  b = bitRead( Weekday, 1 );
  Serial.print( b );
  Time_Code_Bit_Generate( b );
  b = bitRead( Weekday, 0 );
  Serial.print( b );
  Time_Code_Bit_Generate( b );
  //---------うるう秒---------
  Serial.print('0');
  Time_Code_Bit_Generate(0); //LS1:うるう秒
  Serial.print('0');
  Time_Code_Bit_Generate(0); //LS2:うるう秒
  //--------------------------
  Serial.print('0');
  Time_Code_Bit_Generate(0);
  Serial.print('0');
  Time_Code_Bit_Generate(0);
  Serial.print('0');
  Time_Code_Bit_Generate(0);
  Serial.print('0');
  Time_Code_Bit_Generate(0);
}
//********** タイムコードビット送信 *****************
void Time_Code_Bit_Generate(uint8_t b){
  if( b ){ //2進数の1
    Generator_High(500);
    Generator_Low(500);
  }else{ //2進数の0
    Generator_High(800);
    Generator_Low(200);
  }
}
//*********** 40kHz High レベル発信******************
void Generator_High(uint16_t H_time){
  dacWrite(AMmod_pin, 255);
  delay(H_time);
}
//*********** 40kHz LOW レベル ******************
void Generator_Low(uint32_t H_time){
  dacWrite(AMmod_pin, 80);
  delay(H_time);
}
//*********** その年の1月1日からの通算日計算 ******
uint16_t TotalDay(){
  //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 Now_Time(){
  Min = minute();
  Hour = hour();
  Year = year() - 2000;
  if(Year > 100) Year = 0; //有り得ない数値は0にする
  Weekday = weekday() - 1;
  T_day = TotalDay();
  Serial.printf("Year=%d / TotalDay=%d / Weekday=%d / %02d:%02d\r\n", Year, T_day, Weekday, Hour, Min);
}
//***********************************
void AD9833_SetFrequency(uint32_t frequency, uint16_t Waveform) {
  uint32_t FreqWord = (frequency * pow(2, 28)) / refFreq;

  uint16_t MSB = (uint16_t)((FreqWord & 0xFFFC000) >> 14);
  uint16_t LSB = (uint16_t)(FreqWord & 0x3FFF);

  LSB |= 0b0100000000000000; //=0x4000
  MSB |= 0b0100000000000000; //=0x4000

  Control_Resister_Write(0b0010000000000000); //制御ワード書き込み
  Control_Resister_Write(LSB);
  Control_Resister_Write(MSB);

  Control_Resister_Write(0b1100000000000000); //位相シフトはゼロ
  Control_Resister_Write(Waveform);
}
//***********************************************
void Control_Resister_Write(uint16_t b){
  digitalWrite(Fsync_PIN, LOW);
  spi.write16(b);
  digitalWrite(Fsync_PIN, HIGH);
}
//********* NTP time GET ***************************
time_t Get_Ntp_Time(){
  while (Udp.parsePacket() > 0) ;
  Serial.println("Transmit NTP Request");
  Send_NTP_Packet(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 Send_NTP_Packet(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();
}

【解説】

前回の記事および前々回の記事と重複しているところは省略します。

●13行目:
AD9833ボードから出力したサイン波搬送波にデジタルデータを載せるため、ESP32-DevKitC の GPIO #25 から HIGH-LOW 切り替えて、上記のトランジスタ回路でAM変調します。

●72-74行:
ここで、AD9833を初期化して、40kHz のサイン波をOUT端子から送信します。
ここでセットするだけで延々と送信し続けます。

●80-90行:
標準電波 JJY を送信するのは、ESP32 のマルチタスクで、CPUコア番号ゼロ番で実行します。
インターネットの NTPサーバーから時刻を取得して ESP32 の内部時計を補正するのは、CPUコア番号1番のメインループで行います。
ESP32 のマルチタスクについては以下の記事を参照してください。
Arduino – ESP32 のマルチタスク ( Dual Core ) を試す

●344-352行:
前々回の記事と異なるのはここの部分です。
digitalWritte で High-LOW を切り替えるのではなく、ESP32 の DA コンバーターを使います。
ESP-WROOM-32 ( ESP32 )で DAC を使える端子は、GPIO #25 , #26 です。
これを使うには、事前に pinMode で設定する必要はなく、いきなり dacWrite関数の実行でOKです。
標準電波 JJY の規格では、HIGHレベルとLOWレベルの比率が 10:1 なので、オシロスコープで測定しながら、LOWレベルを微調整します。
私の場合、LOWレベルは50 でイイ感じでした。

コンパイル書き込み、実行

では、スケッチをコンパイル書き込みし、実行させてみてください。

NTPサーバーから補正した時刻がゼロ秒になったらアンテナに電流が流れます。

ここからは、素人なりの、あくまで自己満足測定です。
FFT解析など必要ないかもしれませんね。
大げさに思ったら読み飛ばしてください。
少なくとも私には必要な測定でしたが・・・。

まず、アンテナ部分を外して、そこに電流計を接続して、25mAレンジにして測定します。
アンテナ部分には 5mA 流せば十分です。
そして、オシロスコープの測定ポイントは下図のポイントで測定してみます。

可変抵抗器を調整して、測定ポイント①、②の電圧値がおよそ下図の様になるように調整してみます。
この時、電流計は 5mA 以内に抑えるようにします。

これの調整のポイントは、トランジスタ Tr 1 のベース、エミッタ間電圧が0.7V 付近に触れるようにバイアス電圧を調整することで、Tr1 のサイン波が増幅されます。
ただし、AM変調用のトランジスタ Tr2 も組み合わせるとこのサイン波が崩れたりするので、両方合わせた回路で微調整していきます。

また、バイアス電圧が低すぎるとサイン波が歪み、高調波が増えてしまいます。
特にFFT解析で周波数を分析しながら行うとそれがはっきり分かります。
上記の様なサイン波でも分析すると多少歪んでいます。

それでも、以前の記事のパルス波に比べれば、劇的に高調波は減っていますね。
波形発生器でサイン波を生成しているので当たり前ですが、スッキリしてイイですね。

アンテナは発信するだけでなく、受信もしてしまうので、逆流防止のダイオードが必要だと思われます。
LED が入っていますが、高周波は遮断できませんので、ショットキーバリアを入れるのが良いかと思います。
ただ、その分電圧降下してしまうので、3.3Vという限られた電圧では厳しいものがありますね。

では、ここまでできたら、今度は電流計をアンテナに変えて、再度調整してみます
これ、大事です。
電流計はわずかですが抵抗値があるので、若干波形が違ってきます。

そして、HIGH と LOW の波形幅の比を見てみましょう。
アンテナ部分の電圧をオシロスコープで見てみます。

トランジスタはエミッタ接地なので、逆相になっています。
HIGHレベルでは直流電圧成分が混ざっていて段差ができています。
ただ、直流成分は電波にならないので無視できます。
交流成分のみで HIGHレベルと LOWレベルの比をみてみると、ほぼ標準電波 JJY 規格の 10:1ですね。

もし、ここで10:1になっていなければ Arduino IDE スケッチで調整します。

上記スケッチの 350行の 50 という数値を変えればLOWレベルの調整ができます。。
その場合、AM変調用トランジスタのベース電流調整用の可変抵抗も調整して、LED が点滅するように微調整します。

これで日本標準電波 JJY の規格をほぼ満たした電波を ESP-WROOM-32 ( ESP32 ) と AD9833 で作ることができました。
長かった・・・。

これで電波時計を合わせてみます。

前々回の記事よりも、カツンと受信できて、ちょっとくらいアンテナから離しても確実に受信してくれますね。
メーカーによって受からないものもありましたが、私の手持ち電波時計は全て気持ちよく受信できました。

でも、アンテナに5mA 流す程度では1mも飛ばないので良いのですが、さらに電流を増強したりして電界強度を高める場合には、電波法違反にならないように十分注意する必要があります。

まとめ

どうでしょうか?

電波を作るには、純粋なサイン波を搬送波とすることが大事だということが、私自身改めて理解しました。
市販の電波時計もガッツリ受信してくれるようになりました。

でも、今回の実験で改めて思った事は、高性能なオシロスコープが無いとなかなか質の良い電波を出すことは難しいですね。
私のオシロスコープの FFT解析は簡易的なものなので、高性能なFFTアナライザーが欲しいと思いました。
(大げさかもしれないけど)

これを作ると、アマチュア無線免許を取りたいとも思いました。
陸上無線も取りたくなってきました。

それと、やはり今回の実験で感動したことは、Webベースの回路シミュレータでした。
イマイチ電子回路が分からない私のようなアマチュアにとってはとても素晴らしいツールです。
これさえあれば、ディスクリートアナログ回路をバンバン組めそうな気がします。
この回路シミュレータのおかげで、パーツケースの奥底に眠っていた 2SC1815 を無事成仏させる(いや!、生かし切る)ことができました。

ということで、アマチュアなりにトランジスタ回路を組んでみましたが、穴だらけだと思いますので、もし誤りやお気づきの点がありましたらコメント等でご連絡いただけると助かります。

以上、今回はここまでです。

ではまた・・・。

Amazon.co.jp 当ブログのおすすめ

スイッチサイエンス ESPr Developer 32 Type-C SSCI-063647
スイッチサイエンス
¥2,420(2024/12/26 11:02時点)
ZEROPLUS ロジックアナライザ LAP-C(16032)
ZEROPLUS
¥28,006(2024/12/26 17:56時点)
Excelでわかるディープラーニング超入門
技術評論社
¥2,068(2024/12/26 09:03時点)

コメント

  1. 匿名 より:

    >もし誤りやお気づきの点がありましたらコメント等でご連絡いただけると助かります

    とのことでしたので…

    “永遠と” ではなく “延々と” が正解です。

    • mgo-tec mgo-tec より:

      匿名さん

      ご指摘ありがとうございます。
      早速修正しました。
      結構、他の記事でも間違えていましたね。
      失礼しました。
      m(_ _)m
      私は、工作がメインになると、文章は多少間違えてもイイや、みたいな脳ミソになってしまっています。
      今後できるだけ注意していきたいと思っています。

タイトルとURLをコピーしました