„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[1], 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.
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
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:
if (SaveDialog1.Execute) then
bResult_Yes := TRUE;
AssignFile(OutFile, SaveDialog1.FileName+'.dat');
Rewrite(OutFile);
mrNo : Exit;
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;
REPEAT
Clean_Buffers;
{-- pytanie o aktualną moc grzejnika [%] --}
if (bResult_Heater = TRUE) then
StrCopy(Buffer_O, query_HEAT);
repeat // transmisja komunikatu
until (Write_Comm(hCommDev, StrLen(Buffer_O)) <> 0);
Sleep(100);
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0)
and (bResult_Heater = TRUE) then
val(Buffer_I, ivart, Code);
Form1.Gauge1.Progress := ivart;
end
else
Form1.Gauge1.Progress := 0;
{-- pytanie o aktualnie mierzoną wartość --}
StrCopy(Buffer_O, query);
Sleep(intVarSleep);
//-------odczyt danych z portu--------
if (Read_Comm(hCommDev, SizeOf(Buffer_I)) > 0) then
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);
Form1.RichEdit1.Text := 'x0'; // błędny odczyt
Beep();
UNTIL(bResult = FALSE);
Result := 0;
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);
UpDown1.Position := ivart;
Edit2.Text := IntToStr(UpDown1.Position);
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);
if (CheckBox8.Checked = FALSE) then
if (UpDown2.Position = 10) then
UpDown1.Position := UpDown1.Position + 1;
UpDown2.Position := 0;
if (UpDown2.Position = 0) then
UpDown1.Position := UpDown1.Position;
if (UpDown2.Position < 0) then
UpDown1.Position := UpDown1.Position - 1;
UpDown2.Position := 9;
Edit3.Text := IntToStr(UpDown2.Position);
RS_Send(PChar('SETP'+''+Edit2.Text+'.'+Edit3.Text+#13+#10));
StartMeasure.Enabled := TRUE;
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 }
Form1: TForm1;
implementation
{$R *.DFM}
const
dcb_fBinary = $0001;
dcb_fParity = $0002;
cbInQueue = 32;
cbOutQueue = 32;
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;
Ksiundz