電動スライダー、モーターコントロール、照明、測定器等の制御を、Windows PCで行う場合の注意点について、少し此処で取り上げます。
メカコントロールの場合、普通の計算や、文章作成のソフトとは違い、PCと、外部機器との通信によるコントロール、入出力用インターフェース(IOポート)、DA.ADコンバーター等を使い、メカを動かしたり、測定をしたりします。
問題は、Windows PCは、リアルタイムに動作させる事にはあまり向いていない事にあります。
リアルタイムに、動作させたり、測定する作業は、PCIコントロールボード、外部のシーケンサーや、測定器に任せ、IOポート、USB、LAN、RS232C通信により、メカのコントロール、測定器の動作等を、全体的にコントロールするのに、Windows
PCを使用します。
特に、測定したデーターの処理、データーの解析、加工、保存、表示にに関しては、Windows PCに勝るものはありません。
プログラム起動時のフォーム表示位置
最近は、一台のパソコンで、モニターを二台使用する事は珍しくありません。
二台目のモニターに表示していて、プログラムを終了したら、次起動した時、二台目のモニターへ表示されるようにすると、
二台使用していて、一台を取り外したりすると、そこに表示していた アプリケーションが、モニターの範囲外となり表示されなくなります。
それを防ぐため、表示範囲外だったら、表示範囲内に戻るようにします。
プログラムの起動時、プログラム例のFormsetを呼び出すことにより、必ず表示範囲内に表示されるようにします。
単に、Screen.Width
Screen.Height
を使用するとプライマリィのモニター情報になるだけなので、二台目のモニターに表示していた、ウインドウが、プログラムの起動時、一台目のモニターへ戻ってしまいます。
そこで、GetMonitorInfoを使用して一台目、二台目のモニター情報を取得して、表示位置の設定をします。
もし、マルチモニターに対応していないプログラムがウィンドーの表示外へ出てしまった場合は、キーボードで移動でまきます。
ウィンドウの位置をキーボードで移動するには
移動するウィンドウをクリックしてアクティブにします。ウィンドウ自体をクリックできない場合は、タスクバーのボタンをクリックします。
Alt
キーを押しながら、Space キーを押します。
M キーを押します。
ウィンドウを方向キーで移動します。
マウスでカーソルを移動すると、マウスカーソルにウィンドーが付いてきます。
任意の位置まで移動したら、Enter キーを押すか、マウスをクリックします。
uses に Multimon を追加
var
HMoninf :
TMonitorinfo; // モニター情報
procedure TMainF.FormposSet;
begin
if GetMonitorInfos then
// モニター情報取得
begin
Left:= MonitorLeftSet(Left, Width); // Left位置セット
Top := MonitorTopSet(Top, Height); // Top位置セット
end
else MonitorinfoErr('iniset');
end;
//
********************************* マルチモニター情報取得 **********************************
function TIm_whereF.GetMonitorInfos: boolean;
begin
ZeroMemory(@HMoninf, sizeof(HMoninf)); // メモリーの値を全て0にする起動時はクリアされているので不要だが念のため
HMoninf.cbSize := SizeOf(HMoninf); // メモリーのサイズセット
// NMonitor :=
Screen.MonitorCount; // モニターの数
result :=
GetMonitorInfo(Monitor.Handle, @HMoninf); // モニター情報取得
end;
//
********************************* マルチモニター Top位置セット ****************************
function TMainF.MonitorTopSet(TopPos, Heighti: integer): integer;
// Top位置セット
begin
Result := TopPos;
if TopPos + Heighti >
HMoninf.rcMonitor.Bottom then Result := HMoninf.rcMonitor.Bottom - Heighti;
if Result < HMoninf.rcMonitor.Top then Result := HMoninf.rcMonitor.Top;
end;
// ********************************* マルチモニター Left位置セット
****************************
function TMainF.MonitorLeftSet(LeftPos, Widthi:
integer): integer; // Left位置セット
begin
Result := LeftPos;
if LeftPos + Widthi > HMoninf.rcMonitor.Right then Result :=
HMoninf.rcMonitor.Right - Widthi;
if Result < HMoninf.rcMonitor.Left
then Result := HMoninf.rcMonitor.Left;
end;
procedure
TMainF.MonitorinfoErr(addr: string);
begin
MessageDlg('モニター情報が取得出来ません。' + addr, mtInformation,[mbOk], 0);
end;
二重起動の防止
機械を動作させるので、安全に動作させる必要があります。
二重に起動すると、誤動作を起こすので、装置の破損、怪我、火災等の原因となります、絶対に二重に起動してはいけません。
その為、Mutexを使用して、起動済みかどうかの確認をしてから起動します。
次がMutexを使用した例です。
メモリーリーク検出は、デバッグが終わったら、コメントアウトします。
メカコントロール、測定用のソフトは、可なり長時間使用するのですが、途中で止まることは許されません、メモリーリークの無いことを必ず確認する必要があります。
プロジェクトファイルの表示は、プロジェクト->ソースの表示です。
program mechaCon;
uses
Vcl.Forms,
Winapi.Windows,
MainCon in 'MainCon.pas'
{MainF};
{$R *.res}
var
hMutex: THandle;
FUniqueName: Pchar;
begin
ReportMemoryLeaksOnShutdown := True; // メモリーリーク検出
FUniqueName :=
'mechaCon001'; // ユニーク名
hMutex := OpenMutex(MUTEX_ALL_ACCESS, False,
FUniqueName); // Mutexの検索
if hMutex <> 0
then // 既に起動されていたら終了する
begin
CloseHandle(hMutex);
exit;
end;
hMutex := CreateMutex(nil, False,
FUniqueName); // Mutexの作成
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainF, MainF);
Application.Run;
ReleaseMutex(hMutex); // Mutexの解放
end.
システムメッセージ処理の停滞防止
タイマーや、マウスのイベント処理用に、アプリケーションのフォームにWindowsシステムから、システムメッセージが送られてきますが、フォームをマウスで移動しようとして、タイトルバー(フォームの枠上部)にカーソルを置き、マウスのボタンをクリックダウンすると、システムメッセージの SC_MOVEにより、他のメッセージが少しの間停滞します。
PostMessage の場合は、少しの時間停滞し、溜まったメッセージを連続して処理し、その後正常に処理されるようになります。
とにかく、メカの動作をタイマーを使用して、コントローラーにコマンドを送信して動作させる場合、少しの時間でも、タイマー処理が止まったり、停滞することがあってはなりません。
プログラム中にApplication.ProcessMessagesがある場合は、プログラムがそこで停止したりする現象も発生しますが、これは致命的です。
メッセージの処理を遅らせているのは、アプリケーション側のフォームなので、メカコントロール実行時は、SC_MOVE コマンドの処理をしないようにします。
又、フォームのクローズボタンはグレイアウトします。
SC_CLOSEの処理は、フォームのクローズボタンを表示していなければ必要ありません。
WM_SysCommandをトラップすることで、先に処理をします。
メカのコントロール中に途中でプログラムを終了されてしまうのを防止するために処理をしていますが、ダウンロード出来るプログラム例では、プログラムの実行中に、タイトルバーのクローズボタンを押されると、押している間メッセージ処理が停止します。
これは、タイマーのPostMessaged処理が停滞するためです。
表示しないか、グレイアウトする必要があります。
グレイアウトする方法を下記にあげてあるので参考にして下さい。(EnableMenuItemを使用してスレッド実行中だけグレイアウトする方法もあります。)
又、スレッドのテストプログラム(特にBeginThread)の中で、色々な例を取り上げていますので、そちらも参照して下さい。
private
Procedure WMSysCommand(var Messages:
TWMSysCommand);message WM_SysCommand;
Procedure TMainF.WMSysCommand(var Messages:
TWMSysCommand); // フォームのborder Close ボタンのみ有効の場合
begin
//
メカコントロール用なので、minimize Maximize は非表示に設定します、Close ボタンをグレイアウトするか、非表示にした方が良いでしょう
case (messages.CmdType and $FFF0)
of
SC_SIZE : Messages.Result := 0;
// サイズ変更キャンセル bsSizeableの時 bsSingleであれば不要
SC_CLOSE: SDoClose; // フォームのborder Closeボタン使用時スライダー移動中の終了を避ける グレイアウトしたほうが良いです
SC_MOVE :
if
not SliderF and not SliderAF
then inherited
// スライダー移動していなかったらフォーム移動
else
Messages.Result := 0; // フォームの移動キャンセル
else
inherited; // 指定以外はデフォルト処理
end;
end;
{ クローズ処理 }
procedure TMainF.SDoClose;
begin
if not SliderF
and not SliderAF then close
else
Application.MessageBox('スライダーを停止してから終了してください', '注意', 0);
end;
{ タイトルバー の クローズボタングレイアウト}
procedure
TMainF.Button12Click(Sender: TObject);
var
insMenu
: HMenu;
begin
insMenu := GetSystemMenu(Handle,
False);
// DeleteMenu (insMenu, 6, MF_BYPOSITION);
// メニューの6番目の閉じるを削除 メニューの閉じるは特に削除する必要は無いようです
DeleteMenu
(insMenu, SC_CLOSE, MF_BYCOMMAND);
// 上のバーの閉じるをグレイアウト
DrawMenuBar (Handle);
end;
メカコントロール及び測定中のレジューム、サスペンドの処理
マウス、キー入力で、レジューム、サスペンドに入るタイマーがリセットされますが、メカコントロール、測定用ソフトの起動中は、マウス、キーボードによる入力がありません。
メカコントロール及び測定中に レジューム或いはサスペンド状態になってしまうと、重大な事故に繋がります。
パソコンの設定で、出来る限りレジューム、サスペンドは無しに設定します。
しかし、パソコンを扱うのは、人なので設定を忘れるかも知れません、そこで、プログラムの起動時に、レジューム、サスペンド、スクリーンセーバーを停止する命令を実行します。
プログラム終了時に、元へ復帰させます。
function SetThreadExecutionState(esFlags: DWORD): DWORD;
stdcall external 'kernel32.dll';
const
ES_SYSTEM_REQUIRED = $00000001;
//
システムレジューム(サスペンド)停止
ES_DISPLAY_REQUIRED = $00000002;
// スクリーンセーバー
Display電源0ff 停止
ES_CONTINUOUS = $80000000;
// 状態継続
ES_AWAYMODE_REQUIRED = $00000004; //
Enables away mode
{
システムレジューム(サスペンド) の電源offの管理 }
Procedure
TMainF.SetThreadExecutionStateD;
begin
SetThreadExecutionState(ES_SYSTEM_REQUIRED or ES_CONTINUOUS);
//
システムレジューム(サスペンド)停止
end;
Procedure
TMainF.SetThreadExecutionStateE;
begin
SetThreadExecutionState(ES_CONTINUOUS); // レジュームサスペンド許可
電源のoff許可
end;
SetThreadExecutionStateは Delphi の API に宣言されていないので、追加しています。
スクリーンセーバーも止める場合は
SetThreadExecutionState(ES_SYSTEM_REQUIRED or
ES_DISPLAY_REQUIRED or ES_CONTINUOUS);
//
システムレジューム(サスペンド)、スクリーンセーバー 停止
です。
スクリーンセーバーだけ停止場合は、
SetThreadExecutionState(ES_DISPLAY_REQUIRED or ES_CONTINUOUS);
ですが、OSのバージョンと、スクリーンセーバーのパスワードの設定で、動作しない場合があるようです。
確認が取れていませんので、テストをしてから使用して下さい。
スクリーンセーバーを停止する場合で
SetThreadExecutionState(ES_DISPLAY_REQUIRED); をタイマーで、スクリーンセーバーの設定時間より短い間隔で実行すると、スクリーンセーバーが停止する場合もあるようです。
メカコントロールルーチン用のタイマー
メカの制御は、通信によって行いますが、その動作コマンドの送信は、タイマーと、フロー制御のステップカウンター、制御フラグにより行います。
その為、タイマーは出来る限り正確な方が良いことになります。
カウンター付きの外部コントロール用のPCIボードを使用すると、ハードタイマーが付いているものもあり、それを利用すれば、精度の良いコントロールが可能となります。
しかし、前記の用意が出来ない場合、
動作させる相手によっては、バラツキの大きい、システムタイマーでも良いのですが、ある程度精度を要求する場合は、マルチメディアタイマーの利用が良いでしょう。
次の例では、システムコマンドのSC_MOVEの処理をしていませんが、タイマーを正確に動作するために必要です。
プログラム例がダウンロード出来るので、テストしてみて下さい。
タイトルバー(
フォームの上側の枠の部分)にマウスカーソルを置いて、マウスのボタンをクリックダウンすると、表示の更新が停止し、フォームの移動か、マウスのクリックをアップすると表示が更新されるのがわかります。
溜まっていた、数の分一気に更新されます。
unit MMTimerMain;
interface
uses
Winapi.Windows,
Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, MMsystem, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Label1: TLabel;
Label2: TLabel;
procedure FormCreate(Sender:
TObject);
procedure Button1Click(Sender: TObject);
procedure
Button2Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言
}
procedure WndProcd(var Msg: Tmessage);
public
{ Public 宣言 }
end;
var
Form1: TForm1;
mmResult : UINT;
FWindowHandle : HWNd;
Counter : integer;
// Windows Handle
StartTime : DWORD;
endtime : DWORD;
implementation
{$R *.dfm}
procedure TimeCallBack(TimerID, Msg:
Uint; dwUser, dw1, dw2: DWORD);stdcall;
var
interval : DWORD;
begin
endtime := timeGetTime;
if endtime >= StartTime
then
interval := endtime - StartTime
else
interval :=
not StartTime + endtime + 1;// 約 49.71 日でカウンターが一周するので、その対策
StartTime
:= timeGetTime;
// SendMessage(dwUser, WM_USER, 0, interval);
PostMessage(dwUser, WM_USER, 0, interval);
end;
procedure TForm1.WndProcd(var Msg: Tmessage);
//
MMTimerのPostMessage送信先
begin
with Msg
do
case Msg of
WM_USER :
begin
inc(Counter);
Label1.Caption := intTostr(Counter);
// カウンターの値表示
Label2.Caption := 'Interval ' + intTostr(LParam) + ' msec';
// インターバル時間表示
end;
else
Result := DefWindowProc(FWindowHandle,Msg,WParam,LParam);
// WM_USER以外の処理
end;
end;
procedure TForm1.Button1Click(Sender:
TObject);
begin
Button1.Enabled := False;
Counter := 0;
timeBeginPeriod(1);
StartTime := timeGetTime;
mmResult :=
TimeSetEvent(100, 0, @TimeCallBack, FWindowHandle, TIME_PERIODIC);
Button2.Enabled := True;;
end;
procedure TForm1.Button2Click(Sender:
TObject);
begin
Button2.Enabled := False;
timeKillEvent(mmResult);
timeEndPeriod(1);
Button1.Enabled
:= True;;
end;
procedure
TForm1.FormCreate(Sender: TObject);
begin
FWindowHandle :=
AllocateHWnd(WndProcd); // WndProcd Handle の 生成
Button2.Enabled :=
False;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
DeallocateHWnd(FWindowHandle); // WndProcd Handle の破棄
end;
end.
RS232C通信の送信書き込みWaitについて
RS232C通信の送信書き込み時、書き込み処理が済むまで待って、制御を戻すのと、バッファに書き込んだら直ぐに制御を戻す方法が選択できます。
これは、受信データーの読み出し時も同じで、読み出し時、指定したバイト数読み出すまで待って、制御を戻すのと、指定したバイト数に満たなくても既に受信済みのデーターのみ読み出して制御を戻す方法の選択が出来ます。
通常は、通信によるコントローラーの制御は、ハンドシェイクによって行われ、コマンド送信後、受信確認のメッセージが、コントローラーから送られてこない限り、次のコマンドは送信しないので、書き込み時は、バッファに書き込んだら直ぐに制御を戻しても構いません。
受信時は、受信割り込みを使用するので、読み出しWaitは、有っても無くて良いでしょう。
送受信時の、コマンドの最後の文字は、コントローラーによって決められた文字コードが使用されるのが一般的です。
通常は改行又は復帰ですが、$03のような、コードを使用する場合もあります。
RS232Cの受信割り込みの発生は、受信時、Windowsのシステムに任せてランダムに発生させる場合と、割り込み発生文字を指定して、それを受信したら割り込みを発生させる方法が選択出来ます。
外部のコントローラー、測定器等を制御する場合、コマンドの最後の文字コードが決まっているので、この文字コードを受信したら割り込みが発生するようにすることで、通信プログラムを簡単に出来ます。
通信によるハンドシェイクを使用しない場合は、指定した文字コードを含んだ場合となり、文字コードを再度、プログラムで検出して、区切りを処理しなければなりませんが、通信によるハンドシェイクが無い、コントローラー、測定器等はありません。
受信割り込みの文字コードの指定は、DCB構造体の DCB.EvtChar を指定する事で行います。
プログラムの内容は、本ホームページのRS232CコンポーネントからRS232C通信用コンポーネントがダウンロード出来るのでそれを参照して下さい。
Vista以降のWindowsで、OnBoardのRS232Cを使用する場合、CPUの電源管理の最小のプロセッサの状態を100%に設定しないと、受信エラーが発生する事があります。
通信速度で、19.8Kを越えると、発生率が高くなります。通信エラーが発生すると、装置のコントロールがとまってしまい致命的な問題となります。
最小のプロセッサの状態を100%にしても、温度上昇は僅かなので問題ありませんが、気になるようであれば、USBのRS232Cケーブルを使用すれば良いでしょう。受信処理が、ケーブル内のチップで処理されるので、CPUの処理速度に影響される事は有りません。
外部コントローラーとのRS232C通信での、コマンドの応答受信後の次のコマンド送信について
コマンド送信後、外部のコントローラーからの応答受信後。速攻で次のコマンドを送信すると、コントローラーが受信エラーになる場合があります。
特にコントローラーが古く使用していたPCも古く、PCを変えた時などに問題が発生します。
モーターコントローラー、シーケンサー等に使用されているCPUは、ワンチップのマイコンであることが多く、さほど演算速度の速いものではありません。
最近のPCは、複数のコアのCPUを載せているため、実行速度が非常に速くなっていて、外部コントローラーが応答送信後、受信モードに入る前に、次のコマンドを送ってしまう事があります。
それを防止するため、応答受信後、数ミリSec以上遅らせて次のコマンドを送信するようにします。
新しい、最近のシーケンサーでは、十分に早くなっていて、問題なく応答しますがそれでも、コマンドを連続で送信すると、応答で忙しくなり、本来のシーケンサーの実行が遅くなるので注意が必要です。
USB通信、RS232C通信によるI/Oポートの入出力
PCIボードの入出力I/Oポート以外、入力端子による割り込みが無いのが普通です。
有ったとしても、通信によりソフト上で割り込みを発生させる為、入力端子の信号レベルが変化してから、プログラム上で、割り込みとして認識するまでの時間が相当掛かります。
ドライバーに割り込みの為のソフトが組み込まれていない場合は、入力ボートの状態を早く知るために、入力ボートのポーリングが必要となります。
ポーリングには、BeginThread、CreateAnonymousThread を使用して、必要な時のみ、スレッドを立てるのが良い方法ですが、通常のTThreadを利用しても良いでしょう。
スレッドのサンプルは、インターネットで検索すれば沢山見つかるので、参考にしましょう。
一般的には、USB通信の機器との通信はDLLにより、出力ルーチン、入力ルーチンを呼び出すのですが、USBシステムそのものがポーリングしているので、PCIボードのコントローラーと違い、通信に時間が掛かります。
PCの性能によっても、時間が変わるので、USB通信の機器を使用する場合、実際に使用するPCで、どれ位の時間を要するか、測定をしておく必要があります。
又、USBポートの使用状況によっても時間が変動するので、測定値に対して、余裕を持ったプログラムを組む必要があります。
時間の測定は、ストップウォッチコンポーネントを使用すれば、容易に出来ます。
特に速度を要求されない場合は、タイマールーチンにより、I/Oボートを読み出し、ポートの状態フラグをセットしてそれを利用するようにします。
出力ポートは、出力用ポート内容のフラグを変更、ポート出力のタイミング時、前のポート状態のフラグと変更になったかをチェックし変更があったら出力します、これにより、無駄な出力動作を避けることが出来ます。
速度を要求される場合は、必要な時に、出力用のサプ゛ルーチンを呼び出すようにすれば良いでしょう。
出力ポートでも、ポートの状態を読み出せるので、書き込み後、読み出して正しく出力したか確認するようにします。
次の例はBeginThreadの例です。
画面の表示更新は、処理の更新が衝突してはいけません、又、BeginThreadでのスレッド内で可変長の変数Stringを使用するとメモリーリークが発生します。
そこで、画面の更新、可変長の変数Stringは別のルーチンを用意、スレッド内から呼び出すようにします。
新しく作ったルーチンは、複数のスレッドから呼び出される事になりますので、実行の衝突を避ける必要があります。
衝突の回避として、Synchronizeは使わずに、SendMessage又は、PostMessagで、スレッドの処理の衝突をさけていますが、問題なく動作します。
処理の衝突を避けるのには、TCriticalSectionを使用する方法もあります。
サンプルプログラムThreadTest1に使用例があるのでテストをしてみてください、こちらの方法の方がプログラムは簡単にすみます。
BeginThrad で、渡せる引数は、ポインター1個だけなので、構造体のポインターを渡します。
此処の例では、構造体のポインターを渡します、SendMessageでは、ポインターを一旦CARDINAL 又は
DWORDに変換し、メッセージの送り先で、構造体のポインターに戻して使用しています。
プログラム中の Thread :=
BeginThread(); は、スレッドが開始したら、直ぐにハンドルをかえします、その後 直ぐに CloseHandle(Thread); を実行してもスレッドが終了するわけではありません、単に取得した Handle が無くなるだけです。
スレッドの終了と解放は、スレッド内の EndThread(0); で実行されます。(EndThread(0)は無くても、スレッドが終了すれれば、スレッドは自動的に解放されます。)
unit Main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils,
System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls,
Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, MMSystem;
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Label1: TLabel;
Label2: TLabel;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure
Button4Click(Sender: TObject);
procedure
FormCreate(Sender: TObject);
procedure
FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private 宣言 }
procedure
WndProcd(var Msg: Tmessage);
procedure
WMSysCommand(var Messages: TWMSysCommand);message WM_SysCommand;
public
{ Public 宣言 }
procedure
ThreadStart1;
procedure ThreadStart2;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type // スレッドレコードの定義
TThreadParams =
record //
スレッドに値を渡す
FButton : TButton;
FEdit :
Tedit;
FLabel : TLabel;
FTimeValue :
Integer;
end;
PThreadParams =
^TThreadParams;
var
FWindowHandle : HWNd;
//
Windows Handle
StopF : Boolean;
// スレッド停止用フラグ
{ SYSTEM Command
処理
測定用ソフトやメカコントロールソフトの場合 SYSTEM Command でタイマーに処理に
悪影響があるものがあり、特に SC_MOVE は表示の実行をとめ表示の割り込み処理が溜まってしまう
ここの例ではタイマーを使用していないので、滞ることはありません}
Procedure
TForm1.WMSysCommand(var Messages: TWMSysCommand); // フォームのborder Close
ボタンのみ有効の場合
begin // メカコントロール用なので、minimize Maximize
は非表示に設定すること
case (messages.CmdType
and $FFF0)
of
SC_SIZE :
Messages.Result := 0; // サイズ変更キャンセル bsSizeableの時 bsSingleであれば不要
SC_CLOSE: Button4Click(nil); // フォームのborder Closeボタン
// SC_MOVE : if StopF
// then inherited // スレッド停止していたらフォーム移動
// else Messages.Result := 0; // フォームの移動キャンセル
else
inherited; // 指定以外はデフォルト処理
end;
end;
// スレッド内で可変長Stringを扱うとメモリーリークが発生するためサブルーチンにする
procedure Threadsub(FCont: integer; Param: PThreadParams);
// スレッド内のカウンターの値表示
begin
Param^.FEdit.Text :=
inttostr(FCont);
Param^.FEdit.Update;
end;
procedure TimerThread(Param : PThreadParams);
// スレッドの例
var
Count: integer;
begin
while
not StopF do begin
Param^.FLabel.Caption := 'Going'; // 'Going' const stringはOK メモリーリークなし
Param^.FLabel.Update; // LabelをAutoSizeにしておくと、文字のサイズに異常が出る場合があります
Count:= 0;
while Param^.FTimeValue >=
Count do begin // Countの値待ち
//
SendMessage(FWindowHandle, WM_USER, Count, cardinal(Param));
//
カウンターの値表示 メッセージ送信後終了まで制御を戻さない
PostMessage(FWindowHandle, WM_USER, Count, cardinal(Param));
//
カウンターの値表示 メッセージ送信直ぐに制御を戻す
// これがないとCPU使用率が跳ね上がります 此処ではタイマー役割も兼用
Sleep(5); // 用途に応じてスリープ時間を設定する最小値は1
inc(Count); // カウンターインクリメント
end;
//
終了したらビープ音
Winapi.windows.Beep(800, 70);
Param^.FLabel.Caption := 'Stop'; // 'Stop' const stringはOK メモリーリークなし
Param^.FLabel.Update;
Sleep(200);
//
用途に応じてスリープ時間を設定する最小値は1
end;
Param^.FButton.Enabled := True;
//スレッド終了
Dispose(Param);
//
スレッド開始時に New(Pinfo)で作成されたレコードを此処で解放します
EndThread(0);
// スレッド終了
end;
procedure TForm1.WndProcd(var Msg:
Tmessage); // MMTimerのSendMessage又はPostMessage送信先
begin
//
MMTimerで直接グラフィク表示ルーチンを呼び出すと正常に動作しない
with Msg
do
case Msg of
WM_USER : Threadsub(WParam, PThreadParams(LParam));
// ポインターに戻す
PThreadParams(LParam)
else
Result := DefWindowProc(FWindowHandle,Msg,WParam,LParam);
// WM_USER以外の処理
end;
end;
procedure
TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
DeallocateHWnd(FWindowHandle); // WndProcd Handle の破棄
timeEndPeriod(1);
end;
procedure
TForm1.FormCreate(Sender: TObject);
begin
Top :=
(Screen.Height - Height) div 2;
Left := (Screen.Width - Width)
div 2;
StopF := True;
timeBeginPeriod(1); // タイマーの分解能設定
FWindowHandle := AllocateHWnd(WndProcd); // WndProcd Handle の 生成
Label1.AutoSize := False;
Label2.AutoSize := False;
Label1.Height := 13;
Label1.Width := 65;
Label2.Height :=
13;
Label2.Width := 65;
end;
procedure TForm1.ThreadStart1;
var
id :
Cardinal;
Pinfo : PThreadParams;
begin
Button1.Enabled := False;
New(Pinfo);
Pinfo.FButton :=
Button1;
Pinfo.FEdit := Edit1;
Pinfo.FLabel := Label1;
Pinfo.FTimeValue := 120;
CloseHandle(BeginThread(nil, 0,
Addr(TimerThread), Pinfo, 0, id));
end;
procedure TForm1.ThreadStart2;
var
id :
Cardinal;
Pinfo : PThreadParams;
begin
Button2.Enabled := False;
New(Pinfo);
Pinfo.FButton :=
Button2;
Pinfo.FEdit := Edit2;
Pinfo.FLabel := Label2;
Pinfo.FTimeValue := 250;
CloseHandle(BeginThread(nil, 0,
Addr(TimerThread), Pinfo, 0, id));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
StopF := False;
ThreadStart1;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
StopF := False;
ThreadStart2;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
StopF := True;
end;
procedure
TForm1.Button4Click(Sender: TObject);
begin
if Button1.Enabled and Button2.Enabled
then Close;
end;
end.
入力信号に対して、リアルタイムにアクションが必要な場合は、PCIボードのコントローラーか、プログラムマブルシーケンサーが必要です。
市販されているシーケンサーは、単にステッピングモーターを一個、入出力おのおの8bit程度のものから、ステッピングモーター、サーボモーター、AD DAコンバーター、液晶タッチパネル等の追加の出来る高価なシーケンサーまであります。
当然シーケンサーだけで、装置の制御は出来ますが、コスト、操作性を考えて、何処までをシーケンサーで制御し、何処からパソコンで行うかを検討します。
単純な動作の繰り返しが多い場合は、液晶タッチパネルを利用した、シーケンサーによる制御、ユーザーにより動作の組み合わせ、データーのサンプリング処理等の条件が多い、遠隔操作が必要等の場合は、シーケンサーと、パソコンの組み合わせでコントロールします。
シーケンサーは、量産はされていますが、可なり高価ですし、シーケンサーのプログラム言語を学ぶ必要があります。
そこで、安価なパソコン+安価なIO、コントローラーで、装置のコントロールと、測定等を行う方法が取られます、特に実験装置等は長期間使用するものではないかわりに、直ぐに変更されるので、柔軟に対応出来る方法として採用されています。
サンプルプログラムがダウンロードできますが、残念ながら、通信相手が無いため、実装状態のプログラムではありません。
それでも、多少は参考になるかと思います。