- DDS関数発生器とは何ですか?
- AD9833関数発生器ICの動作を理解する
- AD9833ベースの関数発生器を構築するために必要なコンポーネント
- AD9833ベースの関数発生器-回路図
- AD9833ベースの関数発生器-Arduinoコード
- AD9833ベースの関数発生器のテスト
- さらなる機能強化
私のようにさまざまな電子回路を調整したい電子愛好家の場合は、適切な関数発生器を用意することが必須になることがあります。しかし、そのような基本的な機器は大金がかかる可能性があるため、所有することは問題です。独自のテスト機器を構築することは、安価であるだけでなく、知識を向上させるための優れた方法でもあります。
そのため、この記事では、Arduino とAD9833 DDS関数発生器モジュールを使用して、出力で最大周波数12 MHzの正弦波、方形波、三角波を生成できる単純な信号発生器を構築します。そして最後に、オシロスコープを使用して出力周波数をテストします。
以前、基本的なアナログ回路を使用して、単純な正弦波ジェネレーター、方形波ジェネレーター、および三角波ジェネレーターを構築しました。あなたがいくつかの基本的な波形発生器回路を探しているなら、あなたはそれらをチェックすることができます。また、AD9833モジュールを使用せずに安価なArduino関数発生器を構築したい場合は、DIYArduino波形発生器プロジェクトを確認できます。
DDS関数発生器とは何ですか?
名前が示すように、関数発生器は、設定時に特定の周波数で特定の波形を出力できるデバイスです。たとえば、出力周波数応答をテストするLCフィルタがある場合、関数発生器を使用して簡単にテストできます。あなたがする必要があるのはあなたの望む出力周波数と波形を設定することです、そしてあなたは応答をテストするためにそれを上下にクランクすることができます。これはほんの一例であり、リストが進むにつれて、それを使ってさらに多くのことができるようになります。
DDSの略ダイレクトデジタル合成。これは、デジタル-アナログコンバーター(DAC)を使用してゼロから信号を構築するタイプの波形発生器です。この方法は、特に正弦波を生成するために使用されます。しかし、私たちが使用しているICは、方形波または三角波信号を生成できます。DDSチップ内で発生した操作はデジタルであるため、周波数を非常に高速に切り替えることも、ある信号から別の信号に非常に迅速に切り替えることもできます。このデバイスは、広い周波数スペクトルを持つ優れた周波数分解能を備えています。
AD9833関数発生器ICの動作を理解する
私たちのプロジェクトの中心は、アナログデバイスによって設計および開発されたAD9833プログラマブル波形発生器ICです。これは、最大周波数12 MHzの正弦波、三角波、および方形波を生成できる、低電力のプログラム可能な波形発生器です。ソフトウェアプログラムだけで出力周波数と位相を変えることができる非常にユニークなICです。 3線式SPIインターフェースを備えているため、このICとの通信は非常にシンプルで簡単になります。このICの機能ブロック図を以下に示します。
このICの動作は非常に簡単です。上記の機能ブロック図を見ると、0から2πまでの正弦波のすべての可能なデジタル値を格納することを目的とした位相アキュムレータがあることがわかります。次に、後で直接振幅にマッピングできる位相情報を変換することを目的としたSINROMがあります。 SIN ROMは、デジタル位相情報をルックアップテーブルへのアドレスとして使用し、位相情報を振幅に変換します。そして最後に、SIN ROMからデジタルデータを受信し、それを対応するアナログ電圧に変換することを目的とした10ビットのデジタル-アナログコンバーターがあります。これが出力から得られます。出力には、わずかなソフトウェアコードでオンまたはオフにできるスイッチもあります。これについては、記事の後半で説明します。上記の詳細は、IC内で起こっていることを非常に簡略化したものです。上記の詳細のほとんどは、AD9833データシートから取得したものです。詳細については、こちらを確認することもできます。
AD9833ベースの関数発生器を構築するために必要なコンポーネント
AD9833ベースの関数発生器を構築するために必要なコンポーネントを以下に示します。この回路は非常に汎用的なコンポーネントで設計されているため、複製プロセスが非常に簡単になります。
- ArduinoNano-1
- AD9833DDS関数発生器-1
- 128 X 64OLEDディスプレイ-1
- 汎用ロータリーエンコーダ-1
- DCバレルジャック-1
- LM7809電圧レギュレータ-1
- 470uFコンデンサ-1
- 220uFコンデンサ-1
- 104pFコンデンサ-1
- 10K抵抗-6
- タクタイルスイッチ-4
- ネジ留め式端子5.04mm-1
- 女性ヘッダー-1
- 12V電源-1
AD9833ベースの関数発生器-回路図
AD9833およびArduinoベースの関数発生器の完全な回路図を以下に示します。
ArduinoでAD9833を使用して、目的の周波数を生成します。そしてこのセクションでは、回路図を使用してすべての詳細を説明します。回路で何が起こっているのかについて簡単に説明します。AD9833モジュールから始めましょう。 AD9833モジュールは関数発生器モジュールであり、回路図に従ってArduinoに接続されています。回路に電力を供給するために、適切なデカップリングコンデンサを備えたLM7809電圧レギュレータICを使用しています。これは、電源ノイズが出力信号に干渉して不要な出力をもたらす可能性があるために必要です。いつものように、Arduinoはこのプロジェクトの頭脳として働いています。設定周波数やその他の貴重な情報を表示するために、128 X 64OLEDディスプレイモジュールを接続しました。周波数範囲を変更するために、3つのスイッチを使用しています。 1つ目は周波数をHzに設定し、2つ目は出力周波数をKHzに設定し、3つ目は周波数をMHzに設定します。また、出力を有効または無効にするために使用できる別のボタンがあります。最後に、ロータリーエンコーダーがあります。プルアップ抵抗を接続する必要があります。そうしないと、プーリング方式のボタン押下イベントをチェックしているため、これらのスイッチは機能しません。ロータリエンコーダは周波数を変更するために使用され、ロータリーエンコーダ内の触覚スイッチは設定された波形を選択するために使用されます。
AD9833ベースの関数発生器-Arduinoコード
このプロジェクトで使用される完全なコードは、このページの下部にあります。必要なヘッダーファイルとソースファイルを追加すると、Arduinoファイルを直接コンパイルできるようになります。以下のリンクからad9833Arduinoライブラリおよびその他のライブラリをダウンロードできます。または、ボードマネージャーメソッドを使用してライブラリをインストールすることもできます。
- BillWilliamsによるAD9833ライブラリをダウンロード
- AdafruitによるSSD1306OLEDライブラリをダウンロード
- AdafruitGFXライブラリをダウンロードする
ino のコードの説明 。 ファイル は以下の通りです。まず、必要なすべてのライブラリを含めることから始めます。以下のためのライブラリAD9833 DDSモジュールは、最初のOLEDのためのライブラリが続いていると数学ライブラリは、我々の計算のいくつかのために必要とされます。
#include // AD9833モジュールのライブラリ#include
次に、ボタン、スイッチ、ロータリーエンコーダー、およびOLEDに必要なすべての入力ピンと出力ピンを定義します。
#define SCREEN_WIDATA_PINH 128 // OLEDディスプレイの幅(ピクセル単位)#define SCREEN_HEIGHT 64 // OLEDディスプレイの高さ(ピクセル単位)#define SET_FREQUENCY_HZ A2 //周波数をHz単位で設定するプッシュボタン#defineSET_FREQUENCY_KHZ A3 // Khz単位で周波数を設定するプッシュボタン#defineSET_FREQUENCY_Z A6 // Mhzで周波数を設定するためのプッシュボタン#defineENABLE_DISABLE_OUTPUT_PIN A7 //出力を有効/無効にするためのプッシュボタン#defineFNC_PIN 4 // AD9833モジュールに必要なFsync#define CLK_PIN 8 //エンコーダのクロックピン#defineDATA_PIN 7 / /エンコーダのデータピン#defineBTN_PIN 9 //エンコーダの内部プッシュボタン
その後、このコードで必要なすべての必要な変数を定義します。まず、ロータリーエンコーダーの値を格納する整数変数カウンターを定義します。次の2つの変数clockPinとclockPinStateは、エンコーダーの方向を理解するために必要なピンスタチューを格納します。現在のタイマーカウンター値を保持する時間変数があります。この変数はボタンのデバウンスに使用されます。次に、適用される計算された頻度を保持する符号なしの長い変数moduleFrequencyがあります。次に、デバウンス遅延があります。この遅延は、必要に応じて調整できます。次に、3つのブール変数set_frequency_hzがあります。set_frequency_Khz、および set_frequency_Mhz これらの3つの変数は、モジュールの現在の設定を決定するために使用されます。これについては、この記事の後半で詳しく説明します。次に、出力波形のステータスを格納する変数があります。デフォルトの出力波形は正弦波です。そして最後に、出力波形を設定するために使用されるエンコーダボタンカウントを保持するencoder_btn_count変数があります。
intカウンター= 1; //ロータリーエンコーダーをintclockPinにすると、このカウンター値は増減します。 //ロータリーエンコーダーが使用するピンステータスのプレースホルダーintclockPinState; //ロータリーエンコーダで使用されるピンステータスのプレースホルダーunsignedlong time = 0; // unsigned longmoduleFrequencyをデバウンスするために使用されます; //出力周波数を設定するために使用されますlongdebounce = 220; //デバウンス遅延boolbtn_state; // AD98333モジュールの出力を無効にするために使用boolset_frequency_hz = 1; // AD9833モジュールのデフォルト頻度boolset_frequency_khz; bool set_frequency_mhz; String waveSelect = "SIN"; //モジュールの起動波形intencoder_btn_count = 0; //エンコーダボタンの押下を確認するために使用されます次に、2つのオブジェクトがあります。1つはOLEDディスプレイ用で、もう1つはAD9833モジュール用です。Adafruit_SSD1306 display(SCREEN_WIDATA_PINH、SCREEN_HEIGHT、&Wire、-1); AD9833 gen(FNC_PIN);
次に、setup()関数があります。そのセットアップ関数では、デバッグ用にシリアルを有効にすることから始めます。begin()メソッドを使用してAD9833モジュールを初期化します。次に、割り当てられたすべてのロータリーエンコーダピンを入力として設定します。そして、クロックピンの値をclockPinState変数に格納します。これは、ロータリーエンコーダーに必要な手順です。
次に、すべてのボタンピンを入力として設定し、 display.begin() メソッドを使用してOLEDディスプレイを有効にします。また、 ifステートメント を使用してエラーをチェックします。それが完了したら、表示をクリアして起動スプラッシュ画面を印刷し、スプラッシュ画面の遅延でもある2秒の遅延を追加し、最後に、画面をクリアして更新するupdate_display()関数を呼び出します。もう一度表示します。 update_display() メソッドの詳細については、この記事の後半で説明します。
void setup(){Serial.begin(9600); //シリアル@ 9600ボーを有効にしますgen.Begin(); //これは、AD9833オブジェクトを宣言した後の最初のコマンドである必要がありますpinMode(CLK_PIN、INPUT); //ピンを入力として設定pinMode(DATA_PIN、INPUT); pinMode(BTN_PIN、INPUT_PULLUP); clockPinState = digitalRead(CLK_PIN); pinMode(SET_FREQUENCY_HZ、INPUT); //ピンを入力として設定pinMode(SET_FREQUENCY_KHZ、INPUT); pinMode(SET_FREQUENCY_MHZ、INPUT); pinMode(ENABLE_DISABLE_OUTPUT_PIN、INPUT); if(!display.begin(SSD1306_SWITCHCAPVCC、0x3C)){// 128x64のアドレス0x3D Serial.println(F( "SSD1306の割り当てに失敗しました"));にとって (;;); } display.clearDisplay(); //画面をクリアしますdisplay.setTextSize(2); //テキストサイズを設定しますdisplay.setTextColor(WHITE); // LCDカラーを設定display.setCursor(30、0); //カーソル位置を設定display.println( "AD9833"); //このテキスト表示を印刷します。setCursor(17、20); //カーソル位置を設定display.println( "Function"); //このテキストを出力しますdisplay.setCursor(13、40); //カーソル位置を設定display.println( "Generator"); //このテキストを出力しますdisplay.display(); //表示遅延を更新します(2000); // 2 SECの遅延update_display(); // update_display関数を呼び出します}
次に、loop()関数があります。すべての主要な機能は、ループセクションに記述されています。
まず、ロータリーエンコーダーのClockピンを読み取り、前に宣言したclockPin変数に格納します。次に、 if ステートメントで、ピンの以前の値とピンの現在の値が類似しているかどうかを確認し、ピンの現在の値も確認します。すべてtrueの場合、データピンをチェックします。trueの場合、エンコーダーが反時計回りに回転していることを意味し、counter--コマンドを使用してカウンター値をデクリメントします。それ以外の場合は、counter ++コマンドを使用してカウンター値をインクリメントします。最後に、別の if ステートメントを配置して最小値を1に設定します。次に、clockPinStateを現在のclockPinで更新します。将来の使用のための値。
void loop(){clockPin = digitalRead(CLK_PIN); if(clockPin!= clockPinState && clockPin == 1){if(digitalRead(DATA_PIN)!= clockPin){カウンター-; } else {カウンター++; //エンコーダーはCWを回転しているので、インクリメント} if(counter <1)counter = 1; Serial.println(counter); update_display(); }
次に、ボタンの押下を検出するコードがあります。このセクションでは、ネストされたifステートメントを使用してエンコーダー内のボタンを検出しました。if(digitalRead(BTN_PIN)== LOW && millis()-time> denounce)、 このステートメントでは、最初にボタンかどうかを確認しますピンが低いかどうか、低い場合は押されています。次に、デバウンス遅延を使用してタイマー値を再度チェックします。両方のステートメントが真の場合は、ボタンを押したアクションが成功したと宣言し、その場合はエンコーダー_btn_count値をインクリメントします。次に、最大カウンター値を2に設定する別のifステートメントを宣言します。これは、出力波形の設定に使用しているため、必要です。連続する3つのifステートメントは、値がゼロの場合は正弦波形が選択され、1の場合は方形波、値が2の場合は三角波を選択します。これら3つのifステートメントすべてで、update_display() 関数を使用して表示を更新します 。そして最後に、現在のタイマーカウンター値で時間変数を更新します。
// LOW信号を検出した場合、ボタンが押されますif(digitalRead(BTN_PIN)== LOW && millis()-time> debounce){encoder_btn_count ++; //値をインクリメントしますif(encoder_btn_count> 2)//値が2より大きい場合は、0にリセットします{encoder_btn_count = 0; } if(encoder_btn_count == 0){//値が0の場合正弦波が選択されますwaveSelect = "SIN"; //文字列変数をsin値で更新しますupdate_display(); //表示を更新します} if(encoder_btn_count == 1){//値が1の場合、方形波が選択されますwaveSelect = "SQR"; //文字列変数をSQR値で更新しますupdate_display(); //表示を更新します} if(encoder_btn_count == 2){//値が1の場合三角波が選択されますwaveSelect = "TRI"; //文字列変数をTRI値で更新しますupdate_display();//表示を更新します} time = millis(); //時間変数を更新します}
次に、デバウンス遅延を使用してすべてのボタンを設定するために必要なすべての必要なコードを定義します。ボタンはArduinoのアナログピンに接続されているため、アナログ読み取りコマンドを使用して、アナログ読み取り値が30未満に達した場合にボタンが押されたことを識別し、ボタンが正常に押されたことを検出して、200ミリ秒待機します。それが実際のボタン押下なのか、ノイズのみなのかを確認してください。このステートメントが真の場合、関数発生器のHz、Khz、およびMhz値を設定するために使用される値をブール変数に割り当てます。次に、表示を更新し、時間変数を更新します。 Arduinoに接続されている4つのボタンすべてに対してこれを行います。
if(analogRead(SET_FREQUENCY_HZ)<30 && millis()-時間>デバウンス){set_frequency_hz = 1; //ブール値を更新しますset_frequency_khz = 0; set_frequency_mhz = 0; update_display(); //表示時間を更新= millis(); //時間変数を更新} if(analogRead(SET_FREQUENCY_KHZ)<30 && millis()-time> debounce){set_frequency_hz = 0; //ブール値を更新しますset_frequency_khz = 1; set_frequency_mhz = 0; moduleFrequency =カウンター* 1000; update_display(); //表示時間を更新= millis(); //時間変数を更新} if(analogRead(SET_FREQUENCY_MHZ)<30 && millis()-time> debounce){//デバウンス遅延でアナログピンをチェックset_frequency_hz = 0; //ブール値を更新しますset_frequency_khz = 0; set_frequency_mhz = 1; moduleFrequency =カウンター* 1000000; update_display();//表示時間を更新します= millis(); //時間変数を更新します} if(analogRead(ENABLE_DISABLE_OUTPUT_PIN)<30 && millis()-time> debounce){//デバウンス遅延でアナログピンをチェックしますbtn_state =! btn_state; //ボタンの状態を反転しますgen.EnableOutput(btn_state); //ボタンの状態に応じて関数発生器の出力を有効/無効にしますupdate_display(); //表示時間を更新します= millis(); //時間変数を更新します}}//時間変数を更新します}}//時間変数を更新します}}
最後に、update_display()関数があります。この機能では、ディスプレイの特定の部分がOLEDで更新できないため、このディスプレイを更新するだけではありません。更新するには、新しい値で再描画する必要があります。これにより、コーディングプロセスがはるかに困難になります。
この関数の内部では、表示をクリアすることから始めます。次に、必要なテキストサイズを設定します。その後、カーソルを設定し、display.println( "Function Function");を使用して関数発生器を出力します。コマンド。display.setCursor(0、20) 関数を使用して、テキストサイズを2に設定し、カーソルを(0,20)に設定し ます。
これは、波が何であるかに関する情報を印刷する場所です。
display.clearDisplay(); //最初にディスプレイをクリアしますdisplay.setTextSize(1); //テキストサイズを設定display.setCursor(10、0); //カーソル位置を設定しますdisplay.println( "Function Generator"); //テキストを印刷するdisplay.setTextSize(2); //テキストサイズを設定するdisplay.setCursor(0、20); //カーソル位置を設定する
次に、ブール変数で頻度の詳細を確認し、moduleFrequency変数の値を更新します。これは、Hz、kHz、およびMHzの値に対して行います。次に、waveSelect変数をチェックして、選択されているウェーブを特定します。これで、波のタイプと周波数を設定するための値が得られました。
if(set_frequency_hz == 1 && set_frequency_khz == 0 && set_frequency_mhz == 0){//周波数をHz単位で設定するためのボタンが押されているかどうかを確認しますmoduleFrequency = counter; // moduleFrequency変数を現在のカウンター値で更新します} if(set_frequency_hz == 0 && set_frequency_khz == 1 && set_frequency_mhz == 0){// KHzで周波数を設定するためのボタンが押されているかどうかを確認しますmoduleFrequency = counter * 1000; // moduleFrequency変数を現在のカウンター値で更新しますが、1000を乗算してKHZに設定します} if(set_frequency_hz == 0 && set_frequency_khz == 0 && set_frequency_mhz == 1){// MHzで周波数を設定するためのボタンが押されているかどうかを確認しますmoduleFrequency =カウンター* 1000000; if(moduleFrequency> 12000000){moduleFrequency = 12000000;//周波数が12Mhzカウンター= 12より大きくならないようにします。 }} if(waveSelect == "SIN"){//正弦波が選択されているdisplay.println( "SIN"); gen.ApplySignal(SINE_WAVE、REG0、moduleFrequency); Serial.println(moduleFrequency); } if(waveSelect == "SQR"){// Sqrウェーブが選択されているdisplay.println( "SQR"); gen.ApplySignal(SQUARE_WAVE、REG0、moduleFrequency); Serial.println(moduleFrequency); } if(waveSelect == "TRI"){//トライウェーブが選択されているdisplay.println( "TRI"); gen.ApplySignal(TRIANGLE_WAVE、REG0、moduleFrequency); // AD9833モジュールを更新します。 Serial.println(moduleFrequency); }} if(waveSelect == "SQR"){// Sqrウェーブが選択されているdisplay.println( "SQR"); gen.ApplySignal(SQUARE_WAVE、REG0、moduleFrequency); Serial.println(moduleFrequency); } if(waveSelect == "TRI"){//トライウェーブが選択されているdisplay.println( "TRI"); gen.ApplySignal(TRIANGLE_WAVE、REG0、moduleFrequency); // AD9833モジュールを更新します。 Serial.println(moduleFrequency); }} if(waveSelect == "SQR"){// Sqrウェーブが選択されているdisplay.println( "SQR"); gen.ApplySignal(SQUARE_WAVE、REG0、moduleFrequency); Serial.println(moduleFrequency); } if(waveSelect == "TRI"){//トライウェーブが選択されているdisplay.println( "TRI"); gen.ApplySignal(TRIANGLE_WAVE、REG0、moduleFrequency); // AD9833モジュールを更新します。 Serial.println(moduleFrequency); }
カーソルを再設定し、カウンター値を更新します。再びブール値をチェックしてディスプレイの周波数範囲を更新します。OLEDの動作原理が非常に奇妙であるため、これを行う必要があります。
display.setCursor(45、20); display.println(counter); //カウンタ情報をディスプレイに出力します。 if(set_frequency_hz == 1 && set_frequency_khz == 0 && set_frequency_mhz == 0){display.setCursor(90、20); display.println( "Hz"); //ディスプレイにHzを出力display.display(); //すべてのセットが表示を更新するとき} if(set_frequency_hz == 0 && set_frequency_khz == 1 && set_frequency_mhz == 0){display.setCursor(90、20); display.println( "Khz"); display.display(); //すべてのセットが表示を更新するとき} if(set_frequency_hz == 0 && set_frequency_khz == 0 && set_frequency_mhz == 1){display.setCursor(90、20); display.println( "Mhz"); display.display(); //すべてのセットが表示を更新したら}
次に、ボタンプレス変数をチェックして、出力オン/出力オフをOLEDに出力します。この場合も、OLEDモジュールのためにこれを行う必要があります。
if(btn_state){display.setTextSize(1); display.setCursor(65、45); display.print( "出力オン"); //出力をディスプレイに出力しますdisplay.display(); display.setTextSize(2); } else {display.setTextSize(1); display.setCursor(65、45); display.print( "出力オフ"); //出力をディスプレイに出力しますdisplay.display(); display.setTextSize(2); }
これでコーディングプロセスは終了です。この時点で混乱している場合は、コード内のコメントを確認してさらに理解を深めることができます。
AD9833ベースの関数発生器のテスト
回路をテストするには、上記の設定を使用します。ご覧のとおり、12V DC電源アダプターをDCバレルジャックに接続し、Hantekオシロスコープを回路の出力に接続しました。また、出力周波数を視覚化して測定するために、オシロスコープをラップトップに接続しました。
これが完了したら、ロータリーエンコーダーを使用して出力周波数を5Khzに設定し、出力正弦波をテストします。これは、出力で5Khzの正弦波であることを確認します。
次に、出力波形を三角波に変更しましたが、周波数は同じままで、出力波形を以下に示します。
次に、出力を方形波に変更して観測したところ、完全な方形波でした。
また、周波数範囲を変更して出力をテストしたところ、うまく機能していました。
さらなる機能強化
この回路は概念実証にすぎず、さらに拡張する必要があります。まず、出力用に高品質のPCBと高品質のBNCコネクタが必要です。そうしないと、より高い周波数を得ることができません。モジュールの振幅は非常に小さいので、それを強化するには、出力電圧を増幅するためのいくつかのオペアンプ回路が必要です。出力振幅を変化させるためにポテンショメータを接続することができます。信号をオフセットするためのスイッチを接続できます。これも必須の機能です。さらに、コードは少しバグがあるため、多くの改善が必要です。最後に、OLEDディスプレイを変更する必要があります。そうしないと、簡単に理解できるコードを書くことができません。
これでこのチュートリアルは終了です。この記事が気に入って、何か新しいことを学んだことを願っています。記事に関して質問がある場合は、下のコメントセクションに残すか、ElectronicsForumを使用してください。