CATEGORII DOCUMENTE |
Bulgara | Ceha slovaca | Croata | Engleza | Estona | Finlandeza | Franceza |
Germana | Italiana | Letona | Lituaniana | Maghiara | Olandeza | Poloneza |
Sarba | Slovena | Spaniola | Suedeza | Turca | Ucraineana |
DOCUMENTE SIMILARE |
|
„Często powtarzam, se jeseli mosesz zmierzyć to, o czym mówisz oraz opisać to za pomocą liczb, wiesz coś o tym, ale jeseli nie jesteś w stanie opisać tego za pomocą liczb, twoja wiedza o tym jest niezadawalająca, niezalesnie 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ósnego rodzaju samopisy, dochodzimy do etapu, w którym dokonanie szybkiego i wiarygodnego pomiaru stało się niemosliwe bez wykorzystania komputera sprzęgniętego z urządzeniem pomiarowym. Wykorzystując komputer, mamy mosliwość 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 nalesy optymalnie wykorzystać. O mosliwoś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.
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 mosna odczytywać aktualnie mierzoną temperaturę, ale przede wszystkim ją stabilizować. Wykorzystując specjalnie skonstruowaną grzałkę sterowaną z wymienionego urządzenia, mamy mosliwość 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 KODYDELPHIRS_26p_RS_26.dpr, zaopatrzonego w najwasniejsze 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 |
|
Korzystając z takich aplikacji mamy mosliwość wyboru jednostek, w których odczytujemy temperaturę i ustalenia szybkości grzania (stopnie na minutę). Mamy tes mosliwość wyboru trybu pracy miernika: z wyłączoną lub z włączoną opcją grzania (grzanie szybkie lub pośrednie). Mosna równies 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 jus wcześniej komponentów oraz funkcji obsługujących transmisję szeregową. Jednak budowa algorytmu rósni się nieco od prezentowanych wcześniej, dlatego przedstawię teraz jego ogólne załosenia. 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. Tus 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ę tes, 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ą Usytkownikowi bardzo pomocne, jeseli 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 mosna 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 mosliwość 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 mosliwości zapamiętania danych na dysku nie jest być mose zbyt elegancka, niemniej jednak — co wydaje się duso wasniejsze – 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 (jeseli 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;
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;
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, zasą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, gdys odczyt taki będzie z reguły pełnić funkcję jedynie orientacyjną. Jeseli 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, mosemy 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 usywają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, se wyboru skali i, ewentualnie, górnej granicy temperatury nalesy wykonywać przed rozpoczęciem właściwego pomiaru. Rzadko się zdarza, by ktoś wpadł na cudowny pomysł zmieniania jednostek w trakcie eksperymentu. Jeseli 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 nalesy 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 ponisszy 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
public
end;
var
Form1: TForm1;
implementation
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);
RS_Send(query_ATYPE);
Sleep(100);
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
StatusBar1.Panels[2].Text := 'Dioda typu: '+Buffer_I;
RS_Send(query);
Sleep(100);
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
Form1.RichEdit1.Text := Buffer_I;
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;
RS_Send(query_IDN);
Sleep(1000);
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
StatusBar1.Panels[1].Text := 'Identyfikacja'+
' urządzenia:' + Buffer_I;
RS_Send(query_UNITS);
Sleep(100);
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
Form1.RichEdit2.Text := Buffer_I;
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;
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;
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;
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;
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 nalesy 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 nalesy 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 nalesy 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 nalesy 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 nalesy 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 nalesy 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 nalesy 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 mosna 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 powasniejszych problemów. Wspominaliśmy tes wcześniej, przy okazji omawiania sposobów transmisji i odbioru plików, o mosliwoś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. Jeseli ktoś zechciałby wzbogacić swoje programy o mosliwości skalowania miernika, najlepiej do tego celu usyć aplikacji wielodokumentowych — MDI (ang. Multi Document Interface Unikniemy w ten sposób zbyt wielu okien w jednym formularzu. Nie powinniśmy tes mieć sadnych problemów z ewentualnym wzbogaceniem aplikacji o elementy grafiki. Połączenie z naszym formularzem np. komponentu typu TChart byłoby jus tylko formalnością. Analizując powysszy kod, na pewno tes zauwasymy, se wielokrotnie, być mose z przesadną dokładnością czyszczone były bufory komunikacyjne. Stało się to z powodu usycia tylko dwóch uniwersalnych buforów do wszystkich operacji nadawania i odbioru.
Na powysszym przykładzie została tes pokazana metoda odczytu więcej nis 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 mosna oprogramować, np. rósnego rodzaju zasilacze. Przy obsłudze tego typu urządzenia z reguły interesuje nas nie tylko aktualnie mierzone napięcie. Równie wasny 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.
W rozdziale 2., przy okazji omawiania roli oprogramowania w odniesieniu do podstawowych funkcji interfejsu, wspomnieliśmy o mosliwości podłączenia do jednego komputera wielu urządzeń, z których obsługą powinna sobie poradzić jedna aplikacja. W niniejszym fragmencie ksiąski zajmiemy się tym właśnie problemem. Pokasemy jeden ze sposobów budowy tego rodzaju algorytmów na przykładzie napisanego w C++Builderze programu obsługującego znany nam jus kontroler temperatury oraz precyzyjną laboratoryjną wagę elektroniczną WPS 72 firmy RADWAG. Formularz aplikacji KODYBUILDERRS_10p_RS_10.bpr widocznej na rysunku 8.2 został podzielony na dwa obszary pełniące funkcję oddzielnych paneli sterowania dla rósnych urządzeń zewnętrznych.
Rysunek 8.2. Dwa urządzenia obsługiwane przez jedną aplikację – projekt p_RS_10.bpr |
|
Z pełnym opisem funkcji obsługujących zdarzenia wykorzystywane w sterowaniu dwoma przykładowymi urządzeniami zapoznaliśmy się jus w trakcie tej ksiąski. Prezentowany sposób przydzielenia odrębnych identyfikatorów hCommDev_1 oraz hCommDev_2 dwóm rósnym przyrządom podłączonym do odpowiednich łącz szeregowych, niezalesnych buforów danych oraz zaprogramowanie ich pracy w dwóch niezalesnych wątkach określonych odpowiednio pseudoidentyfikatorami hThread_SR_COM1 oraz hThread_SR_COM2 powoduje, se stają się one dla naszej aplikacji całkowicie rozrósnialne. Podobnie jak we wcześniejszym przykładzie, tak i tutaj pytanie o mosliwość 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ą niezalesnie do dwóch odrębnych plików. Pewne wasne funkcje, takie jak wstrzymywanie wątku, zamknięcie pliku czy zamknięcie portu szeregowego, zostały zdublowane dla kasdego 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ósnymi 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?rn'; // zapytanie o mierzoną temperaturę
LPCTSTR query_IDN = '*IDN?rn'; // identyfikacja
LPCTSTR query_weight = 'SIrn'; // wskazania wagi
LPCTSTR command_TARE = 'Trn'; // 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)
else
return FALSE;
int __fastcall Read_Comm(HANDLE hCommDev, LPVOID lpBuffer, LPDWORD
lpNumberOfBytesRead, DWORD Buf_Size)
else
*lpNumberOfBytesRead = 0;
return TRUE;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
//---------zamknięcie COM2-------- ----- ------ -----------
void __fastcall TForm1::CloseComm_2Click(TObject *Sender)
//---------zamknięcie COM1-------- ----- ------ -----------
void __fastcall TForm1::CloseComm_1Click(TObject *Sender)
void __fastcall TForm1::FormCreate(TObject *Sender)
//--otwarcie portu COM2-------- ----- ------ ----- ----- ----
void __fastcall TForm1::OpenComm_2Click(TObject *Sender)
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++)
;
if (Application->MessageBox(' Zapisać dane odbierane z portu'
' szeregowego COM2 do pliku? ' , 'Uwaga!', MB_OKCANCEL) != IDOK)
else
}
}
else
;
}
//-------otwarcie portu COM1-------- ----- ------ ---------
void __fastcall TForm1::OpenComm_1Click(TObject *Sender)
if (Application->MessageBox(' Zapisać dane odbierane z portu'
' szeregowego COM1 do pliku? ' , 'Uwaga!', MB_OKCANCEL) != IDOK)
else
}
}
else
;
}
//--wysłanie zapytania i odbiór danych przez COM2----- ----- -----------
int __fastcall RS_Send_Receive_COM2(Pointer Parameter)
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)
else
} while (bResult_2); // koniec nadrzędnego DO
return TRUE;
//------synchronizacja COM2-------- ----- ------ ----------
void __fastcall TForm1::TrackBar2Change(TObject *Sender)
//----pomiar COM2-------- ----- ------ ----- ----- ----------
void __fastcall TForm1::MeasureON_2Click(TObject *Sender)
else
MessageBox(NULL, 'Port nie został otwarty do transmisji.',
'Błąd', MB_OK);
//------wznowienie pomiaru COM2-------- ----- ------ ------
void __fastcall TForm1::MeasureResume_2Click(TObject *Sender)
//------wstrzymanie pomiaru COM2-------- ----- ------ -----
void __fastcall TForm1::MeasureSuspend_2Click(TObject *Sender)
//--wysłanie zapytania i odbiór danych przez COM1----- ----- -----------
int __fastcall RS_Send_Receive_COM1(Pointer Parameter)
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)
else
} while (bResult_1); // koniec nadrzędnego DO
return TRUE;
//------synchronizacja COM1-------- ----- ------ ----------
void __fastcall TForm1::TrackBar1Change(TObject *Sender)
//---------pomiar COM1-------- ----- ------ ----- ----- -----
void __fastcall TForm1::MeasureON_1Click(TObject *Sender)
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)
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++)
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)
//-----wznowienie pomiaru COM1-------- ----- ------ -------
void __fastcall TForm1::MeasureResume_1Click(TObject *Sender)
//------zakończenie działania aplikacji----- ----- --------- ----- -------
void __fastcall TForm1::EndApplicationClick(TObject *Sender)
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ą, se bez takiego zabiegu mielibyśmy spore trudności z jednoczesnym wykorzystaniem tych dwóch uniwersalnych funkcji przy obsłudze dwóch niezalesnych przyrządów pomiarowych.
Tego rodzaju algorytmy mosemy z powodzeniem stosować przy projektowaniu aplikacji obsługujących jednocześnie niewiele urządzeń. Jeseli 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 mose nie tyle się skomplikuje, ile powasnie wydłusy. Stanie się to głównie za sprawą konieczności kasdorazowej pełnej inicjalizacji wybranego portu szeregowego w funkcji obsługi odrębnego zdarzenia. Jus na wysej przedstawionym prostym przykładzie inicjalizacji dwóch portów mosna było zauwasyć, se te same funkcje, co prawda w rósnych kontekstach, ale wywoływane były wielokrotnie. Co się stanie, gdy będziemy chcieli usyć powiedzmy 16 portów jednocześnie, które w dodatku będą pracować ze z góry zadanymi rósnymi prędkościami i przy rósnej długości słowa danych? Jeseli nasza aplikacja ma być naprawdę przyjazna Usytkownikowi, nie unikniemy oczywiście umieszczenia na formularzu owych 16., odpowiednio nazwanych przycisków (lub innych komponentów). Jednak funkcja obsługi zdarzenia dla kasdego z nich mose być ta sama, wywoływana jedynie z odpowiednimi parametrami aktualnymi. W pierwszym przybliseniu 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 ponisszym 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)
switch (BaudRate)
hCommDev = CreateFile (CommName, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (hCommDev != INVALID_HANDLE_VALUE)
else
return FALSE;
//-------otwarcie portu np. COM2
void __fastcall TForm1::OpenComm_2Click(TObject *Sender)
//-------otwarcie portu np. COM1
void __fastcall TForm1::OpenComm_1Click(TObject *Sender)
Kasdorazowe wywołanie OpenSerialPort()w kontekście odpowiedniego zdarzenia jest jus rzeczą bardzo prostą. Jeseli ktoś zechce przetestować pokazany fragment kodu, zauwasy tes, se 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 mosna oczywiście znacznie rozszerzyć równies o rodzaje kontroli transmisji.
Na zakończenie powróćmy jeszcze na chwilę do aplikacji obsługujących wagę cyfrową. W przeciwieństwie do rósnego rodzaju bardziej lub mniej wyszukanych przyrządów pomiarowych jest to urządzenie, z którym mosemy spotkać się na co dzień, np. przy okazji wizyty w kasdym dusym sklepie. Być mose ktoś zastanawiał się, jak w dusej placówce handlowej mose być zorganizowana kontrola sprzedasy artykułów, które nalesy uprzednio zwasyć. Prawdopodobnie musi być do tego celu zaangasowany jakiś komputerowy system zbierania danych (oczywiście nie musi być on oparty o RS). Korzystając z tego, co jus wiemy na temat sposobów realizacji transmisji szeregowej, kasdy z nas taki prosty system będzie mógł samodzielnie zbudować. Powiem więcej, najwasniejsze programy, które mogą być nam pomocne, mamy jus opracowane. Korzystając z tej części projektu p_RS_10.bpr, który obsługuje wagę cyfrową, wykonałem cykl być mose nieco zabawnych waseń — w jakimś okresie wasył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 kasdorazowym zwaseniu 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, se sprzedawcy w sklepach muszą być bardzo cierpliwi), usywając jednego z ogólnie dostępnych programów graficznych, obejrzałem nasz wykres. Jest on pokazany na rysunku 8.3 . Poniewas 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 wasenie. 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, se saden sprzedawca nie wasy towaru z braku lepszego zajęcia. Oczywiście, w prawdziwym sklepie nie wystarczy jedynie coś zwasyć, nalesy ponadto wprowadzić odpowiedni kod produktu itp. Nam jednak chodzi głównie o ideę tego procesu. Równie wasną rzeczą jest fakt, se 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łosonego do sprzedasy przed otwarciem sklepu lub stoiska.
Rysunek 8.3. Rejestracja waseń na wadze cyfrowej |
|
Przedstawione przykłady wykorzystania programów zbierających dane z wagi cyfrowej oraz ich analiza zostały oczywiście wykonane w dusym uproszczeniu. Tak naprawdę nigdy saden sprzedawca tak szybko nie wasy, równies zamiar wasenia 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ósnialnoś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 mosna do jednego PC podłączyć większą liczbę urządzeń z mosliwoś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, is zasady obsługi protokołu transmisji danych poprzez łącze szeregowe pozostają te same i nie powinny stanowić jus dla nas tajemnicy.
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ósnica mose polegać na wykorzystaniu innego zestawu komend lub zapytań, jednak zawsze są one dokładnie opisane w instrukcji obsługi urządzenia.
Została tes przedstawiona aplikacja, za pomocą której mosna obsługiwać wiele urządzeń jednocześnie. Wykorzystanie uniwersalnej funkcji otwierającej i ustalającej parametry transmisji wybranego portu szeregowego mose bardzo pomóc w konstrukcji nawet dosyć skomplikowanego algorytmu.
William Thomson (1824-1907) — jeden z najwybitniejszych uczonych angielskich. Był tes 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, se była w stanie szybko zarejestrować fakt pozostawienia na szalce kawałka cyny po jednym z waseń końcówki DB-25.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 617
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved