#=============================================== # TkIV #=============================================== package TkIV; # TkPlotModuleの継承クラスとする use TkPlotModule; @ISA = qw(TkPlotModule); use lib 'd:/Programs/Perl/lib'; use lib 'd:/Programs/Perl/lib2.0'; use lib 'd:/Tsukamoto'; use lib '.'; use strict; use Utils; use MyTk::GraphFrameArray; use GraphData; use Device::Device; my $Port = "GPIB27"; my $ModuleName = "Keithley6517A"; #my $Port = "Dummy"; #my $ModuleName = "Keithley6517A_Dummy"; #============================================================ # コンストラクタ、デストラクタ(お約束) #============================================================ sub new { my ($module, $app, $canvas) = @_; my $this = {}; bless $this; $this->SetApplication($app); return $this; } sub DESTROY { my $this = shift; $this->SUPER::DESTROY(@_); } #============================================================ # 継承クラスで定義しなおすウィンドウ関係メンバー関数 #============================================================ # 以下、標準の動作をしたい場合は、mw()の標準関数を呼び出す # 何もせずにundefを返すと、それぞれのウジェットを作成しない # ウジェット全体の作成 # mw()->InitCreateWidgets()を呼び出した後、 # ウィンドウタイトルを変更している sub CreateWidgets { my ($this) = @_; my $mw = $this->mw(); my $ret = $mw->InitCreateWidgets(); my $Frame = $mw->{'DataClassFrame'}; # データを保存するファイル名を選択する my $Frame0 = $Frame->MyFrame()->pack(-fill => 'x'); $this->{'FileLabel'} = $Frame0->MyLabel( -text => 'Save File:' )->pack(-side => 'left'); $this->{'FileEntry'} = $Frame0->MyEntry( -width => 50, -takefocus => 1, )->pack(-side => 'left', -expand => 'yes', -fill => 'x'); $this->{'FileEntry'}->focus(); $this->{'FilePathButton'} = $Frame0->MyButton( -text => '&Choose', -takefocus => 1, -command => [ \&ChooseFile, $this, $this->{'FileEntry'} ], )->pack(-side => 'left'); $this->{'FileEntry'}->SetText('a.txt'); # 測定インターバルを入力するテキストボックス(Entry) my $Frame1 = $Frame->MyFrame()->pack(-fill => 'x'); $this->{'IntervalLabel'} = $Frame1->MyLabel( -text => 'Interval:' )->pack(-side => 'left', -fill => 'x'); $this->{'IntervalEntry'} = $Frame1->MyEntry( -width => 30, -takefocus => 1, )->pack(-side => 'left', -expand => 'yes', -fill => 'x'); $this->{'IntervalEntry'}->SetText("500"); $this->{'IntervalUnitLabel'} = $Frame1->MyLabel( -text => 'ms' )->pack(-side => 'left'); # 平均を取る回数を入力するテキストボックス(Entry) my $Frame2 = $Frame->MyFrame()->pack(-fill => 'x'); $this->{'nAverageLabel'} = $Frame2->MyLabel( -text => '# of Average:', )->pack(-side => 'left', -fill => 'x'); $this->{'nAverageEntry'} = $Frame2->MyEntry( -width => 5, -takefocus => 1, )->pack(-side => 'left', -expand => 'yes', -fill => 'x'); $this->{'nAverageEntry'}->SetText(10); $this->{'VRangeLabel1'} = $Frame2->MyLabel( -text => ' Voltage range:', )->pack(-side => 'left', -fill => 'x'); $this->{'V0Entry'} = $Frame2->MyEntry( -width => 5, -takefocus => 1, )->pack(-side => 'left', -expand => 'yes', -fill => 'x'); $this->{'V0Entry'}->SetText(-3); $this->{'VRangeLabel2'} = $Frame2->MyLabel( -text => ' - ', )->pack(-side => 'left', -fill => 'x'); $this->{'V1Entry'} = $Frame2->MyEntry( -width => 5, -takefocus => 1, )->pack(-side => 'left', -expand => 'yes', -fill => 'x'); $this->{'V1Entry'}->SetText(3); $this->{'VRangeLabel3'} = $Frame2->MyLabel( -text => ', Step:', )->pack(-side => 'left', -fill => 'x'); $this->{'VStepEntry'} = $Frame2->MyEntry( -width => 5, -takefocus => 1, )->pack(-side => 'left', -expand => 'yes', -fill => 'x'); $this->{'VStepEntry'}->SetText(0.1); $this->{'VRangeLabel4'} = $Frame2->MyLabel( -text => ' V', )->pack(-side => 'left', -fill => 'x'); # Start(Stop)ボタンとClearボタン my $Frame3 = $Frame->MyFrame()->pack(-fill => 'x'); $this->{'StartButton'} = $Frame3->MyButton( -text => 'Start', -takefocus => 1, -command => [ \&StartButton, $this, "Start" ], )->pack(-side => 'left', -fill => 'x'); $this->{'ClearButton'} = $Frame3->MyButton( -text => 'Clear', -takefocus => 1, -command => [ \&StartButton, $this, "Clear" ], )->pack(-side => 'left', -fill => 'x'); # メインウィンドウのタイトルを設定 $mw->SetTitle("I-V measurement / TkPlot"); return $ret; } # ファイル選択ペインを作成しない sub CreateSelectFilePane { my ($this) = @_; # return $this->mw()->CreateSelectFilePane(); return undef; } # ファイル内容リストボックス、テキストボックスを作成しない sub CreateFileContentPane { my ($this) = @_; # return $this->mw()->CreateFileContentPane(); return undef; } # 初期のグラフ枠を作成する # 注:InitCreateGraphFrameArray は アプリケーション起動時に一度しか呼ばれない #   CreateGraphFrame は グラフ描画モジュールで、データファイルの読み込みのたびに呼び出される sub InitCreateGraphFrameArray { my ($this, $canvas) = @_; # 引数から$canvasが渡されなかった場合のため、$this->Canvas()を呼び出す $canvas = $this->Canvas($canvas); # 頻繁に使う変数を取得 # TkApplicationオブジェクト my $App = $this->App(); # TkPlotModuleオブジェクト。今回の場合は、TkGPIBTestオブジェクト自身が返る my $pDataArray = $App->TkData(); # Iniファイルの設定からフォントを設定する my $font = $App->{'GraphFrameFont'}; my @font = split(/,/, $font) if($font); # キャンバスの描画幅と高さ my $w = $canvas->width(); my $h = $canvas->height(); # グラフ枠の配列オブジェクトを作り、{'GraphFrameArray'}に格納 my $GraphFrameArray = $this->{'GraphFrameArray'} = new GraphFrameArray($this->mw()); # 念のため、グラフ枠の配列オブジェクトにもキャンバスサイズを設定 $GraphFrameArray->SetCanvasSize($w, $h); # グラフ枠を一つ作り、$GraphFrame0に取得 $GraphFrameArray->AddGraphFrame(); my $GraphFrame0 = $GraphFrameArray->GetGraphFrame(0); # Iniファイルの設定からグラフ枠の位置を設定する my $FramePosStr0 = $App->{"GraphFramePosition"}; $GraphFrame0->SetPositionByStr($FramePosStr0); # グラフ枠の目盛りにつける文字列を設定する $GraphFrame0->SetXCaption("X"); $GraphFrame0->SetYCaption('Y'); # グラフ枠の表示範囲を設定する。 $GraphFrame0->SetViewRange(0, 0, 1, 1); # データの配列をつくる my @X; my @Y; # データの配列を$GraphFrame0に結びつける # AddGraphDataには、データ配列のリファレンスを渡す #  0,'black'は、幅 0、色が'black'の線を描く => 線を描かない #  "dot", 1, "red", 0, "red",は、"red"色のdotを描画する #  "XAutoSkip"は、データ量が多いときに、画面の解像度に合わせて描画データをスキップする #  最後の"x","y"は、X,Yデータにつける名前。ほとんど意味はない $GraphFrame0->AddGraphData(\@X, \@Y, 0, 'black', "circle", 3, "red", 0, "red", "XAutoSkip", "x", "y"); return undef; } #======================================================================= # bindされた応答関数 #======================================================================= #ファイル選択ボタンを押したときに呼ばれる sub ChooseFile { my ($this, $widget) = @_; my $fmask = '*.*'; my $defstr = ''; my $message = 'Choose file'; my $dir = ''; my $filepath = Dialog::OpenFileDialog($this->mw(), 'save', $fmask, $defstr, $message, $dir); if($filepath) { if($widget) { $widget->SetText($filepath); } } return $filepath; } #Start, Clearボタンが押されたときに呼ばれる #$actionに、どのボタンが押されたかの情報が入る sub StartButton { my ($this, $action) = @_; my $button = $this->{'StartButton'}; my $text = $button->GetText(); if($action =~ /start/i) { # Startボタンが押されたとき、Startボタンの状態が"Start"か"Stop"に # なっているかによって処理を変える if($text =~ /start/i) { print "Press for Start\n"; $this->PressStart(); $button->SetText("Start"); } elsif($text =~ /stop/i) { print "Press for Stop\n"; $this->PressStop(); } } elsif($action =~ /clear/i) { print "Press for Clear\n"; $this->PressClear(); } } #======================================================================= # 呼び出される関数 #======================================================================= sub Measure { my ($this) = @_; #"Stop"ボタンがおされると、'DoStop'が1になる。このとき、何もせずに帰る return if($this->{'DoStop'}); my $GraphFrameArray = $this->{'GraphFrameArray'}; my $GraphFrame = $GraphFrameArray->GetGraphFrame(0); my $data = $GraphFrame->GetPlotData(0); #カウンター変数を取得 my $c = $this->{'Count'}; #Start: GPIBシミュレーションルーチン:GPIBでデータを$x,$yに受け取る my ($x, $y) = $this->MeasureByCount($c); #End: GPIBシミュレーションルーチン $data->AddData($x, $y); #データをコンソールに表示 $x = Utils::Round($x, 4); #有効数字を4桁に切り詰める $y = Utils::Round($y, 4); #有効数字を4桁に切り詰める print "$c: ($x,$y)\n"; #データをファイルに書き込む if($this->{'OutFileClass'}) { $this->{'OutFileClass'}->Write("$c\t$x\t$y\n"); } #データと$GraphFrameの最大・最小値を更新 $data->CalMinMax(); $GraphFrame->CalMinMax(); #データの範囲にあわせて$GraphFrameの描画範囲を変えたい場合 $GraphFrame->AdjustViewRange(0.05, 0.05, 0.05, 0.05); #表示範囲を固定する場合 # $GraphFrame->SetViewRange(0, 0, 1, 1); #再描画 $this->Draw(); $this->mw()->update; #カウンター変数を更新 $this->{'Count'} += 1; } #実際の測定ルーチン sub MeasureByCount { my ($this, $count) = @_; my $x = $this->{VStart} + $this->{VStep} * $count; $this->{K6517A}->SetSourceVoltage($x); my $y = $this->{K6517A}->MeasureAveraged($this->{nAverage}); return ($x, $y); } sub PressStart { my ($this) = @_; my $button = $this->{'StartButton'}; #ファイル名を取得し、すでに存在するかどうかをチェック my $FileName = $this->{'FileEntry'}->GetText(); #ファイル名が選択されていないときは、メッセージを出して処理を中止 if(!$FileName) { $this->mw()->messageBox( -icon => "info", # "info" "warning" "question" -type => "Ok", # "YesNo" "YesNoCancel" "OkCancel" "RetryCancel" -title => 'Error on FileName', -message => "FileName is not selected.\nChoose it."); return undef; } #ファイルが存在するときは、上書きの確認 if(-e $FileName) { my $ret = $this->mw()->messageBox( -icon => "question", # "info" "warning" "question" -type => "YesNo", # "YesNo" "YesNoCancel" "OkCancel" "RetryCancel" -title => 'Confirm for overwrite', -message => "$FileName exists.\nOverwrite?"); return unless($ret eq 'Yes'); } $this->{nAverage} = $this->{nAverageEntry}->GetText(); my $VStart = $this->{VStart} = $this->{V0Entry}->GetText(); my $VEnd = $this->{VEnd} = $this->{V1Entry}->GetText(); my $VStep = $this->{VStep} = $this->{VStepEntry}->GetText(); my $nVStep = $this->{nVStep} = int( ($VEnd - $VStart) / $VStep + 1.0001 ); $this->{K6517A} = new Device( -Port => $Port, -ModuleName => $ModuleName); my $VMax = abs($VStart); $VMax = abs($VEnd) if($VMax < abs($VEnd)); $this->{K6517A}->Initialize(); $this->{K6517A}->SetDCCurrentMode("off"); $this->{K6517A}->SetVSourceLimit($VMax); $this->{K6517A}->SetAutoRangeForCurrent("on"); $this->{K6517A}->SetSourceVoltage($VStart); $this->{K6517A}->SetVSourceOutput("on"); $button->SetText("Stop"); $this->{'DoStop'} = 0; $this->{'Count'} = 0; #ファイルを書き込みようにオープンする $this->{'OutFileClass'} = new JFile($FileName, "w"); if(!$this->{'OutFileClass'}) { $this->mw()->messageBox( -icon => "info", # "info" "warning" "question" -type => "Ok", # "YesNo" "YesNoCancel" "OkCancel" "RetryCancel" -title => 'Error', -message => "Can not write to $FileName."); return undef; } # データの読み込みをループ処理で行うと、ボタンの更新などができないので、 # タイマー(mw()->after())を使って$itime ms後にMeasure()関数を呼び出す # my $itime = $this->{'IntervalEntry'}->GetText(); for(my $i = 0 ; $i < $nVStep ; $i++) { # Stopボタンが押されると、{'DoStop'}が1になる last if($this->{DosStop}); $this->Measure(); } if($this->{'OutFileClass'}) { $this->{'OutFileClass'}->Close(); delete $this->{'OutFileClass'}; } } sub PressClear { my ($this) = @_; my $GraphFrameArray = $this->{'GraphFrameArray'}; my $GraphFrame = $GraphFrameArray->GetGraphFrame(0); my $data = $GraphFrame->GetPlotData(0); $data->ClearAll(); $GraphFrame->CalMinMax(); $this->Draw(); } # {'DoStop'}に1を入れて、測定ループを終了する sub PressStop { my ($this) = @_; $this->{'DoStop'} = 1; } 1;