2008.1.23.
石立 喬

Visual C++ 2005 Express Edition の易しい使い方(9)

――― リサージュ波形を画面に表示させる ―――


 リサージュ(Lissajous)波形は、電気技術者にとって、非常に馴染みの深い波形である。二つの周波数源の同期や位相を調整するのに便利で、オッシロスコープを学ぶ際に、必ずと言っても良いほど実験させられる。

リサージュ(Lissajous)波形とは
 リサージュ図形、リサージュ曲線などとも呼ばれることがある。フランスの物理学者Jules Antoine Lissajousが、1857年に発表したと言われ、その名前が付けられている。これは、直交するX軸とY軸のそれぞれに異なる正弦波を加えて得られる平面図形のことで、それぞれの正弦波の周波数、位相の違いなどによって、多様な曲線が描かれる。

リサージュ波形による周波数の測定(設定)
 オッシロスコープで、基準波を横軸に、被測定波を縦軸に入力すると、リサージュ図形が得られる。上下に描かれた山の数と、左右に描かれた山の数が、基準波と被測定波の周波数比となって現れるので、周波数の測定ができる。リサージュ波形を見ながら被測定波の周波数を調整することにより、周波数を設定することもできる。

リサージュ波形による位相の測定(設定)
 同様に、基準波を横軸に、それと同じ周波数の被測定波を縦軸に入力し、縦横が同振幅に表示されるように調節すると、二つの波の位相関係により、直線、円、楕円のリサージュ図形が得られる。位相差を0またはπ(逆相)にすると直線になり、2/πまたは-2/πにすると円になる。この原理により、位相の測定または設定ができる。

パソコン上でのリサージュ波形の作成方法
 画面上の座標を(x,y)としたとき、
  横軸入力波形 x = A・sin ωt
  縦軸入力波形 y = B・sin(ωt + φ)
で描いた図形がリサージュ波形である。
 ただし、ここで紹介する例では、A = B とし、tは時間で 0秒から1秒までとする。ωは角周波数で、ω = 2πfの関係がある。fは周波数で、1 Hzから10Hzまでとする。φは縦軸入力波形に付加する位相シフトで、-πラジアンからπラジアンまで変化させる。

プログラムの使用方法
1) プログラムを起動させると、横軸周波数 = 2Hz、縦軸周波数 = 3Hz、位相 = 0の初期設定になっており、その条件でのリサージュ波形が描かれる。
2) 横軸周波数と縦軸周波数は、コンボボックスによって設定可能で、変更すると、直ちにリサージュ波形が描かれる。
3) 縦軸周波数の位相は、スクロールバーで、0.01π単位で設定でき、これも変更と同時にリサージュ波形の描画が行われる。
4) 「ゆっくり」ボタンをクリックすると、その時の設定条件で、各種波形の再描画がゆっくり行われる。

プログラム
 注意点は次のとおり。スクロールバーの設定については、文末に別途示す。
1) 時間待ちをさせる Thread::Sleep(10); を使用するために、System::Threading を名前空間として追加する。Sleepのカッコ内は、msecで指定する。
2) 線を引くためには、g->DrawLine(Pan^,int,int,int,int); または  g->DrawLine(Pen^,float,float,float,float); を用いるのが原則である。g->DrawLine(Pan^,double,double,double,double);のように、すべての引数にdoubleを使用すると、オーバーロードできないと言われて、ビルドできない。ただし、g->DrawLine(Pan^,float,double,double,double);のように、引数の一部にfloatやintがある場合には、「doubleからfloatへの変換です。データが失われる可能性があります。」などの警告が出るが、ビルドできる。
3)コンボボックスでは、周波数を1Hzから10Hzまで、1Hz刻みで設定できる。これを、SelectedIndexの0から11に対応させているので、周波数は、SelectedIndex+1で得られる。

using namespace System::Threading;

Boolean slow_flag;

private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) {

   comboBox1->SelectedIndex=1;   //横軸入力波形の周波数を2Hzに初期設定
   comboBox2->SelectedIndex=2;   //縦軸入力波形の周波数を3Hzに初期設定
   slow_flag=false;

}

private: System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e) {

   Graphics^ g=e->Graphics;

   int X0=10,Y0=50;    //横軸入力波形用
   int X1=190,Y1=50;   //縦軸入力波形用
   int X2=20,Y2=230;   //リサージュ波形用
   int SIZE=16;       //最小目盛間隔

   int t;
   double time;
   double x,y,x_old,y_old;
   
   int freq_x=comboBox1->SelectedIndex+1;
   int freq_y=comboBox2->SelectedIndex+1;
   double omega_x=2.0*Math::PI*freq_x;
   double omega_y=2.0*Math::PI*freq_y;

   double phi1=(hScrollBar1->Value-100)/100.0;
   double phi=phi1*Math::PI;

   Pen^ pen1=gcnew Pen(Color::LightGreen,1);

   //位相シフトを表示する
   if(phi1==0.0)       label4->Text=" 0";
   else if(phi1==1.0)    label4->Text=" π";
   else if(phi1==-1.0)   label4->Text=" -π";
   else            label4->Text=String::Format("{0}π",phi1);

   //グラフの背景を黒にする
   g->FillRectangle(Brushes::Black,X0,Y0,10*SIZE,10*SIZE);
   g->FillRectangle(Brushes::Black,X1,Y1,10*SIZE,10*SIZE);
   g->FillRectangle(Brushes::Black,X2,Y2,20*SIZE,20*SIZE);

   //グラフに目盛を入れる
   for(int i=0;i<=10;i++){
      //横軸入力波形用
      g->DrawLine(Pens::LightGray,X0+SIZE*i,Y0,X0+SIZE*i,Y0+10*SIZE);
      g->DrawLine(Pens::LightGray,X0,Y0+SIZE*i,X0+10*SIZE,Y0+SIZE*i);
      //縦軸入力波形用
      g->DrawLine(Pens::LightGray,X1+SIZE*i,Y1,X1+SIZE*i,Y1+10*SIZE);
      g->DrawLine(Pens::LightGray,X1,Y1+SIZE*i,X1+10*SIZE,Y1+SIZE*i);
      //リサージュ波形用
      g->DrawLine(Pens::LightGray,X2+2*SIZE*i,Y2,X2+2*SIZE*i,Y2+20*SIZE);
      g->DrawLine(Pens::LightGray,X2,Y2+2*SIZE*i,X2+20*SIZE,Y2+2*SIZE*i);
   }

   //各種波形を描く
   for(t=0;t<=20*SIZE;t++){
      time=t/(20.0*SIZE);
      x=Math::Sin(omega_x*time);
      y=Math::Sin(omega_y*time+phi);
      if(t==0){
         x_old=x;
         y_old=y;
      }
      else{
         g->DrawLine(pen1,X0+t*0.5f-1,Y0+5*SIZE-4*SIZE*x_old,X0+t*0.5f,Y0+5*SIZE-4*SIZE*x);  //横軸入力波形
         g->DrawLine(pen1,X1+t*0.5f-1,Y1+5*SIZE-4*SIZE*y_old,X1+t*0.5f,Y1+5*SIZE-4*SIZE*y);  //縦軸入力波形
         g->DrawLine(pen1,(float)(X2+10*SIZE+8*SIZE*x_old),Y2+10*SIZE-8*SIZE*y_old,
                                    X2+10*SIZE+8*SIZE*x,Y2+10*SIZE-8*SIZE*y);  //リサージュ波形
         x_old=x;
         y_old=y;
      }
      if(slow_flag)   Thread::Sleep(10);   //10msecだけ待つ
   }
   slow_flag=false;

}

private: System::Void comboBox1_SelectedIndexChanged(System::Object^ sender,System::EventArgs^ e) {

   Invalidate();

}

private: System::Void comboBox2_SelectedIndexChanged(System::Object^ sender,System::EventArgs^ e) {

   Invalidate();

}

private: System::Void hScrollBar1_ValueChanged(System::Object^ sender,System::EventArgs^ e) {

   Invalidate();

}

private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {

   slow_flag=true;
   Invalidate();

}

得られた画面
 図1は、コンボボックスにより、横軸周波数を3Hzに、縦軸周波数を5Hzに設定し、スクロールバーで縦軸位相を0.51πに設定した場合のリサージュ波形を示す。


図1 画面の例、上の山が5個、横の山が3個ある


スクロールバーの設定方法
 スクロールバーを用いて-1から0を経て1まで、0.01刻みで変数pを可変にしたいとする。スクロールバーの値(Value)は、正注)の整数に限られるので、スクロールバーで0から200までを設定し、それから100を引いて、100.0で割ればよいと考える。
 すなわち、
   p=(hSCrollBar1->Value-100)/100.0;
とする。
 pの初期値を0にするには、スクロールバーのプロパティでValueを100にしておく。結局、hScrollBar1のプロパティで、
   LargeChange --- 10 (規定値のまま)
   Maximum ------- 209
   Minimum ------- 0
   SmallChange --- 1 (規定値のまま)
   Value --------- 100
とする。Maximumを200ではなく、209としたのは、つまみ部分の幅を加味したためである。

注:負に設定して -100から100までとして(Minimumを -100、Valueを0とする)ビルドし、実行することもできたが、不安定で、時々エラーが出ることがあったので、使わない方が良いと判断した。