Girino-高速Arduinoオシロスコープ

私は物理学者であり、この分野で働くことの最も素晴らしい部分は、自分の楽器を作ることができることです。 この考え方で、自家製のArduinoオシロスコープを構築することにしました。 このインストラクタブルは、マイクロコントローラーとデータ収集について少し教えることを目的として書かれました。 これは極端なプロジェクトです。Arduinoからできる限り高速に絞り出したいと思ったからです。他のArduinoオシロスコープはこれほど高速ではありませんでした。

少し前に、私はArduinoプロジェクトに取り組んでいて、出力信号が仕様に準拠しているかどうかを確認する必要がありました。 そのため、すでに実装されているArduinoオシロスコープを探すためにインターネットで時間を費やしましたが、見つけたものが好きではありませんでした。 私が見つけたプロジェクトのほとんどは、Processingで記述されたコンピューターのグラフィカルユーザーインターフェイスと、非常にシンプルなarduinoスケッチで構成されていました。 スケッチは次のようなものでした:void setup(){

Serial.begin(9600);

}

void loop(){

int val = analogRead(ANALOG_IN);

Serial.println(val);

}このアプローチは間違っていないし、だれにもin辱したくありませんが、これは私にとって遅すぎます。 シリアルポートは低速であり、analogRead()のすべての結果を送信することはボトルネックです。

私はしばらくの間、波形デジタイザを研究してきましたが、それらがどのように機能するかをかなりよく知っているので、それらからインスピレーションを得ました。 これらは、私が作成したいオシロスコープの出発点でした。

  • 着信信号は、それを保存するためにArduinoから分離する必要があります。
  • 信号のオフセットにより、負の信号を見ることができます。
  • データはバッファリングする必要があります。
  • 信号をキャッチするには、ハードウェアトリガーが必要です。
  • 循環バッファーは、トリガーの前に信号の形状を与えることができます(この点については後ほど説明します)。
  • 標準のものよりも低いレバー機能を使用すると、プログラムがより速く実行されます。

Arduinoのスケッチは、私が作成した回路の回路図とともにこのステップに添付されています。

私が思いついたギリノという名前は、イタリア語の軽薄なしゃれです。 Giro回転を意味し、接尾辞-inoを追加すると小さな回転が得られますが、 Girinoオタマジャクシも意味します。 このようにして、私は名前とマスコットを得ました。

ステップ1:免責事項

この指示書の作者は有効性の保証も保証もしません

あなたが何をしているかわからない場合、電子機器は危険な場合があり、著者はここにある情報の妥当性を保証できません。 これは専門家のアドバイスではなく、この説明可能なもので書かれたものはすべて、不正確、誤解を招く、危険、または間違っている可能性があります。 独立した検証なしに、ここで見つかった情報に依存しないでください。

情報を確認し、自分自身または誰かを害にさらしたり、損害にさらしたりしていないことを再確認してください。 私は責任を負いません。 このプロジェクトを再現する場合は、適切な安全上の注意事項を自分で守る必要があります。

このガイドはご自身の責任で使用してください!

ステップ2:必要なもの

このプロジェクトに本当に必要なのは、ArduinoボードとATMega328Pのデータシートです。
データシートは、マイクロコントローラの動作方法を示しているものであり、より低い制御のレバーが必要な場合は、それを保持することが非常に重要です。

データシートはここにあります://www.atmel.com/Images/doc8271.pdf

Arduinoに追加したハードウェアは一部必要です。その目的は、ADCの信号を形成し、トリガーに電圧レベルを提供することだけです。 必要に応じて、信号をArduinoに直接送信し、分圧器で定義された電圧リファレンス、またはArduino自体で指定された3.3 Vを使用することもできます。

ステップ3:デバッグ出力

私は通常、多くのデバッグ出力をプログラムに入れます。何が起こったのかを追跡したいからです。 Arduinoの問題は、書き込む標準出力がないことです。 シリアルポートを標準出力として使用することにしました。

ただし、このアプローチは常に機能するわけではないことに注意してください! シリアルポートへの書き込みは実行に時間がかかり、時間に応じた適切なルーチンの間に劇的に変化する可能性があるためです。

通常、プリプロセッサマクロ内でデバッグ出力を定義します。したがって、デバッグが無効になっていると、プログラムからデバッグ出力が消え、実行速度が低下することはありません。
  • dprint(x); -次のようなシリアルポートに書き込みます:#x:123
  • dshow( "Some string"); -文字列を書き込みます

これが定義です:

#if DEBUG == 1
#define dprint(expression)Serial.print( "#"); Serial.print(#expression); Serial.print( ":"); Serial.println(expression)
#define dshow(expression)Serial.println(expression)
#そうしないと
#define dprint(expression)
#define dshow(expression)
#endif

ステップ4:レジスタビットの設定

高速にするために、Arduino IDEが提供する標準機能よりも低いレバー機能でマイクロコントローラー機能を操作する必要があります。 内部関数は、いくつかのレジスタを介して管理されます。レジスタは、それぞれが特定の何かを管理する8ビットのコレクションです。 ATMega328Pには8ビットアーキテクチャがあるため、各レジスタには8ビットが含まれています。

レジスタには、ADC設定レジスタAのADCSRAなど、その意味に応じてデータシートで指定された名前があります。また、レジスタの各意味のあるビットには、ADCSRAレジスタのADCイネーブルビットのADENなどの名前があります。

ビットを設定するには、バイナリ代数に通常のC構文を使用できますが、インターネット上で非常にきれいできれいなマクロがいくつか見つかりました。

//レジスタビットの設定とクリアの定義
#ifndef cbi
#define cbi(sfr、bit)(_SFR_BYTE(sfr)&=〜_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr、bit)(_SFR_BYTE(sfr)| = _BV(bit))
#endif

それらの使用は非常に簡単です。ADCのイネーブルビットを1に設定する場合は、次のように記述できます。

sbi(ADCSRA、ADEN);

一方、0に設定する場合( id est clear it)、次のように記述できます。

cbi(ADCSRA、ADEN);

ステップ5:割り込みとは

次のステップで見るように、このプロジェクトでは割り込みの使用が必要です。 割り込みは、メインループの実行を停止し 、それをいくつかの特別な機能に渡すようにマイクロコントローラーに指示する信号です。 画像は、プログラムの流れを示しています。

実行される関数は割り込みサービスルーチン (ISR)と呼ばれ、多かれ少なかれ単純な関数ですが、引数を取りません。

いくつかのパルスを数えるようなものの例を見てみましょう。 ATMega328Pには、信号が基準電圧を超えるとアクティブになる割り込みが関連付けられたアナログコンパレータがあります。 まず最初に、実行される関数を定義する必要があります。

ISR(ANALOG_COMP_vect)
{
counter ++;
}

これは本当に簡単です。命令ISR()は、次の関数が割り込みサービスルーチンであることをコンパイラに伝えるマクロです。 ANALOG_COMP_vectは割り込みベクターと呼ばれ、どの割り込みがそのルーチンに関連付けられているかをコンパイラーに伝えます。 この場合、それはアナログコンパレータ割り込みです。 そのため、コンパレータが参照よりも大きい信号を検出するたびに、マイクロコントローラにそのコードを実行するように指示します。この場合はestで変数をインクリメントします。

次のステップは、関連する割り込みを有効にすることです。 有効にするには、ACSR(アナログコンパレータ設定レジスタ)レジスタのACIE(アナログコンパレータ割り込みイネーブル)ビットを設定する必要があります。

sbi(ACSR、ACIE);

次のサイトでは、すべての割り込みベクターのリストを確認できます。
//www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

ステップ6:循環バッファーで継続的に取得する

循環バッファーを使用する概念は非常に単純です。

信号が見つかるまで継続的に集録し、デジタル化された信号をコンピューターに送信します。

このアプローチにより、トリガーイベントのにも着信信号の形状を設定できます。


わかりやすくするために、いくつかの図を用意しました。 以下のポイントは画像に関するものです。
  • 最初の画像では、 連続取得の意味を確認できます。 データを格納するバッファー、私の場合は1280スロットの配列を定義し、その後、バッファーにデータを入れてADC出力レジスター(ADCH)を連続して読み取り始めます。 バッファーの最後に到達すると、クリアせずに最初からやり直します。 円形に配列された配列を想像すると、私が言っていることは簡単にわかります。
  • 信号がしきい値を超えると、アナログコンパレータ割り込みがアクティブになります。 次に、 待機フェーズを開始します。このフェーズでは、信号を取得し続けますが、アナログコンパレータ割り込みから渡されたADCサイクルのカウントを保持します。
  • Nサイクル(N <1280)待機したとき、状況を凍結してADCサイクルを停止します。 そのため、信号の時間的形状のデジタル化で満たされたバッファになります。 これの大部分は、トリガーイベントの前にシェイプも持っていることです。なぜなら、それ以前に既に取得していたからです。
  • これで、単一のADC読み取りを送信する代わりに、バッファ全体をバイナリデータのブロックでシリアルポートに送信できます。 これにより、データを送信するのに必要なオーバーヘッドと、インターネットで見つけたスケッチのボトルネックが削減されました。

ステップ7:オシロスコープのトリガー

オシロスコープはディスプレイに信号を表示しますが、その上で私たち全員が同意しますが、どのようにそれを着実に表示し、画面を飛び回って表示しないのですか? 常に画面の同じ位置(または少なくともほとんどの時間)に信号を表示できる内部トリガーがあり、安定したプロットの錯覚を作り出します。

トリガーは、信号が通過したときにスイープをアクティブにするしきい値に関連付けられています。 掃引は、オシロスコープが信号を記録および表示するフェーズです。 掃引の後、別のフェーズが発生します。 ホールドオフで、オシロスコープは着信信号を拒否します。 ホールドオフ期間は、オシロスコープが信号を受信できないデッドタイムの一部と、ユーザーが選択可能な部分で構成できます。 デッドタイムは、画面に描画する必要がある、データをどこかに保存する必要があるなど、さまざまな理由で発生する可能性があります。

画像を見ると、何が起こっているかの感覚が得られます。
  1. 信号1はしきい値を超え、スイープをアクティブにします。
  2. 信号2は掃引時間内にあり、最初の信号で捕捉されます。
  3. ホールドオフ後、信号3は再びスイープをアクティブにします。
  4. 代わりに、信号4はホールドオフ領域内にあるため拒否されます。
ホールドオフフェーズの存在理由は、不要な信号がスイープ領域に入るのを防ぐことです。 この点を説明するのは少し長く、この説明可能な目的を免れます。

この物語の教訓は、私たちに必要なことです:
  1. 着信信号を比較できるしきい値レベル。
  2. マイクロコントローラに待機フェーズを開始するよう指示する信号(前の手順を参照)。
ポイント1にはいくつかの可能な解決策があります。
  • トリマーを使用して、手動で電圧レベルを設定できます。
  • ArduinoのPWMを使用して、ソフトウェアでレベルを設定できます。
  • Arduino自体が提供する3.3 Vを使用。
  • 内部bangap参照を使用すると、固定レベルを使用できます。
ポイント2には、適切なソリューションがあります。マイクロコントローラーの内部アナログコンパレーターの割り込みを使用できます。

ステップ8:ADCの仕組み

Arduinoマイクロコントローラーは、単一の10ビット逐次比較型ADCを備えています。 ADCの前に、異なるピンとソースからの信号をADCに送信できるアナログマルチプレクサがあります(一度に1つだけ)。

逐次近似ADCとは、ADCが変換を完了するのに13クロックサイクル(最初の変換に25クロックサイクル)かかることを意味します。 Arduinoのメインクロックから「計算」されるADC専用のクロック信号があります。 これは、ADCが少し遅く、マイクロコントローラーの他の部分のペースに追いつかないためです。 最大解像度を得るには、50 kHz〜200 kHzの入力クロック周波数が必要です。 10ビットより低い分解能が必要な場合、ADCへの入力クロック周波数を200 kHzより高くして、より高いサンプルレートを得ることができます。

しかし、どれほど高いレートを使用できますか? Open Music Labsには、ADCについて読むことをお勧めするガイドがいくつかあります。
  • //www.openmusiclabs.com/learning/digital/atmega-adc/
  • //www.openmusiclabs.com/learning/digital/atmega-adc/in-depth/
私の目的は高速なオシロスコープを取得することであるため、精度を8ビットに制限することにしました。 これにはいくつかのボーナスがあります:
  1. データバッファはより多くのデータを保存できます。
  2. データごとに6ビットのRAMを無駄にしません。
  3. ADCはより速く集録できます。
プリスケーラでは、ADCSRAレジスタのADPS0-1-2ビットを設定することにより、いくつかの要因で周波数を分割できます。 Open Music Labsの記事の精度のプロットを見ると、8ビットの精度の場合、周波数は最大1.5 MHzになることがわかります。 しかし、プリスケーラ係数を変更する機能により取得レートを変更できるため、オシロスコープのタイムスケールを変更するためにも使用できます。

出力レジスタには優れた機能があります。ADMUXレジスタのADLARビットを設定することにより、変換ビットの調整を決定できます。 0の場合、それらは適切に調整され、 逆も同様です (画像を参照)。 8ビットの精度が必要だったため、ADCHレジスタのみを読み取り、ADCLを無視できるように1に設定しました。

変換のたびにチャネルを切り替える必要がないように、入力チャネルを1つだけにすることにしました。

ADCの最後の1つは、それぞれが異なるトリガーソースを持つ異なる実行モードを備えていることです。
  • フリーランニングモード
  • アナログコンパレータ
  • 外部割り込み要求0
  • タイマー/カウンター0比較一致A
  • タイマー/カウンター0のオーバーフロー
  • タイマー/カウンター1比較一致B
  • タイマー/カウンター1のオーバーフロー
  • タイマー/カウンター1キャプチャイベント
フリーランニングモードに興味がありました。これは、ADCが入力を連続的に変換し、各変換の最後に割り込みをスローするモードです(関連ベクトル:ADC_vect)。

ステップ9:デジタル入力バッファー

Arduinoのアナログ入力ピンはデジタルI / Oピンとしても使用できるため、デジタル機能用の入力バッファーがあります。 アナログピンとして使用する場合は、この機能を無効にする必要があります。

アナログ信号をデジタルピンに送信すると、特に信号が2つの状態の境界付近にある場合、HIGH状態とLOW状態の間で切り替わります。 このトグルにより、ADC自体のような近くの回路にノイズが発生します(さらにエネルギー消費が増加します)。

デジタルバッファを無効にするには、DIDR0レジスタのADCnDビットを設定する必要があります。

sbi(DIDR0、ADC5D);
sbi(DIDR0、ADC4D);
sbi(DIDR0、ADC3D);
sbi(DIDR0、ADC2D);
sbi(DIDR0、ADC1D);
sbi(DIDR0、ADC0D);

ステップ10:ADCのセットアップ

スケッチでは、ADCが機能するすべてのパラメーターを設定する初期化関数を作成しました。 私はクリーンでコメント付きのコードを書く傾向があるので、ここで関数を過ぎます。 前の手順と、レジスタの意味についてのコメントを参照できます。 void initADC(void)
=(ADCPIN&0x07);

// ------------------------------------------------ ---------------------
// ADCSRA設定
// ------------------------------------------------ ---------------------
//このビットを1に書き込むと、ADCが有効になります。 ゼロに書き込むことにより、
// ADCはオフになります。 変換中にADCをオフにする
//進行状況。この変換を終了します。
cbi(ADCSRA、ADEN);
//シングル変換モードでは、このビットを1に書き込み、それぞれを開始します
// 変換。 フリーランニングモードでは、このビットを1に書き込むと、
//最初の変換。 ADSCが書き込まれた後の最初の変換
// ADCが有効になった後、またはADSCが同じ場所に書き込まれた場合
// ADCが有効になるまでの時間は、代わりに25 ADCクロックサイクルかかります
//通常の13。この最初の変換は、
// ADC。 変換が進行している限り、ADSCは1として読み取ります。
//変換が完了すると、ゼロに戻ります。 ゼロを書く
//このビットは効果がありません。
cbi(ADCSRA、ADSC);
//このビットが1に書き込まれると、ADCの自動トリガーは
//有効。 ADCは、ADCのポジティブエッジで変換を開始します
//選択されたトリガー信号。 トリガーソースは次の設定により選択されます
// ADCトリガー選択ビット、ADCSRBのADTS。
sbi(ADCSRA、ADATE);
//このビットが1に書き込まれ、SREGのIビットが設定されると、
// ADC変換完了割り込みがアクティブになります。
sbi(ADCSRA、ADIE);
//これらのビットは、システムクロック間の分周係数を決定します
//周波数とADCへの入力クロック。
// ADPS2 ADPS1 ADPS0除算係数
// 0 0 0 2
// 0 0 1 2
// 0 1 0 4
// 0 1 1 8
// 1 0 0 16
// 1 0 1 32
// 1 1 0 64
// 1 1 1 128
sbi(ADCSRA、ADPS2);
sbi(ADCSRA、ADPS1);
sbi(ADCSRA、ADPS0);

// ------------------------------------------------ ---------------------
// ADCSRB設定
// ------------------------------------------------ ---------------------
//このビットにロジック1が書き込まれ、ADCがオフになったとき
//(ADCSRAのADENはゼロ)、ADCマルチプレクサーは負を選択します
//アナログコンパレータへの入力。 このビットに論理ゼロが書き込まれると、
// AIN1は、アナログコンパレータの負の入力に適用されます。
cbi(ADCSRB、ACME);
// ADCSRAのADATEが1に書き込まれる場合、これらのビットの値
//どのソースがADC変換をトリガーするかを選択します。 ADATEが
//クリアすると、ADTS2:0の設定は無効になります。 変換は
//選択した割り込みフラグの立ち上がりエッジでトリガーされます。 注意
//クリアされたトリガーソースからトリガーへの切り替え
//設定されているソースは、トリガーでポジティブエッジを生成します
//シグナル。 ADCSRAのADENが設定されている場合、変換が開始されます。
//フリーランニングモード(ADTS [2:0] = 0)に切り替えても、
// ADC割り込みフラグが設定されている場合でも、イベントをトリガーします。
// ADTS2 ADTS1 ADTS0トリガーソース
// 0 0 0フリーランニングモード
// 0 0 1アナログコンパレータ
// 0 1 0外部割り込み要求0
// 0 1 1タイマー/カウンター0比較一致A
// 1 0 0タイマー/カウンター0オーバーフロー
// 1 0 1タイマー/カウンター1比較一致B
// 1 1 0タイマー/カウンター1オーバーフロー
// 1 1 1 1タイマー/カウンター1キャプチャイベント
cbi(ADCSRB、ADTS2);
cbi(ADCSRB、ADTS1);
cbi(ADCSRB、ADTS0);

// ------------------------------------------------ ---------------------
// DIDR0設定
// ------------------------------------------------ ---------------------
//このビットがロジック1に書き込まれると、デジタル入力バッファは
//対応するADCピンは無効になります。 対応するPINレジスタ
//このビットが設定されると、ビットは常にゼロとして読み取られます。 アナログ
//信号はADC5..0ピンとこれからのデジタル入力に適用されます
//ピンは必要ありません。このビットは、ロジック1を記述して減らす必要があります
//デジタル入力バッファの消費電力。
// ADCピンADC7およびADC6にはデジタル入力バッファがないことに注意してください。
//したがって、デジタル入力無効ビットは必要ありません。
sbi(DIDR0、ADC5D);
sbi(DIDR0、ADC4D);
sbi(DIDR0、ADC3D);
sbi(DIDR0、ADC2D);
sbi(DIDR0、ADC1D);
sbi(DIDR0、ADC0D);

ステップ11:アナログコンパレータの動作方法

アナログコンパレータはマイクロコントローラの内部モジュールであり、正ピン(デジタルピン6)と負ピン(デジタルピン7)の入力値を比較します。 正ピンの電圧が負ピンAIN1の電圧よりも高い場合、アナログコンパレータはACSRレジスタのACOビットに1を出力します。

オプションで、コンパレータは、アナログコンパレータ専用の割り込みをトリガーできます。 関連するベクトルはANALOG_COMP_vectです。

立ち上がりエッジ、立ち下がりエッジ、または状態のトグルで割り込みを起動するように設定することもできます。

アナログコンパレータは、入力信号をピン6に接続するトリガーに必要なものです。ピン7のしきい値レベルが残ります。

ステップ12:アナログコンパレータのセットアップ

スケッチでは、機能するアナログコンパレータのすべてのパラメータを設定する別の初期化関数を作成しました。 ルーチンの最後にあるように、ADCデジタルバッファに関する同じ問題がアナログコンパレータにも当てはまります。

void initAnalogComparator(void)
{
// ------------------------------------------------ ---------------------
// ACSR設定
// ------------------------------------------------ ---------------------
//このビットがロジック1に書き込まれると、アナログへの電力
//コンパレータはオフになります。 このビットはいつでもオンに設定できます
//アナログコンパレータをオフにします。 これにより、電力消費が削減されます
//アクティブおよびアイドルモード。 ACDビットを変更すると、アナログ
//コンパレータ割り込みは、ACIEビットをクリアして無効にする必要があります
// ACSR。 そうしないと、ビットが変更されたときに割り込みが発生する可能性があります。
cbi(ACSR、ACD);
//このビットが設定されると、固定バンドギャップ基準電圧が
//アナログコンパレータへの正の入力。 このビットがクリアされると、
// AIN0は、アナログコンパレータの正の入力に適用されます。 いつ
//バンドギャップリファレンスは、アナログコンパレータへの入力として使用されます。
//電圧が安定するまで一定の時間がかかります。 そうでない場合
//安定したため、最初の変換で誤った値が与えられる場合があります。
cbi(ACSR、ACBG);
// ACIEビットがロジック1に書き込まれ、Iビットがステータスに書き込まれたとき
//レジスタが設定され、アナログコンパレータ割り込みがアクティブになります。
//ロジック0を書き込むと、割り込みは無効になります。
cbi(ACSR、ACIE);
//ロジック1を書き込むと、このビットはインプットキャプチャ機能を有効にします
//アナログコンパレータによってトリガーされるTimer / Counter1内。 の
//この場合、コンパレータ出力は入力に直接接続されます
//フロントエンドロジックをキャプチャし、コンパレータにノイズを利用させます
// Timer / Counter1入力のキャンセラーおよびエッジ選択機能
//割り込みをキャプチャします。 ロジック0を書き込むと、
//アナログコンパレータとインプットキャプチャ機能が存在します。 に
//コンパレータにTimer / Counter1入力キャプチャをトリガーさせます
//割り込み、タイマー割り込みマスクレジスタのICIE1ビット
//(TIMSK1)を設定する必要があります。
cbi(ACSR、ACIC);
//これらのビットは、アナログをトリガーするコンパレータイベントを決定します
//コンパレータ割り込み。
// ACIS1 ACIS0モード
// 0 0トグル
// 0 1予約済み
// 1 0立ち下がりエッジ
// 1 1立ち上がりエッジ
sbi(ACSR、ACIS1);
sbi(ACSR、ACIS0);

// ------------------------------------------------ ---------------------
// DIDR1設定
// ------------------------------------------------ ---------------------
//このビットがロジック1に書き込まれると、デジタル入力バッファは
// AIN1 / 0ピンは無効です。 対応するPINレジスタビットは
//このビットが設定されている場合、常にゼロとして読み取られます。 アナログ信号が
// AIN1 / 0ピンに適用され、このピンからのデジタル入力は
//必要です。このビットは、電力を削減するためにロジック1で記述する必要があります
//デジタル入力バッファの消費。
sbi(DIDR1、AIN1D);
sbi(DIDR1、AIN0D);
}

ステップ13:しきい値

トリガーについて述べたことを思い出して、しきい値に対して次の2つのソリューションを実装できます。
  • トリマーを使用して、手動で電圧レベルを設定できます。
  • ArduinoのPWMを使用して、ソフトウェアでレベルを設定できます。
画像では、両方のパスでのしきい値のハードウェア実装を見ることができます。

手動選択の場合、+ 5 VとGNDの間に配置されたマルチターンポテンショメーターで十分です。

ソフトウェアの選択には、ArduinoからのPWM信号をフィルタリングするローパスフィルターが必要です。 PWM信号(これについては後ほど説明します)は、周波数は一定ですがパルス幅が可変の方形信号です。 この変動性は、ローパスフィルターで抽出できる信号の可変平均値をもたらします。 フィルターの適切なカットオフ周波数は、PWM周波数の約100分の1であり、約560 Hzを選択しました。

2つのスレッショルドソースの後に、ジャンパーで必要なソースを選択できるピンをいくつか挿入しました。 選択後、Arduinoピンからソースを切り離すエミッターフォロワーも追加しました。

ステップ14:パルス幅変調の仕組み

前述のように、パルス幅変調(PWM)信号は、周波数は固定ですが幅が可変の方形信号です。 画像に例があります。 各行には、 デューティサイクルの異なるこのような信号の1つがあります (信号がHighである期間部分を識別します )。 一定期間の平均信号を取得すると、信号の最大値に関するデューティサイクルに対応する赤い線が得られます。

前のステップで見たように、電子的に「信号の平均を取る」は「ローパスフィルタに渡す」に変換できます。

ArduinoはどのようにPWM信号を生成しますか? PWMについての非常に良いチュートリアルがここにあります:
//arduino.cc/en/Tutorial/SecretsOfArduinoPWM
このプロジェクトに必要なポイントのみが表示されます。

ATMega328Pには、PWM信号を生成するために使用できる3つのタイマーがあり、それぞれに使用できる特性が異なります。 各タイマーには、信号デューティサイクルの設定に使用される出力比較レジスタA / B (OCRnx)と呼ばれる2つのレジスタが対応しています。

ADCに関しては、メインクロックを減速してPWM周波数を正確に制御するプリスケーラ(画像を参照)があります。 減速したクロックは、 タイマー/カウンターレジスタ (TCNTn)をインクリメントするカウンターに供給されます。 このレジスタは、OCRnxと連続的に比較され、等しい場合、出力ピンでパルスを生成する波形ジェネレータに信号が送信されます。 そのため、OCRnxレジスタを何らかの値に設定して、信号の平均値を変更することが重要です。

5 V信号(最大)が必要な場合は、100%のデューティサイクルまたはOCRnxで255(8ビット数の最大)を設定する必要があり、0.5 V信号が必要な場合は、10%のデューティサイクルを設定する必要がありますまたはOCRnxに25。

クロックは、新しいパルスの開始から開始する前にTCNTnレジスタを満たす必要があるため、PWMの出力周波数は次のとおりです。

f =(メインクロック)/プリスケーラ/(TCNTn最大)

プリスケーラなしのタイマー0および2(8ビット)の例:16 MHz / 256 = 62.5 KHz、タイマー1(16ビット)の場合は16 MHz / 65536 = 244 Hz

タイマー番号2を使用することにしました。
  • タイマー0は、Arduino IDEによってmillis()などの機能のために内部的に使用されます。
  • タイマー1は16ビットタイマーであるため、出力周波数が遅すぎます。

ATMega328Pにはさまざまな種類のタイマーの動作モードがありますが、私が欲しかったのは、可能な最大出力周波数を得るための事前スケーリングのない高速PWMモードでした。

ステップ15:PWMのセットアップ

スケッチでは、タイマー機能のすべてのパラメーターをセットアップし、いくつかのピンを初期化する別の初期化関数を作成しました。 void initPins(void)
{
// ------------------------------------------------ ---------------------
// TCCR2A設定
// ------------------------------------------------ ---------------------
//これらのビットは、出力比較ピン(OC2A)の動作を制御します。 1つまたは
//両方のCOM2A1:0ビットが設定され、OC2A出力がオーバーライドします
//接続先のI / Oピンの通常のポート機能。
//ただし、データ方向レジスタ(DDR)ビット
// OC2Aピンに対応するには、有効にするために設定する必要があります
//出力ドライバー。
// OC2Aがピンに接続されている場合、COM2A1:0ビットの機能
// WGM22:0ビットの設定に依存します。
//
//高速PWMモード
// COM2A1 COM2A0
// 0 0通常のポート操作、OC2Aは切断されました。
// 0 1 WGM22 = 0:通常のポート操作、OC0A切断。
// WGM22 = 1:比較一致でOC2Aを切り替えます。
// 1 0比較一致でOC2Aをクリアし、OC2AをBOTTOMに設定します
// 1 1比較一致でOC2Aをクリア、BOTTOMでOC2Aをクリア
cbi(TCCR2A、COM2A1);
cbi(TCCR2A、COM2A0);
sbi(TCCR2A、COM2B1);
cbi(TCCR2A、COM2B0);

// TCCR2BレジスタにあるWGM22ビットと組み合わせて、これらのビット
//最大のソースであるカウンタのカウントシーケンスを制御します
//(TOP)カウンタ値、および使用する波形生成のタイプ
//タイマー/カウンターユニットでサポートされる動作モードは次のとおりです。
//-通常モード(カウンター)、
//-比較一致(CTC)モードでタイマーをクリア、
//-2種類のパルス幅変調(PWM)モード。
//
//モードWGM22 WGM21 WGM20操作TOP
// 0 0 0 0通常0xFF
// 1 0 0 1 PWM 0xFF
// 2 0 1 0 CTC OCRA
// 3 0 1 1高速PWM 0xFF
// 4 1 0 0予約済み-
// 5 1 0 1 PWM OCRA
// 6 1 1 0予約済み-
// 7 1 1 1高速PWM OCRA
cbi(TCCR2B、WGM22);
sbi(TCCR2A、WGM21);
sbi(TCCR2A、WGM20);

// ------------------------------------------------ ---------------------
// TCCR2B設定
// ------------------------------------------------ ---------------------
// WGMビットが非PWMを指定している場合にのみ、FOC2Aビットがアクティブになります
//モード。
//ただし、将来のデバイスとの互換性を確保するために、このビット
// PWMで動作しているときにTCCR2Bが書き込まれる場合、ゼロに設定する必要があります
//モード。 FOC2Aビットに論理1を書き込むと、即時
//波形生成ユニットで比較一致が強制されます。 OC2A
//出力はそのCOM2A1:0ビット設定に従って変更されます。 ご了承ください
// FOC2Aビットはストロボとして実装されます。 したがって、それは値です
// COM2A1:0ビットに存在し、その効果を決定します
//強制比較。
// FOC2Aストローブは割り込みを生成せず、クリアもしません
// OCR2AをTOPとして使用するCTCモードのタイマー。
// FOC2Aビットは常にゼロとして読み取られます。
cbi(TCCR2B、FOC2A);
cbi(TCCR2B、FOC2B);

// 3つのクロック選択ビットは、使用するクロックソースを選択します
//タイマー/カウンター。
// CS22 CS21 CS20プリスケーラー
// 0 0 0クロックソースなし(タイマー/カウンターが停止)。
// 0 0 1プリスケーリングなし
// 0 1 0 8
// 0 1 1 32
// 1 0 0 64
// 1 0 1 128
// 1 1 0 256
// 1 1 1 1024
cbi(TCCR2B、CS22);
cbi(TCCR2B、CS21);
sbi(TCCR2B、CS20);

pinMode(errorPin、OUTPUT);
pinMode(thresholdPin、OUTPUT);

analogWrite(thresholdPin、127);
}

ステップ16:揮発性変数

どこを覚えているかわかりませんが、ISR内で変更される変数はvolatileとして宣言する必要があることを読みました。

揮発性変数は、実行中のプログラムがそれらを変更しなくても、時間の経過とともに変化する可能性のある変数です。 一部の外部介入の値を変更できるArduinoレジスタと同じです。

なぜコンパイラはそのような変数について知りたいのですか? これは、コンパイラが常に記述したコードを最適化して、コードを高速化し、意味を変えないように少し修正するためです。 変数がそれ自体で変更された場合、コンパイラーは、たとえばループの実行中に変数が変更されることはないように見え、それを無視できます。 一方、変数の値を変更することが重要になる場合があります。 そのため、volatile変数を宣言すると、コンパイラーはそれらに関するコードを変更できなくなります。

詳細については、ウィキペディアのページを読むことをお勧めします://en.wikipedia.org/wiki/Volatile_variable

ステップ17:スケッチのカーネルを書く

最後に、プログラムのカーネルにアクセスしました!

前に見たように、継続的な取得が必要で、データを継続的に循環バッファに保存するADC割り込みサービスルーチンを作成しました。 stopIndexに等しいインデックスに到達するたびに停止します。 バッファは、モジュロ演算子を使用する循環として実装されます。

// ------------------------------------------------ -----------------------------
// ADC変換完了割り込み
// ------------------------------------------------ -----------------------------
ISR(ADC_vect)
{
// ADCLが読み取られると、ADCデータレジスタはADCHまで更新されません
//読み取られます。 その結果、結果が調整されたままで、それ以上なら
// 8ビットの精度が必要な場合、ADCHを読み取るのに十分です。
//それ以外の場合、ADCLを最初に読み取り、次にADCHを読み取る必要があります。
ADCBuffer [ADCCounter] = ADCH;

ADCCounter =(ADCCounter + 1)%ADCBUFFERSIZE;

if(待つ)
{
if(stopIndex == ADCCounter)
{
//凍結状況
// ADCを無効にして、フリーランニングコンバージョンモードを停止します
cbi(ADCSRA、ADEN);

freeze = true;
}
}
}

アナログコンパレータ割り込みサービスルーチン(信号がしきい値を超えると呼び出されます)が無効になり、ADC ISRに待機フェーズを開始してstopIndexを設定するように指示します。

// ------------------------------------------------ -----------------------------
//アナログコンパレータ割り込み
// ------------------------------------------------ -----------------------------
ISR(ANALOG_COMP_vect)
{
//アナログコンパレータ割り込みを無効にします
cbi(ACSR、ACIE);

// errorPinをオンにします
// digitalWrite(errorPin、HIGH);
sbi(PORTB、PORTB5);

wait = true;
stopIndex =(ADCCounter + waitDuration)%ADCBUFFERSIZE;
}


すべての接地の後、これは本当に簡単でした!

ステップ18:着信信号の形成

今すぐハードウェアを見てみましょう。 回路は複雑に見えるかもしれませんが、本当に簡単です。
  • 入力に1MΩの抵抗があり、信号に質量基準を与え、高インピーダンス入力を備えています。 高インピーダンスは、低インピーダンスの回路に接続した場合に開回路を「シミュレート」するため、Girinoの存在は測定する回路に過度に影響しません。
  • 抵抗器の後には、信号を分離し、次の電子機器を保護するためのエミッターフォロワーがあります。
  • 分圧器で2.5 Vレベルを生成する簡単なオフセットがあります。 コンデンサに取り付けて安定させます。
  • 着信信号とオフセットを合計する非反転加算増幅器があります。 Arduino ADCは0 Vから5 Vの間の信号しか見ることができないため、この手法を使用したのは負の信号も見たいからです。
  • 合計アンプの後、別のエミッターフォロワーがあります。
  • ジャンパーにより、信号にオフセットを供給するかどうかを決定できます。
私が使用するつもりだったオペアンプは、0 V〜5 Vで動作することができるLM324でしたが、たとえば-12 V〜12 Vでも動作します。これにより、電源の可能性が広がります。 また、LM324よりも高速ですが、デュアル電源が必要なTL084を試しました。 どちらも同じピン配列であるため、回路を変更せずに変更できます。

ステップ19:バイパスコンデンサ

バイパスコンデンサは、集積回路(IC)の電源をフィルタリングするために使用されるコンデンサであり、ICの調整ピンのできるだけ近くに配置する必要があります。 異なる周波数をフィルターで除去できるため、通常、1つはセラミック、もう1つは電解のカップルで使用されます。

ステップ20:電源

TL084には、LM324の単一電源に変換できるデュアル電源を使用しました。

画像では、+ 12 Vの場合は7812、-12 Vの場合は7912の電圧レギュレーターを使用していることがわかります。通常、コンデンサはレベルを安定させるために使用され、その値は推奨されるものです。データシート内。

電圧レギュレータは、安定した出力を提供するためにより高い入力を必要とするため、明らかに±12 Vを得るためには、入力に少なくとも約30 Vが必要です。 私はそのような電源を持っていなかったので、2つの15 V電源を直列で使用するというトリックを使用しました。 2つのうちの1つはArduino電源コネクタに接続され(したがって、Arduinoと私の回路の両方に給電します)、もう1つは回路に直接接続されます。

2番目の電源の+15 Vを最初の電源のGNDに接続してもエラーではありません! これが、 分離された電源で-15 Vを得る方法です。

Arduinoと2つの電源を持ち歩きたくない場合でも、Arduinoが提供する+5 Vを使用してそれらのジャンパーを変更(およびLM324を使用)することができます。

ステップ21:シールドコネクタの準備

Arduinoシールドを作成するために見つけることができるコネクタには常に悩まされます。なぜなら、それらには常に短すぎるピンがあり、使用するボードは片側でしかはんだ付けできないからです。 そこで、Arduinoにはんだ付けして挿入できるように、ピンを長くするためのちょっとしたトリックを作りました。

写真のように、ピンストリップをボードに挿入すると、ピンを押して、黒いプラスチックの片側だけにピンを置くことができます。 その後、Arduinoで挿入されるのと同じ側でそれらをはんだ付けできます。

ステップ22:はんだ付けとテスト

多くの試行錯誤を経たため、回路のすべてのはんだ付け手順を説明することはできません。 最終的には少し乱雑になりましたが、それほど悪くはありませんが、 それは本当に乱雑なので下側を見せません。

回路のすべての部分を詳細に説明したので、この段階で言うことはあまりありません。 友人が私に借りたオシロスコープでテストして、回路の各ポイントの信号を確認しました。 すべてがうまく機能しているようで、私はかなり満足しています。

着信信号のコネクタは、高エネルギー物理学から来ていない人にとっては少し奇妙に見えるかもしれません。これはLEMOコネクタです。 これは核信号用の標準コネクタであり、少なくともヨーロッパでは、米国ではほとんどBNCコネクタを見てきました。

ステップ23:信号をテストする

回路とデータ収集(DAQ)をテストするために、異なる長さの方形パルスを生成する簡単なスケッチで2つ目のArduinoを使用しました。 また、Girinoと通信してデータ系列を取得し、そのうちの1つをファイルに保存するように指示するPythonスクリプトを作成しました。
これらは両方ともこのステップに関連付けられています。

添付ファイル

  • TaraturaTempi.inoダウンロード
  • readgirino.pyダウンロード

ステップ24:時間校正

テスト信号を使用して、プロットの水平スケールを調整しました。 (生成されたために知られている)パルスの幅を測定し、測定されたパルス幅を既知の値に対してプロットすることにより、うまくいけば線形プロットが得られます。 プリスケーラ設定ごとにこれを行うと、すべての取得レートの時間校正ができます。

画像では、分析したすべてのデータを見ることができます。 「適合勾配」プロットは、各プリスケーラ設定でのシステムの実際の取得率を示すため、最も興味深いものです。 勾配は[ch / ms]数値として測定されましたが、これは[kHz]と同等であるため、勾配値は実際にはkHzまたはkS / s(キロサンプル/秒)です。 つまり、プリスケーラーを8に設定すると、次の取得率が得られます。

(154±2)kS / s

悪くないですか?

「フィットy切片」プロットから、システムの線形性の洞察を得ます。 長さがゼロの信号では長さがゼロのパルスに対応する必要があるため、すべてのy切片はゼロでなければなりません。 グラフでわかるように、それらはすべてゼロと互換性がありますが、18プリスケーラデータセットとは互換性がありません。 ただし、このデータセットは2つのデータしかなく、そのキャリブレーションが信頼できないため、最悪のデータセットです。

次に、各プリスケーラ設定の取得率を示す表があります。

プリスケーラー取得率[kS / s]
1289.74±0.04
6419.39±0.06
3237.3±0.6
1675.5±0.3
8153±2
引用されたエラーはGnuplotの適合エンジンからのものであり、私はそれらについて確信がありません。

また、事前スケーリングが半分になると、比率がほぼ2倍になることがわかります。これは逆比例則のように見えるので、レートの重みなし適合を試みました。 そこで、レートとプリスケーラ設定を単純な法則で適合させました

y = a / x

の値を取得しました

a = 1223

χ²= 3.14と4自由度の場合、これは95%の信頼レベルで法律が受け入れられることを意味します!

ステップ25:完了! (ほとんど)

この長い経験の終わりに、私はとても満足していると感じています。
  • マイクロコントローラー全般について多くのことを学びました。
  • Arduino ATMega328Pについてさらに多くのことを学びました。
  • 私は、すでに行われたものを使用するのではなく、何かを作成することによって、データ収集の実地体験をしました。
  • 私はそれほど悪くないアマチュアオシロスコープを実現しました。
このガイドが、それを読むすべての人に役立つことを願っています。 難しい方法(インターネットサーフィン、データシートの読み取り、多くの試行錯誤)ですべてを学んだので、非常に詳細に書きたいと思いました。

ステップ26:続行するには...

ただし、プロジェクトはまだ完了していない場合。 不足しているのは:
  1. 異なるアナログ信号でのテスト(アナログ信号発生器がありません);
  2. コンピューター側のグラフィカルユーザーインターフェイス。
ポイント1についてですが、近いうちに購入または構築する予定はないため、いつ完了するかはわかりません。

ポイント2では、状況は改善される可能性があります。 誰も私を助けてくれますか? 私はここで素晴らしいPythonオシロスコープを見つけました:
//www.phy.uct.ac.za/courses/python/examples/moreexamples.html#oscilloscope-and-spectrum-analyser
Girinoに合うように修正したいのですが、提案を受け入れています。

関連記事