Praktyczne programowanie, R 8-04, Szablon dla tlumaczy


Rozdział 8 Przykładowe aplikacje wykorzystywane w systemach pomiarowych

„Często powtarzam, że jeżeli możesz zmierzyć to, o czym mówisz oraz opisać to za pomocą liczb, wiesz coś o tym, ale jeżeli nie jesteś w stanie opisać tego za pomocą liczb, twoja wiedza o tym jest niezadawalająca, niezależnie od tego, czego ona dotyczy; jest to zaledwie początek wiedzy, pierwszy krok na szczeblach nauki.”

William Thomson, 1858

W rozdziale tym omówimy niektóre przykłady zastosowań aplikacji obsługujących przyrządy pomiarowe z wykorzystaniem standardu RS 232C. Istnieje pewna dziedzina wiedzy, obejmująca zarówno teoretyczne jak i praktyczne zagadnienia związane z pomiarami. Jest nią metrologia. Podobnie jak w innych gałęziach nauki i techniki, tak i w metrologii w ostatnich kilkudziesięciu latach dokonał się olbrzymi postęp. Od fazy, w której dominowały pomiary oparte na metodzie porównawczej (bezpośredniego porównywania mierzonych wielkości za pomocą mierników wychyłowo-wskaźnikowych) poprzez wykorzystywanie przyrządów elektrycznych, których wskazania były rejestrowane przez różnego rodzaju samopisy, dochodzimy do etapu, w którym dokonanie szybkiego i wiarygodnego pomiaru stało się niemożliwe bez wykorzystania komputera sprzęgniętego z urządzeniem pomiarowym. Wykorzystując komputer, mamy możliwość automatycznego sterowania procesem zbierania i przetwarzania danych. Współczesne przyrządy pomiarowe są bardzo zaawansowane pod względem technologicznym. Ich części składowe wykonywane są w postaci wysokospecjalizowanych układów scalonych lub hybrydowych, których konstrukcja objęta jest tajemnicą handlową. Urządzenia takie mają określone funkcje i parametry eksploatacyjne, które należy optymalnie wykorzystać. O możliwościach w pełni skomputeryzowanego systemu pomiarowego w coraz mniejszym stopniu decyduje wiedza o konstrukcji danego przyrządu, w coraz większym zaś specjalistyczne oprogramowanie.

Kontroler temperatury

Jako przykład wykorzystania poznanych do tej pory sposobów programowej obsługi łącza szeregowego RS 232C wybrałem kontroler temperatury firmy LakeShore. Jest on przykładem nowoczesnego wielofunkcyjnego miernika, za pomocą którego nie tylko można odczytywać aktualnie mierzoną temperaturę, ale przede wszystkim ją stabilizować. Wykorzystując specjalnie skonstruowaną grzałkę sterowaną z wymienionego urządzenia, mamy możliwość ciągłego utrzymywania danego układu w z góry zadanej temperaturze. Aktualna wartość mierzonej temperatury odczytywana jest za pomocą diody półprzewodnikowej. Wygląd działającego projektu aplikacji \KODY\DELPHI\RS_26\p_RS_26.dpr, zaopatrzonego w najważniejsze podstawowe funkcje oferowane przez urządzenie pomiarowe pokazany jest na rysunku 8.1.

Rysunek 8.1. Działająca aplikacja obsługująca kontroler temperatury

0x01 graphic

Korzystając z takich aplikacji mamy możliwość wyboru jednostek, w których odczytujemy temperaturę i ustalenia szybkości grzania (stopnie na minutę). Mamy też możliwość wyboru trybu pracy miernika: z wyłączoną lub z włączoną opcją grzania (grzanie szybkie lub pośrednie). Można również ustalić górna granicę temperatury, w której chcemy utrzymywać dany układ fizyczny bez względu na warunki zewnętrzne. Aplikacja obsługująca kontroler temperatury została napisana w Delphi, zaś jej kompletny został zamieszczony na wydruku 8.1.

Obsługa programu sprowadza się do umiejętnego wykorzystania poznanych już wcześniej komponentów oraz funkcji obsługujących transmisję szeregową. Jednak budowa algorytmu różni się nieco od prezentowanych wcześniej, dlatego przedstawię teraz jego ogólne założenia. Główne modyfikacje zostały wprowadzone w treści procedur obsługujących zdarzenia otwarcia portu szeregowego oraz odczytu danych. Uruchamiając program i otwierając wybrany port szeregowy do transmisji, od razu diagnozujemy aktualne ustawienia przyrządu. Tuż po otwarciu portu, w procedurze obsługi zdarzenia OpenCommClick() wielokrotnie wywoływana jest inna procedura:

procedure RS_Send (queryORcommand : PChar);

begin

repeat // transmisja zapytania lub komendy

FlushFileBuffers(hCommDev);

StrCopy(Buffer_O, queryORcommand);

until(Write_Comm(hCommDev, StrLen(Buffer_O)) <> 0);

end;

gdzie w miejsce jej parametru queryORcommand podstawiamy w kolejności zapytania o typ diody, wartość pierwszego pomiaru. Dowiadujemy się też, czy przy poprzednim uruchomieniu programu sterującego ustalono i zapamiętano górną temperaturę grzania układu. Następnie pytamy o identyfikację przyrządu, aktualne jednostki oraz czy ustalono wcześniej szybkość grzania i czy włączono dany stopień grzania. Wszystkie te informacje będą Użytkownikowi bardzo pomocne, jeżeli chce mieć kompletną informację o parametrach wcześniejszych pomiarów. Informację o włączonym procesie podgrzewania próbki otrzymujemy, podświetlając odpowiedni komponent TShape znajdujący się obok odpowiadającego mu przycisku, tak aby w razie potrzeby ewentualnie włączone grzanie można było w miarę szybko wyłączyć.

Po wstępnym zdiagnozowaniu stanu wskazań miernika dobrze by było, gdyby aplikacja od razu zaoferowała nam możliwość zapisu danych (w postaci np. pliku *.dat) na dysku. Dokonamy tego, wyświetlając komunikat:

wResult_Save := MessageDlg('Zapisać dane do pliku ? *.dat.',

mtCustom, [mbYes, mbCancel, mbNo], 0);

case wResult_Save of

mrYes:

begin

if (SaveDialog1.Execute) then

begin

bResult_Yes := TRUE;

AssignFile(OutFile, SaveDialog1.FileName+'.dat');

Rewrite(OutFile);

end;

end;

mrNo : Exit;

end;

Tego rodzaju metoda poinformowania o możliwości zapamiętania danych na dysku nie jest być może zbyt elegancka, niemniej jednak — co wydaje się dużo ważniejsze - jest niezawodna. Postępując w ten sposób, na pewno nie zapomnimy zapamiętać efektu swojej pracy.

Programy obsługujące przyrządy pomiarowe z reguły pracują przez wiele godzin, dlatego zapamiętywanie wskazań miernika w tablicach i zapisanie ich dopiero na końcu nie ma większego sensu. Dane muszą być zapisywane w trakcie pomiaru (on line). Operację tę realizuje funkcja RS_Send_Receive(). W jej treści, oprócz dokonywania właściwego pomiaru oraz cyklicznego zapisu danych na dysku, dowiadujemy się ponadto, jaki jest aktualnie stopień mocy grzania próbki (jeżeli oczywiści opcja ta jest włączona którymś z przycisków HeaterMedium lub HeaterFast).

function RS_Send_Receive(P: Pointer): Integer;

var

ivart, Code : Integer;

begin

REPEAT

Clean_Buffers;

{-- pytanie o aktualną moc grzejnika [%] --}

if (bResult_Heater = TRUE) then

begin

StrCopy(Buffer_O, query_HEAT);

repeat // transmisja komunikatu

FlushFileBuffers(hCommDev);

until (Write_Comm(hCommDev, StrLen(Buffer_O)) <> 0);

Sleep(100);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0)

and (bResult_Heater = TRUE) then

begin

val(Buffer_I, ivart, Code);

Form1.Gauge1.Progress := ivart;

end;

end

else

Form1.Gauge1.Progress := 0;

{-- pytanie o aktualnie mierzoną wartość --}

StrCopy(Buffer_O, query);

repeat // transmisja komunikatu

FlushFileBuffers(hCommDev);

until (Write_Comm(hCommDev, StrLen(Buffer_O)) <> 0);

Sleep(intVarSleep);

//-------odczyt danych z portu--------

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

begin

Form1.RichEdit1.Text := Buffer_I;

Inc(intVar); // zliczanie kolejnych pomiarów

Form1.Memo1.Lines.Add(AnsiString(IntToStr(intVar)));

if (bResult_Yes = TRUE) then

WriteLN(OutFile, intVar, ' ', Form1.RichEdit1.Text);

end

else

begin

Form1.RichEdit1.Text := 'x0'; // błędny odczyt

Beep();

end;

UNTIL(bResult = FALSE);

Result := 0;

end;

Stopień mocy podgrzewania (od 1 do 100%) pokazywany jest dzięki komponentowi TGauge, zaś kolejny numer pomiaru wyświetlany jest w komponencie edycyjnym TMemo.

Czynności zamiany skali temperatur dokonywane są w procedurach obsługi zdarzeń TemperatureKelvinClick() oraz TemperatureCelsiusClick(). Nie ograniczyłem się w nich jedynie do prostego sposobu wysłania rozkazu zmiany skali, zażądałem ponadto odczytu górnej granicy temperatury grzania właściwej dla danej skali:

...

RS_Send(query_SETP);

Sleep(1000);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

begin

val(Buffer_I, ivart, Code);

UpDown1.Position := ivart;

Edit2.Text := IntToStr(UpDown1.Position);

end;

Jej część całkowita wyświetlana jest w komponencie edycyjnym Edit2. Część ułamkowa tej liczby nie została uwzględniona, gdyż odczyt taki będzie z reguły pełnić funkcję jedynie orientacyjną. Jeżeli zajdzie potrzeba ponownego jej ustalenia i tak będziemy musieli uczynić to powtórnie, korzystając z procedur obsługi zdarzeń UpDown1Click() oraz UpDown2Click(). W treści drugiego z nich zamieściłem algorytm, dzięki któremu, manipulując cechami Position komponentów TUpDown, możemy płynnie ustalać górną temperaturę grzania z wymaganą dokładnością do jednego miejsca po kropce, jednocześnie wysyłając odpowiedni rozkaz do przyrządu. Aktualne wartości cech Position odpowiednich komponentów TUpDown zostaną przypisane cechom Text komponentów edycyjnych TEdit. Rozkaz wysyłamy używając funkcji RS_Send(), której argumentem jest akceptowana przez przyrząd komenda SETP (ang. Set Point), uzupełniony o aktualne cechy Text komponentów Edit3 (reprezentuje część całkowitą liczby) i Edit2 (część ułamkowa) oraz zakończona parą znaków CR LF.

procedure TForm1.UpDown2Click(Sender: TObject; Button: TUDBtnType);

begin

if (CheckBox8.Checked = FALSE) then

begin

if (UpDown2.Position = 10) then

begin

UpDown1.Position := UpDown1.Position + 1;

UpDown2.Position := 0;

end;

if (UpDown2.Position = 0) then

begin

UpDown1.Position := UpDown1.Position;

UpDown2.Position := 0;

end;

if (UpDown2.Position < 0) then

begin

UpDown1.Position := UpDown1.Position - 1;

UpDown2.Position := 9;

end;

Edit3.Text := IntToStr(UpDown2.Position);

Edit2.Text := IntToStr(UpDown1.Position);

RS_Send(PChar('SETP'+''+Edit2.Text+'.'+Edit3.Text+#13+#10));

StartMeasure.Enabled := TRUE;

end;

end;

W bardzo podobny sposób funkcjonują zdarzenia UpDown1Click() oraz UpDown3Click(), w których ustalamy stopień szybkości grzania w stopniach na minutę. Zamiar ustalenia szybkości podgrzewania sygnalizujemy, klikając w obszar komponentu CheckBox8. Trzeba jednak dodać w tym miejscu, że wyboru skali i, ewentualnie, górnej granicy temperatury należy wykonywać przed rozpoczęciem właściwego pomiaru. Rzadko się zdarza, by ktoś wpadł na cudowny pomysł zmieniania jednostek w trakcie eksperymentu. Jeżeli jednak zajdzie taka potrzeba, proces zbierania danych musi być czasowo wstrzymany, co automatycznie związane jest ze wstrzymaniem działania wątku, w którym odbywa się główna transmisja danych. W takich przypadkach należy dać czas urządzeniu na przestrojenie się. Podobnie rzecz się ma np. z ustalaniem tempa grzania czy stopnia jego szybkości. Najpierw ustalamy stopień a dopiero potem podajemy tempo — co jest równoznaczne z włączeniem grzejnika. Musimy pamiętać o zachowaniu kolejności działań. Projektując poniższy algorytm, starałem się tak zabezpieczyć aplikację, by w danej chwili dostępne były opcje, które aktualnie mogą być wykonywane.

Wydruk 8.1. Kod modułu RS_26.pas aplikacji obsługującej kontroler temperatury

unit RS_26;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls,

Forms, Dialogs, Gauges, StdCtrls, ExtCtrls, Buttons, ComCtrls;

type

TForm1 = class(TForm)

GroupBox1: TGroupBox;

GroupBox2: TGroupBox;

GroupBox3: TGroupBox;

GroupBox4: TGroupBox;

GroupBox5: TGroupBox;

GroupBox6: TGroupBox;

GroupBox7: TGroupBox;

GroupBox8: TGroupBox;

GroupBox9: TGroupBox;

Memo1: TMemo;

Shape1: TShape;

Shape2: TShape;

Shape3: TShape;

HeaterMedium: TBitBtn;

HeaterOFF: TBitBtn;

HeaterFast: TBitBtn;

TemperatureKelvin: TBitBtn;

TemperatureCelsius: TBitBtn;

StartMeasure: TButton;

OpenComm: TButton;

SuspendMeasure: TButton;

ResumeMeasure: TButton;

CloseComm: TButton;

Bevel1: TBevel;

SaveDialog1: TSaveDialog;

CheckBox1: TCheckBox;

CheckBox2: TCheckBox;

CheckBox3: TCheckBox;

CheckBox4: TCheckBox;

CheckBox5: TCheckBox;

CheckBox6: TCheckBox;

CheckBox7: TCheckBox;

CheckBox8: TCheckBox;

RichEdit1: TRichEdit;

RichEdit2: TRichEdit;

TrackBar1: TTrackBar;

Edit1: TEdit;

Edit2: TEdit;

Edit3: TEdit;

Edit4: TEdit;

Label1: TLabel;

Label2: TLabel;

Label3: TLabel;

Label4: TLabel;

Label5: TLabel;

Label6: TLabel;

UpDown3: TUpDown;

UpDown1: TUpDown;

UpDown2: TUpDown;

Gauge1: TGauge;

StatusBar1: TStatusBar;

StaticText1: TStaticText;

procedure CloseCommClick(Sender: TObject);

procedure OpenCommClick(Sender: TObject);

procedure StartMeasureClick(Sender: TObject);

procedure FormCreate(Sender: TObject);

procedure TrackBar1Change(Sender: TObject);

procedure SuspendMeasureClick(Sender: TObject);

procedure HeaterOFFClick(Sender: TObject);

procedure HeaterMediumClick(Sender: TObject);

procedure ResumeMeasureClick(Sender: TObject);

procedure HeaterFastClick(Sender: TObject);

procedure TemperatureKelvinClick(Sender: TObject);

procedure TemperatureCelsiusClick(Sender: TObject);

procedure UpDown1Click(Sender: TObject; Button: TUDBtnType);

procedure UpDown2Click(Sender: TObject; Button: TUDBtnType);

procedure CheckBox8Click(Sender: TObject);

procedure UpDown3Click(Sender: TObject; Button: TUDBtnType);

private

{ Private declarations }

public

{ Public declarations }

end;

var

Form1: TForm1;

implementation

{$R *.DFM}

const

dcb_fBinary = $0001;

dcb_fParity = $0002;

cbInQueue = 32;

cbOutQueue = 32;

const

query_IDN : PChar = '*IDN?'+#13+#10;

query_ATYPE : PChar = 'ATYPE?'+#13+#10;

query_UNITS : PChar = 'CUNI?'+#13+#10;

query_HEATER : PChar = 'RANG?'+#13+#10;

query_RAMP : PChar = 'RAMP?'+#13+#10;

query_SETP : PChar = 'SETP?'+#13+#10;

query_HEAT : PChar = 'HEAT?'+#13+#10;

query : PChar = 'CDAT?'+#13+#10;

command_RANG0 : PChar = 'RANG 0'+#13+#10;

command_RANG2 : PChar = 'RANG 2'+#13+#10;

command_RANG3 : PChar = 'RANG 3'+#13+#10;

command_TK : PChar = 'CUNI K'+#13+#10;

command_TC : PChar = 'CUNI C'+#13+#10;

command_RAMP0 : PChar = 'RAMP 0'+#13+#10;

command_RAMP1 : PChar = 'RAMP 1'+#13+#10;

var

Buffer_O : ARRAY[0..cbOutQueue] of Char;

Buffer_I : ARRAY[0..cbInQueue] of Char;

Number_Bytes_Read : DWORD;

hCommDev : THANDLE;

lpFileName : PChar;

fdwEvtMask : DWORD;

Stat : TCOMSTAT;

Errors : DWORD;

dcb : TDCB;

intVar : LongWord;

intVarSleep : Cardinal;

bResult : BOOL;

hThread_SR : THANDLE;

ThreadID_SR: Cardinal;

OutFile : TextFile;

bResult_Yes : BOOL;

bResult_Heater : BOOL;

//--------------------------------------------------------------------

procedure TForm1.CloseCommClick(Sender: TObject);

var

iCheckProcess: Integer;

begin

iCheckProcess := MessageDlg('Zakończenie pomiaru i'+

' zamknięcie aplikacji?', mtConfirmation, [mbYes, mbNo], 0);

case iCheckProcess of

idYes:

begin

SuspendThread(hThread_SR);

if (bResult_Yes = TRUE) then

CloseFile(OutFile);

CloseHandle(hCommDev);

Application.Terminate();

end;

idNo: Exit;

end;

end;

//--------------------------------------------------------------------

function Write_Comm(hCommDev: THANDLE;

nNumberOfBytesToWrite: DWORD): Integer;

var

NumberOfBytesWritten : DWORD;

begin

if (WriteFile(hCommDev, Buffer_O, nNumberOfBytesToWrite,

NumberOfBytesWritten, NIL) = TRUE) then

begin

WaitCommEvent(hCommDev, fdwEvtMask, NIL);

Write_Comm := 1;

end

else

Write_Comm := 0;

end;

//--------------------------------------------------------------------

function Read_Comm(hCommDev: THANDLE;

Buf_Size: DWORD): Integer;

var

nNumberOfBytesToRead: DWORD;

begin

ClearCommError(hCommDev, Errors, @Stat);

if (Stat.cbInQue > 0) then

begin

if (Stat.cbInQue > Buf_Size) then

nNumberOfBytesToRead := Buf_Size

else

nNumberOfBytesToRead := Stat.cbInQue;

ReadFile(hCommDev, Buffer_I, nNumberOfBytesToRead,

Number_Bytes_Read, NIL);

Read_Comm := 1;

end

else

begin

Number_Bytes_Read := 0;

Read_Comm := 0;

end;

end;

//--------------------------------------------------------------------

procedure RS_Send (queryORcommand : PChar);

begin

repeat // transmisja komunikatu

FlushFileBuffers(hCommDev);

StrCopy(Buffer_O, queryORcommand);

until(Write_Comm(hCommDev, StrLen(Buffer_O)) <> 0);

end;

//--------------------------------------------------------------------

procedure Clean_Buffers;

var

i : Integer;

begin

for i := 0 to cbInQueue do

begin

Buffer_I[i] := ' ';

Buffer_O[i] := ' ';

end;

end;

//--------------------------------------------------------------------

procedure TForm1.OpenCommClick(Sender: TObject);

var

ivart, Code : Integer;

wResult_Save: Word;

begin

if (CheckBox1.Checked = TRUE) then

lpFileName:='COM1';

if (CheckBox2.Checked = TRUE) then

lpFileName:='COM2';

hCommDev:= CreateFile(lpFileName, GENERIC_READ or GENERIC_WRITE, 0,

NIL, OPEN_EXISTING, 0, 0);

if (hCommDev <> INVALID_HANDLE_VALUE) then

begin

SetupComm(hCommDev, cbInQueue, cbOutQueue);

dcb.DCBlength := sizeof(dcb);

GetCommState(hCommDev, dcb);

if (CheckBox3.Checked = TRUE) then

dcb.BaudRate:=CBR_300;

if (CheckBox4.Checked = TRUE) then

dcb.BaudRate:=CBR_1200;

dcb.Flags := dcb_fParity;

dcb.Parity := ODDPARITY;

dcb.StopBits :=ONESTOPBIT;

dcb.ByteSize :=7;

CheckBox5.Checked := TRUE;

CheckBox6.Checked := TRUE;

CheckBox7.Checked := TRUE;

StatusBar1.Panels[0].Text := 'Otwarty port: ' + lpFileName;

SetCommState(hCommDev, dcb);

GetCommMask(hCommDev, fdwEvtMask);

SetCommMask(hCommDev, EV_TXEMPTY);

{ -- pytanie o typ diody --}

RS_Send(query_ATYPE);

Sleep(100);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

StatusBar1.Panels[2].Text := 'Dioda typu: '+Buffer_I;

{-- pytanie o pierwszy pomiar --}

RS_Send(query);

Sleep(100);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

Form1.RichEdit1.Text := Buffer_I;

{ -- pytanie, czy ustalono górną temperaturę grzania --}

RS_Send(query_SETP);

Sleep(100);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

begin

val(Buffer_I, ivart, Code);

UpDown1.Position := ivart;

Edit2.Text := IntToStr(UpDown1.Position);

end;

{ -- pytanie o identyfikację przyrządu --}

RS_Send(query_IDN);

Sleep(1000);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

StatusBar1.Panels[1].Text := 'Identyfikacja'+

' urządzenia:' + Buffer_I;

{ -- pytanie o aktualne jednostki --}

RS_Send(query_UNITS);

Sleep(100);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

Form1.RichEdit2.Text := Buffer_I;

{-- pytanie, czy ustalono szybkość grzania --}

RS_Send(query_RAMP);

Sleep(100);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

begin

if(Copy(Buffer_I, 1, 1) = '0') then

CheckBox8.Checked := FALSE;

if(Copy(Buffer_I, 1, 1) = '1') then

CheckBox8.Checked := TRUE;

end;

{ -- pytanie, czy włączono dany stopień grzania --}

RS_Send(query_HEATER);

Sleep(100);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

begin

val(Buffer_I, ivart, Code);

if (ivart = 0) then

Shape2.Brush.Color := clBlack;

if (ivart = 2) then

Shape1.Brush.Color := clMaroon;

if (ivart = 3) then

Shape3.Brush.Color := clRed;

if (ivart <>0) then

bResult_Heater := TRUE;

end;

OpenComm.Enabled := FALSE;

UpDown1.Enabled := TRUE;

UpDown2.Enabled := TRUE;

CheckBox8.Enabled := TRUE;

{ -- pytanie o identyfikację przyrządu --}

RS_Send(query_IDN);

Sleep(1000);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

StatusBar1.Panels[1].Text := 'Identyfikacja'+

' urządzenia:' + Buffer_I;

Clean_Buffers;

wResult_Save := MessageDlg('Zapisać dane do pliku ? *.dat.',

mtCustom, [mbYes, mbCancel, mbNo], 0);

case wResult_Save of

mrYes:

begin

if (SaveDialog1.Execute) then

begin

bResult_Yes := TRUE;

AssignFile(OutFile, SaveDialog1.FileName+'.dat');

Rewrite(OutFile);

end;

end;

mrNo : Exit;

end; // koniec case

end

else

case hCommDev of

IE_BADID:

begin

Application.MessageBox('Niewłaściwa nazwa portu'+

'lub jest on aktywny', 'Uwaga !', MB_OK);

lpFileName:='';

end;

end;

end;

//--------------------------------------------------------------------

function RS_Send_Receive(P: Pointer): Integer;

var

ivart, Code : Integer;

begin

REPEAT

Clean_Buffers;

{-- pytanie o aktualną moc grzejnika [%] --}

if (bResult_Heater = TRUE) then

begin

StrCopy(Buffer_O, query_HEAT);

repeat // transmisja komunikatu

FlushFileBuffers(hCommDev);

until (Write_Comm(hCommDev, StrLen(Buffer_O)) <> 0);

Sleep(100);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0)

and (bResult_Heater = TRUE) then

begin

val(Buffer_I, ivart, Code);

Form1.Gauge1.Progress := ivart;

end;

end

else

Form1.Gauge1.Progress := 0;

{-- pytanie o aktualnie mierzoną wartość --}

StrCopy(Buffer_O, query);

repeat // transmisja komunikatu

FlushFileBuffers(hCommDev);

until (Write_Comm(hCommDev, StrLen(Buffer_O)) <> 0);

Sleep(intVarSleep);

//-------odczyt danych z portu--------

if ( Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0 ) then

begin

Form1.RichEdit1.Text := Buffer_I;

Inc(intVar); // zliczanie kolejnych pomiarów

Form1.Memo1.Lines.Add(AnsiString(IntToStr(intVar)));

if (bResult_Yes = TRUE) then

WriteLN(OutFile, intVar, ' ', Form1.RichEdit1.Text);

end

else

begin

Form1.RichEdit1.Text := 'x0'; // błędny odczyt

Beep();

end;

UNTIL(bResult = FALSE);

Result := 0;

end;

//--------------------------------------------------------------------

procedure TForm1.StartMeasureClick(Sender: TObject);

begin

if (hCommDev > 0) then

begin

Clean_Buffers;

StartMeasure.Enabled := FALSE;

ResumeMeasure.Enabled := FALSE;

UpDown1.Enabled := FALSE;

UpDown2.Enabled := FALSE;

UpDown3.Enabled := FALSE;

hThread_SR := BeginThread (NIL, 0, @RS_Send_Receive, NIL, 0,

ThreadID_SR);

end

else

Application.MessageBox('Niewłaściwa nazwa portu lub'+

' jest on aktywny ', 'Uwaga !',MB_OK);

end;

//--------------------------------------------------------------------

procedure TForm1.FormCreate(Sender: TObject);

begin

SetWindowLong(Handle, GWL_EXSTYLE, 256 or WS_EX_CLIENTEDGE);

Width := Width + 1;

TrackBar1.Position := 1000;

TrackBar1.Max := 5000;

TrackBar1.Min := 1;

TrackBar1.Frequency := 500;

OpenComm.Enabled := TRUE;

StartMeasure.Enabled := TRUE;

intVar := 0;

intVarSleep := 1000;

Shape1.Brush.Color := clBtnFace;

Shape2.Brush.Color := clBtnFace;

Shape3.Brush.Color := clBtnFace;

UpDown1.Max := 1000;

UpDown2.Min := -10;

UpDown1.Enabled := FALSE;

UpDown2.Enabled := FALSE;

UpDown2.Max := 10;

UpDown3.Min := 0;

UpDown3.Max := 99;

UpDown3.Enabled := FALSE;

CheckBox8.Enabled := FALSE;

bResult := TRUE;

bResult_Yes := FALSE;

bResult_Heater := FALSE;

SaveDialog1.Filter := 'Data files (*.dat)|*.dat|All files'+

' (*.*)|*.*';

SaveDialog1.InitialDir := ExtractFilePath(ParamStr(0));

end;

//--------------------------------------------------------------------

procedure TForm1.TrackBar1Change(Sender: TObject);

begin

intVarSleep := TrackBar1.Position; // sterowanie późnieniem

Edit1.Text := IntToStr(TrackBar1.Position + 100);

end;

//--------------------------------------------------------------------

procedure TForm1.SuspendMeasureClick(Sender: TObject);

begin

Clean_Buffers;

SuspendThread(hThread_SR);

Memo1.Lines.Add('Wstrzymanie');

ResumeMeasure.Enabled := TRUE;

UpDown1.Enabled := TRUE;

UpDown2.Enabled := TRUE;

UpDown2.Enabled := TRUE;

if (bResult_Heater = FALSE) then

Gauge1.Progress := 0;

end;

//--------------------------------------------------------------------

procedure TForm1.HeaterOFFClick(Sender: TObject);

begin

if (hCommDev > 0) then

begin

if (SuspendThread(hThread_SR) <> 0) then

begin

RS_Send(command_RANG0);

StartMeasure.Enabled := TRUE;

ResumeMeasure.Enabled := FALSE;

Shape1.Brush.Color := clBtnFace;

Shape3.Brush.Color := clBtnFace;

Shape2.Brush.Color := clBlack;

bResult_Heater := FALSE;

Clean_Buffers;

end

else

Application.MessageBox('Pomiar należy czasowo wyłączyć ',

'Uwaga !',MB_OK);

end

else

Application.MessageBox('Niewłaściwa nazwa portu lub'+

' jest on aktywny ', 'Uwaga !',MB_OK);

end;

//--------------------------------------------------------------------

procedure TForm1.HeaterMediumClick(Sender: TObject);

begin

if (hCommDev > 0) then

begin

if (SuspendThread(hThread_SR) <> 0) then

begin

RS_Send(command_RANG2);

StartMeasure.Enabled := TRUE;

ResumeMeasure.Enabled := FALSE;

Shape2.Brush.Color := clBtnFace;

Shape3.Brush.Color := clBtnFace;

Shape1.Brush.Color := clMaroon;

bResult_Heater := TRUE;

Clean_Buffers;

end

else

Application.MessageBox('Pomiar należy czasowo wyłączyć ',

'Uwaga !',MB_OK);

end

else

Application.MessageBox('Niewłaściwa nazwa portu lub'+

' jest on aktywny ', 'Uwaga !',MB_OK);

end;

//--------------------------------------------------------------------

procedure TForm1.HeaterFastClick(Sender: TObject);

begin

if (hCommDev > 0) then

begin

if (SuspendThread(hThread_SR) <> 0) then

begin

RS_Send(command_RANG3);

StartMeasure.Enabled := TRUE;

ResumeMeasure.Enabled := FALSE;

Shape1.Brush.Color := clBtnFace;

Shape2.Brush.Color := clBtnFace;

Shape3.Brush.Color := clRed;

bResult_Heater := TRUE;

end

else

Application.MessageBox('Pomiar należy czasowo wyłączyć ',

'Uwaga !',MB_OK);

end

else

Application.MessageBox('Niewłaściwa nazwa portu lub'+

' jest on aktywny ', 'Uwaga !',MB_OK);

end;

//--------------------------------------------------------------------

procedure TForm1.ResumeMeasureClick(Sender: TObject);

begin

Clean_Buffers;

ResumeThread(hThread_SR);

UpDown1.Enabled := FALSE;

UpDown2.Enabled := FALSE;

UpDown2.Enabled := FALSE;

end;

//--------------------------------------------------------------------

procedure TForm1.TemperatureKelvinClick(Sender: TObject);

var

ivart, Code : Integer;

begin

if (hCommDev > 0) then

begin

if (SuspendThread(hThread_SR) <> 0) then

begin

RS_Send(command_TK);

StartMeasure.Enabled := TRUE;

ResumeMeasure.Enabled := FALSE;

Form1.RichEdit2.Text := 'K';

Clean_Buffers;

RS_Send(query_SETP);

Sleep(1000);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

begin

val(Buffer_I, ivart, Code);

UpDown1.Position := ivart;

Edit2.Text := IntToStr(UpDown1.Position);

end;

Sleep(100);

RS_Send(query);

Sleep(100);

//-------odczyt danych z portu--------

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

Form1.RichEdit1.Text := Buffer_I;

Clean_Buffers;

end

else

Application.MessageBox('Pomiar należy czasowo wyłączyć ',

'Uwaga !',MB_OK);

end

else

Application.MessageBox('Niewłaściwa nazwa portu lub'+

' jest on aktywny ', 'Uwaga !',MB_OK);

end;

//--------------------------------------------------------------------

procedure TForm1.TemperatureCelsiusClick(Sender: TObject);

var

ivart, Code : Integer;

begin

if (hCommDev > 0) then

begin

if (SuspendThread(hThread_SR) <> 0) then

begin

RS_Send(command_TC);

StartMeasure.Enabled := TRUE;

ResumeMeasure.Enabled := FALSE;

Form1.RichEdit2.Text := 'C';

Clean_Buffers;

RS_Send(query_SETP);

Sleep(1000);

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

begin

val(Buffer_I, ivart, Code);

UpDown1.Position := ivart;

Edit2.Text := IntToStr(UpDown1.Position);

end;

Sleep(100);

RS_Send(query);

Sleep(100);

//-------odczyt danych z portu--------

if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then

Form1.RichEdit1.Text := Buffer_I;

Clean_Buffers;

end

else

Application.MessageBox('Pomiar należy czasowo wyłączyć ',

'Uwaga !',MB_OK);

end

else

Application.MessageBox('Niewłaściwa nazwa portu lub'+

' jest on aktywny ', 'Uwaga !',MB_OK);

end;

//--------------------------------------------------------------------

procedure TForm1.UpDown1Click(Sender: TObject; Button: TUDBtnType);

begin

if (CheckBox8.Checked = FALSE) then

begin

Edit2.Text := IntToStr(UpDown1.Position);

RS_Send(PChar('SETP'+''+Edit2.Text+'.'+Edit3.Text+#13+#10));

StartMeasure.Enabled := TRUE;

end;

end;

//--------------------------------------------------------------------

procedure TForm1.UpDown2Click(Sender: TObject; Button: TUDBtnType);

begin

if (CheckBox8.Checked = FALSE) then

begin

if (UpDown2.Position = 10) then

begin

UpDown1.Position := UpDown1.Position + 1;

UpDown2.Position := 0;

end;

if (UpDown2.Position = 0) then

begin

UpDown1.Position := UpDown1.Position;

UpDown2.Position := 0;

end;

if (UpDown2.Position < 0) then

begin

UpDown1.Position := UpDown1.Position - 1;

UpDown2.Position := 9;

end;

Edit3.Text := IntToStr(UpDown2.Position);

Edit2.Text := IntToStr(UpDown1.Position);

RS_Send(PChar('SETP'+''+Edit2.Text+'.'+Edit3.Text+#13+#10));

StartMeasure.Enabled := TRUE;

end;

end;

//--------------------------------------------------------------------

procedure TForm1.CheckBox8Click(Sender: TObject);

begin

if (SuspendThread(hThread_SR) <> 0) then

begin

if (CheckBox8.Checked = TRUE) then

begin

RS_Send(command_RAMP1);

Sleep(100);

UpDown1.Enabled := FALSE;

UpDown2.Enabled := FALSE;

UpDown3.Enabled := TRUE;

end;

if (CheckBox8.Checked = FALSE) then

begin

RS_Send(command_RAMP0);

Sleep(100);

UpDown1.Enabled := TRUE;

UpDown2.Enabled := TRUE;

UpDown3.Enabled := FALSE;

end;

end

else

Application.MessageBox('Pomiar należy czasowo wyłączyć ',

'Uwaga !',MB_OK);

end;

//--------------------------------------------------------------------

procedure TForm1.UpDown3Click(Sender: TObject; Button: TUDBtnType);

begin

if (SuspendThread(hThread_SR) <> 0) then

begin

Edit4.Text := IntToStr(UpDown3.Position);

RS_Send(PChar('RAMPR'+' '+Edit4.Text+#13+#10));

StartMeasure.Enabled := TRUE;

Clean_Buffers;

end

else

Application.MessageBox('Pomiar należy czasowo wyłączyć ',

'Uwaga !',MB_OK);

end;

//--------------------------------------------------------------------

end.

Przedstawiony algorytm został opracowany w celu obsługi konkretnego przyrządu pomiarowego, niemniej jednak jego budowa będzie charakterystyczna dla większości aplikacji sterujących urządzeniami, z którymi można nawiązać komunikację za pomocą uniwersalnego języka zapytań. Nie uwzględniono tu jeszcze paru funkcji, jakie mogą spełniać mierniki tej klasy. Niektóre modele mogą ponadto pracować jako woltomierze czy omomierze. Samodzielne uzupełnienie aplikacji o dodatkowe funkcje właściwe konkretnemu modelowi nie powinno sprawić zainteresowanym Czytelnikom poważniejszych problemów. Wspominaliśmy też wcześniej, przy okazji omawiania sposobów transmisji i odbioru plików, o możliwości samodzielnego wyskalowania takich przyrządów za pomocą specjalnego ciągu danych (zazwyczaj dokładnie opisanych w instrukcji obsługi), zwanych krzywymi skalowania charakterystycznymi dla danego typu czujnika (diody), w jaki zaopatrzone jest urządzenie. Jeżeli ktoś zechciałby wzbogacić swoje programy o możliwości skalowania miernika, najlepiej do tego celu użyć aplikacji wielodokumentowych — MDI (ang. Multi Document Interface). Unikniemy w ten sposób zbyt wielu okien w jednym formularzu. Nie powinniśmy też mieć żadnych problemów z ewentualnym wzbogaceniem aplikacji o elementy grafiki. Połączenie z naszym formularzem np. komponentu typu TChart byłoby już tylko formalnością. Analizując powyższy kod, na pewno też zauważymy, że wielokrotnie, być może z przesadną dokładnością czyszczone były bufory komunikacyjne. Stało się to z powodu użycia tylko dwóch uniwersalnych buforów do wszystkich operacji nadawania i odbioru.

Na powyższym przykładzie została też pokazana metoda odczytu więcej niż jednej wielkości pomiarowej zwracanej przez urządzenie. W tym wypadku były nimi: aktualna temperatura (zapytanie CDAT?) oraz stopień mocy grzania (zapytanie HEAT?). W bardzo podobny sposób można oprogramować, np. różnego rodzaju zasilacze. Przy obsłudze tego typu urządzenia z reguły interesuje nas nie tylko aktualnie mierzone napięcie. Równie ważny jest aktualny prąd. Wiele modeli takich przyrządów zwraca odpowiednie wielkości w odpowiedzi na standardowe zapytania: MEASURE:VOLTAGE? oraz MEASURE:CURRENT? Budowa aplikacji obsługującej tego rodzaju mierniki będzie bardzo podobna do zaprezentowanej w tym podrozdziale.

Aplikacja obsługująca kilka urządzeń

W rozdziale 2., przy okazji omawiania roli oprogramowania w odniesieniu do podstawowych funkcji interfejsu, wspomnieliśmy o możliwości podłączenia do jednego komputera wielu urządzeń, z których obsługą powinna sobie poradzić jedna aplikacja. W niniejszym fragmencie książki zajmiemy się tym właśnie problemem. Pokażemy jeden ze sposobów budowy tego rodzaju algorytmów na przykładzie napisanego w C++Builderze programu obsługującego znany nam już kontroler temperatury oraz precyzyjną laboratoryjną wagę elektroniczną WPS 72 firmy RADWAG. Formularz aplikacji \KODY\BUILDER\RS_10\p_RS_10.bpr widocznej na rysunku 8.2 został podzielony na dwa obszary pełniące funkcję oddzielnych paneli sterowania dla różnych urządzeń zewnętrznych.

Rysunek 8.2. Dwa urządzenia obsługiwane przez jedną aplikację - projekt p_RS_10.bpr

0x01 graphic

Z pełnym opisem funkcji obsługujących zdarzenia wykorzystywane w sterowaniu dwoma przykładowymi urządzeniami zapoznaliśmy się już w trakcie tej książki. Prezentowany sposób przydzielenia odrębnych identyfikatorów hCommDev_1 oraz hCommDev_2 dwóm różnym przyrządom podłączonym do odpowiednich łącz szeregowych, niezależnych buforów danych oraz zaprogramowanie ich pracy w dwóch niezależnych wątkach określonych odpowiednio pseudoidentyfikatorami hThread_SR_COM1 oraz hThread_SR_COM2 powoduje, że stają się one dla naszej aplikacji całkowicie rozróżnialne. Podobnie jak we wcześniejszym przykładzie, tak i tutaj pytanie o możliwość zapisu danych na dysku oraz funkcję zakładającą odpowiedni plik umieściłem w funkcjach obsługi zdarzeń OpenComm_1Click() oraz OpenComm_2Click(), otwierających odpowiednie porty szeregowe. Dane odbierane zarówno od kontrolera temperatury jak i wagi cyfrowej zapisywane są niezależnie do dwóch odrębnych plików. Pewne ważne funkcje, takie jak wstrzymywanie wątku, zamknięcie pliku czy zamknięcie portu szeregowego, zostały zdublowane dla każdego z obsługiwanych urządzeń. Bardzo często programiści postępują w ten sposób, zabezpieczając się tym samym przed próbami nieprawidłowego lub bezkrytycznego korzystania z programu (mamy ty przede wszystkim na myśli próby zamknięcia aplikacji z aktywnym portem szeregowym). Przykład kompletnego kodu aplikacji komunikującej się z dwoma różnymi urządzeniami został przedstawiony na wydruku 8.2.

Wydruk 8.2. Kod modułu RS_10.cpp aplikacji obsługującej jednocześnie kontroler temperatury oraz wagę cyfrową

//--- kompilować z borlndmm.dll oraz cc3250mt.dll --------------

//----RS_10.cpp-------------

#include <vcl.h>

#include <stdio.h>

#pragma hdrstop

#include "RS_10.h"

#pragma package(smart_init)

#pragma resource "*.dfm"

#define cbOutQueue 32 //rozmiar bufora danych wyjściowych

#define cbInQueue 32 //rozmiar bufora danych wejściowych

TForm1 *Form1;

LPCTSTR query = "CDAT?\r\n"; // zapytanie o mierzoną temperaturę

LPCTSTR query_IDN = "*IDN?\r\n"; // identyfikacja

LPCTSTR query_weight = "SI\r\n"; // wskazania wagi

LPCTSTR command_TARE = "T\r\n"; // rozkaz tarowania wagi

char Buffer_O_COM2[cbOutQueue]; // bufor danych wyjściowych

char Buffer_I_COM2[cbInQueue]; // bufor danych wejściowych

char Buffer_I_COM1[cbInQueue];

char Buffer_O_COM1[cbOutQueue];

DWORD Number_Bytes_Read; // liczba bajtów do czytania

HANDLE hCommDev_1, hCommDev_2; // identyfikatory portów

LPCTSTR lpFileName_1, lpFileName_2;

DCB dcb;

DWORD fdwEvtMask;

COMSTAT Stat;

DWORD Errors;

BOOL bResult_2 = TRUE;

BOOL bResult_1 = TRUE;

BOOL bResult_Save1, bResult_Save2;

int hThread_SR_COM2;

int hThread_SR_COM1;

unsigned uThreadID_SR_COM2;

unsigned uThreadID_SR_COM1;

Cardinal intVar2, intVar1; // liczniki pomiarów

FILE *pstream2; // wskaźnik do pliku

FILE *pstream1; // wskaźnik do pliku

//--------------------------------------------------------------------

int __fastcall Write_Comm(HANDLE hCommDev, LPCVOID lpBuffer,

DWORD nNumberOfBytesToWrite)

{

DWORD NumberOfBytesWritten;

if (WriteFile(hCommDev, lpBuffer, nNumberOfBytesToWrite,

&NumberOfBytesWritten , NULL) > 0)

{

WaitCommEvent(hCommDev, &fdwEvtMask, NULL);

return TRUE;

}

else

return FALSE;

}

//--------------------------------------------------------------------

int __fastcall Read_Comm(HANDLE hCommDev, LPVOID lpBuffer, LPDWORD

lpNumberOfBytesRead, DWORD Buf_Size)

{

DWORD nNumberOfBytesToRead;

ClearCommError(hCommDev, &Errors ,&Stat);

if (Stat.cbInQue > 0)

{

if (Stat.cbInQue > Buf_Size)

nNumberOfBytesToRead = Buf_Size;

else

nNumberOfBytesToRead = Stat.cbInQue;

ReadFile(hCommDev, lpBuffer, nNumberOfBytesToRead,

lpNumberOfBytesRead, NULL);

}

else

*lpNumberOfBytesRead = 0;

return TRUE;

}

//--------------------------------------------------------------------

__fastcall TForm1::TForm1(TComponent* Owner)

: TForm(Owner)

{

}

//---------zamknięcie COM2--------------------------------------------

void __fastcall TForm1::CloseComm_2Click(TObject *Sender)

{

SuspendThread((HANDLE)hThread_SR_COM2);

StatusBar1->Panels->Items[0]->Text = "Zamknięty port: COM2";

fclose(pstream2);

CloseHandle(hCommDev_2);

MeasureON_2->Enabled = TRUE;

OpenComm_2->Enabled = TRUE;

}

//---------zamknięcie COM1--------------------------------------------

void __fastcall TForm1::CloseComm_1Click(TObject *Sender)

{

SuspendThread((HANDLE)hThread_SR_COM1);

StatusBar1->Panels->Items[3]->Text = "Zamknięty port: COM1";

fclose(pstream1);

CloseHandle(hCommDev_1);

MeasureON_1->Enabled = TRUE;

OpenComm_1->Enabled = TRUE;

}

//--------------------------------------------------------------------

void __fastcall TForm1::FormCreate(TObject *Sender)

{

SaveDialog1->InitialDir = ExtractFilePath(ParamStr(0));

SaveDialog1->Filter = "Data files (*.dat)|*.dat|All files"

" (*.*)|*.*";

SaveDialog2->InitialDir = ExtractFilePath(ParamStr(0));

SaveDialog2->Filter = "Data files (*.dat)|*.dat|All files"

" (*.*)|*.*";

TBorderIcons temporaryBI = BorderIcons;

temporaryBI >> biMaximize;

BorderIcons = temporaryBI;

OpenComm_1->Enabled = TRUE;

OpenComm_2->Enabled = TRUE;

TrackBar2->Position = 1000;

TrackBar1->Position = 1000;

TrackBar2->Max = 5000;

TrackBar1->Max = 5000;

TrackBar2->Min = 100;

TrackBar1->Min = 200;

TrackBar2->Frequency = 500;

TrackBar1->Frequency = 500;

bResult_Save2 = FALSE;

bResult_Save1 = FALSE;

intVar1 = 0;

intVar2 = 0;

}

//--otwarcie portu COM2-----------------------------------------------

void __fastcall TForm1::OpenComm_2Click(TObject *Sender)

{

int i;

if (CheckBox2->Checked == TRUE)

lpFileName_2 = "COM2";

hCommDev_2 = CreateFile(lpFileName_2, GENERIC_READ |

GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

if (hCommDev_2 != INVALID_HANDLE_VALUE)

{

SetupComm(hCommDev_2, cbInQueue, cbOutQueue);

dcb.DCBlength = sizeof(dcb);

GetCommState(hCommDev_2, &dcb);

if (CheckBox3->Checked == TRUE)

dcb.BaudRate = CBR_300;

if (CheckBox4->Checked == TRUE)

dcb.BaudRate = CBR_1200;

if (CheckBox5->Checked == TRUE)

dcb.BaudRate = CBR_9600;

dcb.Parity = ODDPARITY; // ustawienie parzystości

dcb.StopBits = ONESTOPBIT; // bity stopu

dcb.ByteSize = 7; // bity danych

//-przykładowe ustawienia znaczników sterujących DCB-

dcb.fParity = TRUE; // sprawdzanie parzystości

dcb.fDtrControl = DTR_CONTROL_DISABLE;

dcb.fRtsControl = RTS_CONTROL_DISABLE;

dcb.fOutxCtsFlow = FALSE;

dcb.fOutxDsrFlow = FALSE;

dcb.fDsrSensitivity = FALSE;

dcb.fAbortOnError = FALSE;

dcb.fOutX = FALSE;

dcb.fInX = FALSE;

dcb.fErrorChar = FALSE;

dcb.fNull = FALSE;

SetCommState(hCommDev_2, &dcb);

GetCommMask(hCommDev_2, &fdwEvtMask);

SetCommMask(hCommDev_2, EV_TXEMPTY);

StatusBar1->Panels->Items[0]->Text = "Otwarty port: COM2";

strcpy(Buffer_O_COM2, query_IDN);

do { //-- wysyłanie zapytania

//Beep();

FlushFileBuffers(hCommDev_2);

} while (Write_Comm(hCommDev_2, Buffer_O_COM2,

strlen(Buffer_O_COM2)) == 0);

Sleep(1000);

Read_Comm(hCommDev_2, &Buffer_I_COM2[0], &Number_Bytes_Read,

sizeof(Buffer_I_COM2));

if (Number_Bytes_Read > 0)

StatusBar1->Panels->Items[1]->Text = &Buffer_I_COM2[0];

for (i = 0; i <= cbInQueue - 1; i++)

{

Buffer_O_COM2[i] = NULL;

Buffer_I_COM2[i] = NULL;

};

if (Application->MessageBox(" Zapisać dane odbierane z portu"

" szeregowego COM2 do pliku? " , "Uwaga!", MB_OKCANCEL) != IDOK)

{

Abort();

}

else

{

if (SaveDialog2->Execute())

{

bResult_Save2 = TRUE;

pstream2 = fopen(SaveDialog2->FileName.c_str(), "w+");

}

}

}

else

{

switch ((int)hCommDev_2)

{

case IE_BADID:

MessageBox(NULL, "Niewłaściwa nazwa portu lub port jest"

" aktywny.", "Błąd", MB_OK);

break;

};

}

}

//-------otwarcie portu COM1------------------------------------------

void __fastcall TForm1::OpenComm_1Click(TObject *Sender)

{

int i;

if (CheckBox1->Checked == TRUE)

lpFileName_1 = "COM1";

hCommDev_1 = CreateFile(lpFileName_1, GENERIC_READ |

GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

if (hCommDev_1 != INVALID_HANDLE_VALUE)

{

SetupComm(hCommDev_1, cbInQueue, cbOutQueue);

dcb.DCBlength = sizeof(dcb);

GetCommState(hCommDev_1, &dcb);

if (CheckBox6->Checked == TRUE)

dcb.BaudRate = CBR_4800;

if (CheckBox7->Checked == TRUE)

dcb.BaudRate = CBR_9600;

dcb.Parity = NOPARITY; // ustawienie parzystości

dcb.StopBits = ONESTOPBIT; // bity stopu

dcb.ByteSize = 8; // bity danych

//-przykładowe ustawienia znaczników sterujących DCB-

dcb.fParity = TRUE;

dcb.fDtrControl = DTR_CONTROL_DISABLE;

dcb.fRtsControl = RTS_CONTROL_DISABLE;

dcb.fOutxCtsFlow = FALSE;

dcb.fOutxDsrFlow = FALSE;

dcb.fDsrSensitivity = FALSE;

dcb.fAbortOnError = FALSE;

dcb.fOutX = FALSE;

dcb.fInX = FALSE;

dcb.fErrorChar = FALSE;

dcb.fNull = FALSE;

SetCommState(hCommDev_1, &dcb);

GetCommMask(hCommDev_1, &fdwEvtMask);

SetCommMask(hCommDev_1, EV_TXEMPTY);

StatusBar1->Panels->Items[3]->Text = "Otwarty port: COM1";

StatusBar1->Panels->Items[4]->Text = "Waga laboratoryjna WPS"

" 72";

for (i = 0; i <= cbInQueue - 1; i++)

{

Buffer_O_COM1[i] = NULL;

Buffer_I_COM1[i] = NULL;

}

if (Application->MessageBox(" Zapisać dane odbierane z portu"

" szeregowego COM1 do pliku? " , "Uwaga!", MB_OKCANCEL) != IDOK)

{

Abort();

}

else

{

if (SaveDialog1->Execute())

{

bResult_Save1 = TRUE;

pstream1 = fopen(SaveDialog1->FileName.c_str(), "w+");

}

}

}

else

{

switch ((int)hCommDev_1)

{

case IE_BADID:

MessageBox(NULL, "Niewłaściwa nazwa portu lub port jest"

" aktywny.", "Błąd", MB_OK);

break;

};

}

}

//--wysłanie zapytania i odbiór danych przez COM2---------------------

int __fastcall RS_Send_Receive_COM2(Pointer Parameter)

{

do {

strcpy(Buffer_O_COM2, query);

do { //-- wysyłanie zapytania

//Beep();

FlushFileBuffers(hCommDev_2);

} while (Write_Comm(hCommDev_2, Buffer_O_COM2,

strlen(Buffer_O_COM2)) == 0);

Sleep(Form1->TrackBar2->Position);

//-- odbiór danych

Read_Comm(hCommDev_2, &Buffer_I_COM2[0], &Number_Bytes_Read,

sizeof(Buffer_I_COM2));

if (Number_Bytes_Read > 0)

{

Form1->RichEdit2->Text = IntToStr(intVar2++);

Form1->RichEdit1->Text = Buffer_I_COM2;

if (bResult_Save2 == TRUE)

fprintf(pstream2, "%s %s\n", Form1->RichEdit2->Text,

Form1->RichEdit1->Text);

}

else

{

Beep();

Form1->RichEdit1->Text = "0x"; // błędna wartość pomiaru

}

} while (bResult_2); // koniec nadrzędnego DO

return TRUE;

}

//------synchronizacja COM2-------------------------------------------

void __fastcall TForm1::TrackBar2Change(TObject *Sender)

{

Edit1->Text = IntToStr(TrackBar2->Position); // sterowanie

// opóźnieniem

}

//----pomiar COM2-----------------------------------------------------

void __fastcall TForm1::MeasureON_2Click(TObject *Sender)

{

if (hCommDev_2 > 0) // powtórnie sprawdza czy port jest otwarty

{

OpenComm_2->Enabled = FALSE;

hThread_SR_COM2 = BeginThread (NULL, 0, RS_Send_Receive_COM2,

NULL, 0, uThreadID_SR_COM2);

MeasureON_2->Enabled = FALSE;

}

else

MessageBox(NULL, "Port nie został otwarty do transmisji.",

"Błąd", MB_OK);

}

//------wznowienie pomiaru COM2---------------------------------------

void __fastcall TForm1::MeasureResume_2Click(TObject *Sender)

{

ResumeThread((HANDLE)hThread_SR_COM2);

}

//------wstrzymanie pomiaru COM2--------------------------------------

void __fastcall TForm1::MeasureSuspend_2Click(TObject *Sender)

{

MeasureON_2->Enabled = FALSE;

SuspendThread((HANDLE)hThread_SR_COM2);

}

//--wysłanie zapytania i odbiór danych przez COM1---------------------

int __fastcall RS_Send_Receive_COM1(Pointer Parameter)

{

do {

strcpy(Buffer_O_COM1, query_weight);

do { //-- wysyłanie zapytania

//Beep();

FlushFileBuffers(hCommDev_1);

} while (Write_Comm(hCommDev_1, Buffer_O_COM1,

strlen(Buffer_O_COM1)) == 0);

Sleep(Form1->TrackBar1->Position);

//-- odbiór danych

Read_Comm(hCommDev_1, &Buffer_I_COM1[0], &Number_Bytes_Read,

sizeof(Buffer_I_COM1));

if (Number_Bytes_Read > 0)

{

Form1->RichEdit3->Text = IntToStr(intVar1++);

Form1->RichEdit4->Text = Buffer_I_COM1;

if (bResult_Save1 == TRUE)

fprintf(pstream1, "%s %s\n", Form1->RichEdit3->Text,

Form1->RichEdit4->Text);

}

else

{

Beep();

Form1->RichEdit4->Text = "0x"; // błędna wartość pomiaru

}

} while (bResult_1); // koniec nadrzędnego DO

return TRUE;

}

//------synchronizacja COM1-------------------------------------------

void __fastcall TForm1::TrackBar1Change(TObject *Sender)

{

Edit2->Text = IntToStr(TrackBar1->Position); // sterowanie

// opóźnieniem

}

//---------pomiar COM1------------------------------------------------

void __fastcall TForm1::MeasureON_1Click(TObject *Sender)

{

int i;

if (hCommDev_1 > 0) // powtórnie sprawdza, czy port jest otwarty

{

for (i = 0; i <= cbInQueue - 1; i++)

{

Buffer_O_COM1[i] = NULL;

Buffer_I_COM1[i] = NULL;

}

OpenComm_1->Enabled = FALSE;

hThread_SR_COM1 = BeginThread (NULL, 0, RS_Send_Receive_COM1,

NULL, 0, uThreadID_SR_COM1);

MeasureResume_1->Enabled = TRUE;

MeasureSuspend_1->Enabled = TRUE;

MeasureON_1->Enabled = FALSE;

}

else

MessageBox(NULL, "Port nie został otwarty do transmisji.",

"Błąd", MB_OK);

}

//----tarowanie wagi--------------------------------------------------

void __fastcall TForm1::TareClick(TObject *Sender)

{

int i;

if (hCommDev_1 > 0) // powtórnie sprawdza, czy port jest otwarty

{

SuspendThread((HANDLE)hThread_SR_COM1);

strcpy(Buffer_O_COM1, command_TARE);

do {

FlushFileBuffers(hCommDev_1);

} while (Write_Comm(hCommDev_1, Buffer_O_COM1,

strlen(Buffer_O_COM1)) == 0);

Read_Comm(hCommDev_1, &Buffer_I_COM1[0], &Number_Bytes_Read,

sizeof(Buffer_I_COM1));

if (Number_Bytes_Read > 0)

Form1->RichEdit4->Text = Buffer_I_COM1;

for (i = 0; i <= cbInQueue - 1; i++)

{

Buffer_O_COM1[i] = NULL;

Buffer_I_COM1[i] = NULL;

}

MeasureResume_1->Enabled = FALSE;

MeasureSuspend_1->Enabled = FALSE;

MeasureON_1->Enabled = TRUE;

}

else

MessageBox(NULL, "Port nie został otwarty do transmisji.",

"Błąd", MB_OK);

}

//------wstrzymanie pomiaru COM1--------------------------------------

void __fastcall TForm1::MeasureSuspend_1Click(TObject *Sender)

{

SuspendThread((HANDLE)hThread_SR_COM1);

}

//-----wznowienie pomiaru COM1----------------------------------------

void __fastcall TForm1::MeasureResume_1Click(TObject *Sender)

{

ResumeThread((HANDLE)hThread_SR_COM1);

}

//------zakończenie działania aplikacji-------------------------------

void __fastcall TForm1::EndApplicationClick(TObject *Sender)

{

switch(MessageBox(NULL, " Działanie aplikacji zostanie"

" zakończone.", "Uwaga!",

MB_YESNOCANCEL | MB_ICONQUESTION))

{

case ID_YES :

{

SuspendThread((HANDLE)hThread_SR_COM2);

SuspendThread((HANDLE)hThread_SR_COM1);

fclose(pstream2);

fclose(pstream1);

CloseHandle(hCommDev_2);

CloseHandle(hCommDev_1);

Application->Terminate();

}

case ID_CANCEL : Abort();

}

}

//--------------------------------------------------------------------

W przedstawionym przykładzie powróciliśmy do funkcji Write_Comm() oraz Read_Comm(), dla których wskaźnik do bufora danych, czyli LPVOID lpBuffer był jednym z parametrów formalnych. Jest chyba rzeczą oczywistą, że bez takiego zabiegu mielibyśmy spore trudności z jednoczesnym wykorzystaniem tych dwóch uniwersalnych funkcji przy obsłudze dwóch niezależnych przyrządów pomiarowych.

Tego rodzaju algorytmy możemy z powodzeniem stosować przy projektowaniu aplikacji obsługujących jednocześnie niewiele urządzeń. Jeżeli ktoś zechciałby skorzystać ze specjalnych kart rozszerzających lub wspomnianych w rozdziale 2. konwerterów, dających dostęp do większej liczby RS-ów, opisany algorytm siłą rzeczy może nie tyle się skomplikuje, ile poważnie wydłuży. Stanie się to głównie za sprawą konieczności każdorazowej pełnej inicjalizacji wybranego portu szeregowego w funkcji obsługi odrębnego zdarzenia. Już na wyżej przedstawionym prostym przykładzie inicjalizacji dwóch portów można było zauważyć, że te same funkcje, co prawda w różnych kontekstach, ale wywoływane były wielokrotnie. Co się stanie, gdy będziemy chcieli użyć powiedzmy 16 portów jednocześnie, które w dodatku będą pracować ze z góry zadanymi różnymi prędkościami i przy różnej długości słowa danych? Jeżeli nasza aplikacja ma być naprawdę przyjazna Użytkownikowi, nie unikniemy oczywiście umieszczenia na formularzu owych 16., odpowiednio nazwanych przycisków (lub innych komponentów). Jednak funkcja obsługi zdarzenia dla każdego z nich może być ta sama, wywoływana jedynie z odpowiednimi parametrami aktualnymi. W pierwszym przybliżeniu będą nimi na pewno: numer portu, szybkość transmisji, rodzaj parzystości, liczba bitów stopu oraz liczba bitów danych. Przykład tak skonstruowanej, bardzo uniwersalnej funkcji OpenSerialPort(), która oczywiście będzie typu HANDLE, wraz z jej przykładowymi wywołaniami zamieściłem w poniższym fragmencie kodu.

Wydruk 8.3. Szkielet uniwersalnej funkcji otwierającej i ustalającej parametry transmisji wybranego portu szeregowego wraz z jej wywołaniami z przykładowymi parametrami aktualnymi

...

HANDLE hCommDev_1, hCommDev_2; // identyfikatory portów

...

HANDLE OpenSerialPort (DWORD NumPort, DWORD BaudRate, DWORD Parity,

DWORD StopBits, DWORD ByteSize)

{

HANDLE hCommDev;

char CommName[5];

DCB dcb;

switch (NumPort) {

case 1:

strcpy (CommName, "COM1");

break;

case 2:

strcpy (CommName, "COM2");

break;

case 3:

strcpy (CommName, "COM3");

break;

case 4:

strcpy (CommName, "COM4");

break;

...

default:

return FALSE;

}

switch (BaudRate) {

case 110:

dcb.BaudRate = CBR_110;

break;

case 300:

dcb.BaudRate = CBR_300;

break;

case 600:

dcb.BaudRate = CBR_600;

break;

case 1200:

dcb.BaudRate = CBR_1200;

break;

case 2400:

dcb.BaudRate = CBR_2400;

break;

case 4800:

dcb.BaudRate = CBR_4800;

break;

case 9600:

dcb.BaudRate = CBR_9600;

break;

case 19200:

dcb.BaudRate = CBR_19200;

break;

case 38400:

dcb.BaudRate = CBR_38400;

break;

case 57600:

dcb.BaudRate = CBR_57600;

break;

case 115200:

dcb.BaudRate = CBR_115200;

break;

case 128000:

dcb.BaudRate = CBR_128000;

break;

case 256000:

dcb.BaudRate = CBR_256000;

break;

...

default:

return FALSE;

}

hCommDev = CreateFile (CommName, GENERIC_READ | GENERIC_WRITE,

0, NULL, OPEN_EXISTING, 0, NULL);

if (hCommDev != INVALID_HANDLE_VALUE)

{

SetupComm(hCommDev, cbInQueue, cbOutQueue);

GetCommState(hCommDev, &dcb);

if (Parity == ODDPARITY)

dcb.Parity = ODDPARITY;

if (Parity == NOPARITY)

dcb.Parity = NOPARITY;

...

if (StopBits == ONESTOPBIT)

dcb.StopBits = ONESTOPBIT;

if (StopBits == TWOSTOPBITS)

dcb.StopBits = TWOSTOPBITS;

...

if (ByteSize == 7)

dcb.ByteSize = 7;

if (ByteSize == 8)

dcb.ByteSize = 8;

...

dcb.fParity = TRUE;

dcb.fDtrControl = DTR_CONTROL_DISABLE;

dcb.fRtsControl = RTS_CONTROL_DISABLE;

...

SetCommState(hCommDev, &dcb);

GetCommMask(hCommDev, &fdwEvtMask);

SetCommMask(hCommDev, EV_TXEMPTY);

return hCommDev;

}

else

return FALSE;

}

//-------otwarcie portu np. COM2--------------------------------------

void __fastcall TForm1::OpenComm_2Click(TObject *Sender)

{

hCommDev_2 = OpenSerialPort (2, 1200, ODDPARITY, ONESTOPBIT, 7);

if (hCommDev_2 == 0)

MessageBox(NULL, "Niewłaściwa nazwa portu lub port jest aktywny"

" lub niewłaściwie ustalono parametry transmisji.", "Błąd", MB_OK);

}

//-------otwarcie portu np. COM1--------------------------------------

void __fastcall TForm1::OpenComm_1Click(TObject *Sender)

{

hCommDev_1 = OpenSerialPort (1, 4800, NOPARITY, ONESTOPBIT, 8);

if (hCommDev_1 == 0)

MessageBox(NULL, "Niewłaściwa nazwa portu lub port jest aktywny"

" lub niewłaściwie ustalono parametry transmisji.", "Błąd", MB_OK);

}

//--------------------------------------------------------------------

Każdorazowe wywołanie OpenSerialPort()w kontekście odpowiedniego zdarzenia jest już rzeczą bardzo prostą. Jeżeli ktoś zechce przetestować pokazany fragment kodu, zauważy też, że wprowadzona konstrukcja funkcji otwierającej wybrany port szeregowy oraz inicjalizującej jego parametry transmisji jest bardzo czuła na próby błędnego przypisania szybkości transmisji, bitów stopu czy bitów danych. Na własne potrzeby listę parametrów opisanej funkcji można oczywiście znacznie rozszerzyć również o rodzaje kontroli transmisji.

Na zakończenie powróćmy jeszcze na chwilę do aplikacji obsługujących wagę cyfrową. W przeciwieństwie do różnego rodzaju bardziej lub mniej wyszukanych przyrządów pomiarowych jest to urządzenie, z którym możemy spotkać się na co dzień, np. przy okazji wizyty w każdym dużym sklepie. Być może ktoś zastanawiał się, jak w dużej placówce handlowej może być zorganizowana kontrola sprzedaży artykułów, które należy uprzednio zważyć. Prawdopodobnie musi być do tego celu zaangażowany jakiś komputerowy system zbierania danych (oczywiście nie musi być on oparty o RS). Korzystając z tego, co już wiemy na temat sposobów realizacji transmisji szeregowej, każdy z nas taki prosty system będzie mógł samodzielnie zbudować. Powiem więcej, najważniejsze programy, które mogą być nam pomocne, mamy już opracowane. Korzystając z tej części projektu p_RS_10.bpr, który obsługuje wagę cyfrową, wykonałem cykl być może nieco zabawnych ważeń — w jakimś okresie ważyłem na przemian końcówki DB-25 oraz DB-9. Wyniki moich pomiarów były nieustannie zapisywane w trakcie doświadczenia na dysku w oddzielnym pliku, w formacie: numer pomiaru — odczyt wskazań wagi w gramach. Po każdorazowym zważeniu danej końcówki waga była tarowana specjalnym przyciskiem, który urządzenia tego typu muszą posiadać na płycie czołowej. Wykonawszy kilku takich prób (przyznam, że sprzedawcy w sklepach muszą być bardzo cierpliwi), używając jednego z ogólnie dostępnych programów graficznych, obejrzałem nasz wykres. Jest on pokazany na rysunku 8.3. Ponieważ znałem stałą czasową pomiaru, czyli przedział czasu próbkowania łącza, z dobrą dokładnością na podstawie tak otrzymanego wykresu mogłem oszacować, w jakim czasie następowało dane ważenie. Specjaliści od marketingu mogą wysnuwać z tego wnioski o częstości odwiedzin przez klientów wybranego stoiska w danym czasie (np. w ciągu określonej pory dnia), zakładając rzecz jasna, że żaden sprzedawca nie waży towaru z braku lepszego zajęcia. Oczywiście, w prawdziwym sklepie nie wystarczy jedynie coś zważyć, należy ponadto wprowadzić odpowiedni kod produktu itp. Nam jednak chodzi głównie o ideę tego procesu. Równie ważną rzeczą jest fakt, że posługując się tego typu wykresami, mamy pełną kontrolę nad ilością sprzedanego towaru. Przy końcowym rozliczeniu wagi czy kasy fiskalnej wystarczy zsumować wartości odpowiednich maksimów i odjąć od znanej wartości (w tym przypadku wagi) towaru wyłożonego do sprzedaży przed otwarciem sklepu lub stoiska.

Rysunek 8.3. Rejestracja ważeń na wadze cyfrowej

0x01 graphic

Przedstawione przykłady wykorzystania programów zbierających dane z wagi cyfrowej oraz ich analiza zostały oczywiście wykonane w dużym uproszczeniu. Tak naprawdę nigdy żaden sprzedawca tak szybko nie waży, również zamiar ważenia jest z reguły w jakiś sposób wcześniej sygnalizowany, co wbrew pozorom jeszcze bardziej upraszcza cały problem związany z pełną rozróżnialnością produktów. Ponadto, do pełnej analizy takich wykresów niezbędne są dosyć skomplikowane algorytmy matematyczne, dzięki którym otrzymuje się jeszcze bardzo wiele innych, cennych informacji wykorzystywanych przez specjalistów. Mając do dyspozycji program podobny do tego, który został przedstawiony w niniejszym podrozdziale, bez problemu można do jednego PC podłączyć większą liczbę urządzeń z możliwością nieustannego zapamiętywania ich wskazań. Przy budowie tego rodzaju systemów zbierania danych opartych na RS z reguły wykorzystuje się opisane wcześniej konwertery sygnałów łącza szeregowego (stoiska handlowe bywają czasami bardzo rozległe). Nie wpływa to jednak na fakt, iż zasady obsługi protokołu transmisji danych poprzez łącze szeregowe pozostają te same i nie powinny stanowić już dla nas tajemnicy.

Podsumowanie

W niniejszym rozdziale zostały zilustrowane przykłady praktycznego wykorzystania aplikacji obsługujących przyrządy pomiarowe poprzez łącze szeregowe RS 232C. Omówione urządzenia są typowymi, nowoczesnymi miernikami, wykorzystywanymi w systemach pomiarowych. Większość współczesnych zasilaczy, częstościomierzy, oscyloskopów czy innych wielofunkcyjnych mierników zaopatrzonych w łącze szeregowe obsługuje się w sposób, który został zaprezentowany. Jedyna różnica może polegać na wykorzystaniu innego zestawu komend lub zapytań, jednak zawsze są one dokładnie opisane w instrukcji obsługi urządzenia.

Została też przedstawiona aplikacja, za pomocą której można obsługiwać wiele urządzeń jednocześnie. Wykorzystanie uniwersalnej funkcji otwierającej i ustalającej parametry transmisji wybranego portu szeregowego może bardzo pomóc w konstrukcji nawet dosyć skomplikowanego algorytmu.

William Thomson (1824-1907) — jeden z najwybitniejszych uczonych angielskich. Był też jednym z głównych wykonawców projektu przeprowadzenia pierwszego kabla telegraficznego przez Atlantyk (1858). W roku 1892 otrzymał tytuł lorda i odtąd znany jest jako lord Kelvin.

O jakości testowanej wagi świadczy to, że była w stanie szybko zarejestrować fakt pozostawienia na szalce kawałka cyny po jednym z ważeń końcówki DB-25.

2 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

H:\Książki\!Lukasz\RS 232. Praktyczne programowanie\4 po jezykowej\R_8.doc

299



Wyszukiwarka

Podobne podstrony:
Praktyczne programowanie, R 6-04, Szablon dla tlumaczy
Praktyczne programowanie, R 5c-04, Szablon dla tlumaczy
Praktyczne programowanie, R 5b-04, Szablon dla tlumaczy
Praktyczne programowanie, R 1do4-04, Szablon dla tlumaczy
Professional Linux Programming, R-08-t, Szablon dla tlumaczy
ost str-04 , Szablon dla tlumaczy
Linux Programming Professional, r-13-01, Szablon dla tlumaczy
Linux Programming Professional, R-16-t, Szablon dla tlumaczy
Professional Linux Programming, R-12-01, Szablon dla tlumaczy
C++1 1, r00-05, Szablon dla tlumaczy
Dreamweaver 4 Dla Każdego, ROZDZ07, Szablon dla tlumaczy
Dreamweaver 4 Dla Każdego, ROZDZ03, Szablon dla tlumaczy
Doc20, Szablon dla tlumaczy
Doc04, Szablon dla tlumaczy
Doc17, Szablon dla tlumaczy
C++1 1, r01-06, Szablon dla tlumaczy
Dreamweaver 4 Dla Każdego, STR 788, Szablon dla tlumaczy

więcej podobnych podstron