Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
BulgaraCeha slovacaCroataEnglezaEstonaFinlandezaFranceza
GermanaItalianaLetonaLituanianaMaghiaraOlandezaPoloneza
SarbaSlovenaSpaniolaSuedezaTurcaUcraineana

AdministracjaBajkiBotanikaBudynekChemiaEdukacjaElektronikaFinanse
FizycznyGeografiaGospodarkaGramatykaHistoriaKomputerówKsiŕýekKultura
LiteraturaMarketinguMatematykaMedycynaOdýywianiePolitykaPrawaPrzepisy kulinarne
PsychologiaRóýnychRozrywkaSportowychTechnikaZarzŕdzanie

Przykładowe aplikacje wykorzystywane w systemach pomiarowych

komputerów



+ Font mai mare | - Font mai mic



DOCUMENTE SIMILARE

Przykładowe aplikacje wykorzystywane w systemach pomiarowych

„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.

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 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.

Aplikacja obsługująca kilka urządzeń

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.

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ó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



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 617
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved