この記事で扱っていること
- ソフトウェアタイミングでDCとACの出力を切り替える方法
を紹介しています。
注意:すべてのエラーを確認しているわけではないので、記事の内容を実装する際には自己責任でお願いします。また、エラー配線は適当な部分があるので適宜修正してください。
LabVIEWでDAQ製品を操作する際に、まず決めなければいけないのは、タイミングの指定方法です。
大きく分けて、ソフトウェアタイミングとハードウェアタイミングがあります。
タイミングをより厳密に守り指定した条件で信号を出力させるにはハードウェアタイミングで制御する必要があります。
一方で、そもそもソフトウェアタイミングしか使えない(ハードウェアタイミングに対応していない)、あるいは出力信号の厳密さにはあまり重きを置かず、何となくそれっぽいACの信号(正弦波っぽいのや矩形波、ノコギリ波など)が出せればいいという場合もあるかもしれません。
NIサンプルファインダの中に「オンデマンド出力」という名称でサンプルはあるのですが、基本的にはDC信号対応であり、信号の大きさを切り替えるしかできません。ソフトウェアタイミングなのでAC信号には不向き、という前提があるからだと思います。
しかしやってやれないことはない、ということでプログラムを止めなくてもDCとACを切り替えられるような仕組みを作ってみました。
先に断っておきますが、厳密な指定信号は出せないのでご注意ください。(例えば10 Hzの正弦波を出すと指定しても実際に出る信号は形としては正弦波状ですが10 Hzの波とはなりません。)
なお、実機を使った測定にはUSB-6361を使用しています。
どんな結果になるか
各チャンネルでDCにするかACにするかは専用のクラスタを設けていて、複数チャンネルを使用することを想定してこのクラスタを配列としています。
プログラムの構造は先に使用するチャンネルの数の分だけ配列を用意しておきます。
実行後、DCとACの切替はブールで、そして値の切替はDCに対してはノブ制御器で、ACに対しては信号の種類と周波数および振幅を数値制御器で指定します。
実際に信号を測定したときの結果を見るとわかりますが、やはり厳密には指定した周波数の信号を出せません。手軽さを売りにしています。
以下は一例ですが、DC出力の方はともかく、ACの方は周波数20 Hzの正弦波を出しているつもりなので0.1秒では正弦波が2周期分見えるはずなのですが実際の測定信号では2周期入っていません。もう少し「まし」にすることはできますが、それについては以下のプログラムの構造で紹介しています。
プログラムの構造
DC信号については問題ないと思います。ただ単に出力したい値さえDAQmx書き込みの関数に渡せばいいので単純です。
問題はAC信号の方で、例えば正弦波としたときにはある正弦波データのかたまり(配列)から一ループごとに一点、書き込みの関数に渡し、次のループでもう一点渡すということを繰り返します。ではこの正弦波の信号をどう管理するかということについてはより良い組み方があるかもしれませんが、機能的グローバル変数として値を保持させる方法をとりました。
以下がプログラム全体の構造です。
まず、Whileループに入る前の部分では機能的グローバル変数の初期化を行っています。この機能的グローバル変数は、後で(メインVIのWhileループ内で)出力データをチャンネル毎に保存する役割を持ちます。
機能的グローバル変数が保持する情報は、DCのデータとACのデータがあり、これらをチャンネルの数だけ配列で保持しておきます。例えばプログラムで2チャンネル使用する場合には、DCのデータの配列(要素2つ)とACの波形データの配列(要素2つ)を持ち、メインVIの方で設定の変更があった場合にこれらの配列の要素を切り替えていきます。
メインVIの方ではWhileループの中身で設定の変更(フロントパネルのチャンネル毎設定の値)が前のループと同じか違うかでケースを分け、機能的グローバル変数の操作を変える、ということをForループの自動指標付けによってチャンネル毎に行っています。
各ケースの中身は次のようです。
なお、外枠FALSEで内枠ACの場合(上の図でいう左下)には、基本関数発生器関数を使用しています。
外枠がFALSEの場合、新しいデータを機能的グローバル変数に入れ、これを保持します。保持する仕組みは、機能的グローバル変数の初期化で用意していた波形データ配列あるいは数値データ配列を使用しています。実際に出すデータはACの場合、波形データのYの要素0を出すようにしています。
もしメインVIの方で設定値が変わらなかった場合には、機能的グローバル変数はnewdataステートではなくsamedataステートを実行します。ここでは、newdataステートで保持されたデータを扱うのですが、ACの場合、保持している波形データのYの配列の要素を変える必要があります。
そのため、機能的グローバル変数内で、「前に呼び出されたときに使用していたYの配列の要素番号は何か」を記憶しておく必要があり、それをch指標配列が担っています。
ここで注意するのは、上の図で使用されているIn Place要素ストラクチャの中身の割り算の部分です。ここでは1000で割った余りを使用していますが、この1000という数字の根拠は、メインVIの基本関数発生器のサンプリング情報入力(クラスタ)の値と一致させる必要があります。
なぜなら、基本関数発生器のサンプリング情報のサンプル数(#s)は、この基本関数発生器の波形出力のYの配列の要素数を指定しており、この数と一致させていないと機能的グローバル変数で全てのYの値を順々に取り出すことができなくなるためです。
なお、機能的グローバル変数のフロントパネルとコネクタペーンは以下の構造としました。
さて、プログラムの仕組みとしては以上なのですが、なるべく波形を正しく出したいという場合に大切なのが「待機関数」あるいは「次のミリ秒倍数まで待機関数」の役割です。
実はこのプログラム、メインVIのWhileループに待機関数や次のミリ秒倍数まで待機関数を置いていないと、基本的にまったく意図した周波数の信号を出しません。
これは、Whileループの速さがまちまちであるために、一定リズムで信号を出せないのが原因と考えられます。そのため、待機関数や次のミリ秒倍数まで待機関数を使用することをオススメします。
ただその場合、これらの関数にはどのような値を入れればいいのかという問題が出てきます。それを知るには、Whileループがどれくらいの速度で回っているかを知る必要があります。
そこでWhileループの速度を計るためにティックカウントを使用します。
私が試した際にはループ周期は大体の時間が1ミリ秒で時たま2ミリ秒となっていましたが、これはPCの負荷状況に依ると思います。ループを安定的に回すのであれば、このループ周期の最大値を待機関数や次のミリ秒倍数まで待機関数に配線しますが、ほとんどの場合1ミリ秒ということであれば1をこれらの関数に配線してもいいかもしれません。
いずれにしろ、Windowsの環境で厳密に制御するのは無理と割り切って、妥協できる値を設定するのが望ましいと言えそうです。
ちなみに、待機関数と次のミリ秒倍数まで待機関数のどちらがいいかという点に関しては
- 待機関数:波形が安定しやすいが、AC出力について望みの周波数にはなれない
- 次のミリ秒倍数まで待機関数:波形が不安定だが、AC出力について望みの周波数により近い
という結果となりました。次のミリ秒倍数まで待機関数が、周波数を維持しやすいというのはこの関数自体の性質を考えれば納得ですね。もし両者の違いがよくわからないという場合には、以下の記事も参考にしてみてください。
今回のサンプルでどうなるか、参考までに両者の結果を載せておきます。
あとは、これらタイミング系の関数に配線した値と、基本関数発生器のサンプリング情報のサンプリングレート(fs)も対応させることが必要です。ここがあっていないとデータの整合性がとれなくなるからです(波形は1000 Hzで変化しているはずなのに実際の出力は500 Hzだととびとびになる、といった不都合が生じるためです)。
別の組み方で改良されないか
上記のようにプログラムを組んだ時に、形の上ではある程度なめらかに信号を出せそうなのにどうして狙った周波数の信号にならないのか、Whileループの回る速度が遅すぎるのが原因と考えました。
では、他の組み方をすることでより早くなるのではないかと思い、二つの方法を試しました。先に結論から言うと、速さは変わりません。なぜなら、ボトルネックはForループ部分ではなく、DAQmx書き込みの部分にあったためです。この関数を使う以上、他の部分を工夫しても早くすることはできません。
工夫の一つ目としては、Forループの中や機能的グローバル変数で扱うデータの種類を変えるということです。上の例では波形データとして扱っていましたが、ソフトウェアタイミングで扱う場合には「余計な」データも含まれている波形データより、そのまま1次元の数値配列を扱ったほうが処理速度が向上するのではないかという狙いです。
やってみるとわかりますが、波形データを使用した場合と変わりません。
工夫の二つ目は、データを製造するForループと、DAQmx書き込みの処理を分けるために、生産者・消費者のパターンにすることです。この方法を試したときに、データの製造を行うForループはかなり早く回るのに対してDAQmx書き込みの方がかなり時間がかかることに気づきました。
なお、Nチャンネル1サンプルではなく、NチャンネルNサンプルにするとより長く時間がかかるのでこれもプログラム改善にはいたりませんでした。
結局、どのような形での実装をしたとしても所詮はソフトウェアタイミング、厳密な波形を出すことはできませんでした。
ただ、あまり信号の厳密さにはこだわらないという場合、そしてそもそもハードウェア的にソフトウェアタイミングは使用できない場合には参考にして頂けると嬉しいです。
ここまで読んでいただきありがとうございました。
コメント