Laboratorium wirtualne 1/Moduł 3 - ćwiczenie 3

From Studia Informatyczne

wersja beta


LABORATORIUM WIRTUALNE 1

Ćwiczenie 3 - Projekt wirtualnego oscyloskopu w środowisku LabWindows/CVI

Grafika:LW1_M3_Slajd_intro.png



Grafika:LW1_M3_Slajd01.png Celem ćwiczenia jest stworzenie wirtualnego odwzorowania przyrządu pomiarowego jakim jest oscyloskop. Przyrząd taki swoją funkcjonalnością nie może odbiegać od rozwiązań spotykanych w aparaturze autonomicznej. Co więcej jako realizacja programowa - wirtualny oscyloskop nie może być pozbawiony możliwości rozbudowy posiadanych funkcji czy też adaptacji do indywidualnych potrzeb i wygód użytkownika końcowego. Wszystkie te cechy posiadają aplikacje pełniące funkcje przyrządów pomiarowych zaprogramowanych w środowisku LabWindows/CVI.

Grafika:LW1_M3_Slajd02.png Stworzyć nowy projekt i wybrać File -> New -> *.uir.

Grafika:LW1_M3_Slajd03.png W oknie edytora płyty czołowej wybrać Create -> Command Button i utworzyć przycisk: Koniec.

Parametry:

Constant Name: KONIEC (DUŻE LITERY !)

Callback Function: Zakoncz

Control Mode: Hot


Label: Koniec

Wybrać z menu Create -> Graph -> Graph i utworzyć wyświetlacz oscyloskopowy.

Parametry:

Constant Name: PRZEBIEGT (DUŻE LITERY !)

Callback Function:

Control Mode: Indicator

Label: Przebieg czasowy


W oknie konfigurującym lewą oś Y (przycisk Left Y-axis) można wyłączyć autoskalowanie. Należy jednak w trakcie dalszego pisania programu ustawić właściwe wartości: minimalną i maksymalną.


Zapisać *.uir (Menu Arrange pozwala na uporządkowanie elementów na płycie czołowej).


Opcjonalnie

Z menu: Code -> Preferences -> Default control events wybrać:

EVENT_COMMIT, EVENT_VAL_CHANGED, EVENT_RIGHT_CLICK. Czynność ta ułatwi obsługę wybranych zdarzeń.


Z menu: Code -> Generate -> wybrać All code... . W wyświetlonym oknie konfiguracyjnym pozycje: Select panels to load and display at startup oraz Panel Variable Name pozostawić z ustawieniami domyślnymi. W polu QuitUserInterface Callbacks wybrać funkcję odpowiedzialną za zakończenie programu. W bieżącym projekcie jest to funkcja: Zakoncz.


Zatwierdzić zmiany i zapisać kod (plik *.c)


Grafika:LW1_M3_Slajd04.png Z katalogu \cviXX\instr\ (XX - zastępuje numer wersji środowiska np.: 70 lub 80) skopiować do katalogu zadania pliki: scope.c, scope.h, scope.fp. Jeśli jest ustawiony atrybut Read Only to należy go usunąć. Oryginalny sterownik przesyła bufory z danymi o rozmiarze 100. Celową jest modyfikacja (dotyczy ona funkcji sterownika scope_read_waveform), dzięki której możliwe będzie uzyskanie wektora o rozmiarze 2^n\, - w dalszym ciągu będzie to rozmiar wynoszący 1024 elementy.

Włączyć pliki: *.c, *.uir, *.h oraz scope.fp do projektu. Nazwać i zapisać projekt.


Grafika:LW1_M3_Slajd05.pngGrafika:LW1_M3_Slajd06.png Po dodaniu do projektu pliku scope.fp w menu Instrument pojawi się pozycja Sample Oscilloscope, pod którą znaleźć można 4 funkcje wchodzące w skład sterownika symulującego oscyloskop:

scope_init - inicjuje połączenie z przyrządem. scope_config - konfiguruje nastawy oscyloskopu : numer kanału, wzmocnienie toru Y, podstawę czasu oraz tryb pracy (AC, DC, GND). scope_read_waveform - na podstawie dokonanych w poprzedniej funkcji nastaw z przyrządu wczytany zostaje wektor przebiegu czasowego. scope_close - kończy komunikację z przyrządem.

Funkcję inicjującą najlepiej wywołać w programie jednorazowo w funkcji main a więc na samym początku działania programu:

//Funkcję scope_init dobrze jest wywołać przed funkcją

//DisplayPanel


scope_init (1);

DisplayPanel (panelHandle);

Podobnie funkcję kończącą współpracę z przyrządem najlepiej wywołać w programie jednorazowo w funkcji main tym razem na końcu działania programu:


//Funkcję scope_close wywołać po funkcji //DiscardPanel

DiscardPanel (panelHandle);

scope_close ();

Funkcje scope_config oraz scope_read_waferom będą wywoływane “na żądanie” użytkownika programu.


Dołączyć nagłówek scope.h do pliku źródłowego:

#include ”scope.h”


Grafika:LW1_M3_Slajd07.png Zadeklarować w kodzie funkcję, której zadaniem będzie odświeżanie ekranu,

po każdorazowej zmianie nastaw poczynionych przez użytkownika:

void Przerysuj(void);

Delkaracja powinna się znajdować na początku kodu źródłowego.

W pliku źródłowym umieścić definicję funkcji Przerysuj() :

void Przerysuj(void)

{

}

Definicję można umieścić w dowolnym miejscu w kodzie (poza ciałami innych funkcji !). Najlepiej jednak funkcję definicję Przerysuj() umieścić bezposrednio po funkcji main.

W początkowej fazie programu będzie ona wywoływać funkcje symulatora oscyloskopu ze standardowymi parametrami i wyświetlać „odczytany” na ich podstawie przebieg czasowy.


W tym celu należy w funkcji Przerysuj() umieścić wywołania: scope_config, scope_read_waveform, DeleteGraphPlot oraz PlotWaveform.

Z menu Instrument -> Sample Oscillosope wybrać Configure (funkcja Przerysuj()) i z domyślnymi parametrami wstawić do kodu (wewnątrz funkcji Przerysuj())


Ponownie z menu Instrument -> Sample Oscillosope wybrać Read Waveform (funkcja scope_read_waveform).

Zdefiniować:

  • tablicę, w której zwracany będzie wektor przebiegu czasowego (typ zmiennych w tablicy: double, rozmiar tablicy: 1024, nazwa tablicy np.: przebieg);
  • zwracany okres próbkowania (typ zmiennej: double, nazwa np.: tp);
  • zwracaną chwilę początkową przebiegu (typ zmiennej: double, nazwa np.: x0);


Z ww parametrami wstawić funkcję do kodu (wewnątrz funkcji Przerysuj())


Do funkcji Przerysuj() dodać wywołania DeleteGraphPlot oraz PlotWaveform W funkcji main po wywołaniu DisplayPanel (panelHandle); można umieścić Przerysuj();


Grafika:LW1_M3_Slajd08.png Po uruchomieniu program będzie wyglądał podobnie jak na ilustracji poniżej.

Grafika:LW1_M3_Slajd09.png Sprawdzić typ danych i zakres przyjmowanych wartości dla pierwszego parametru w funkcji scope_config (kursor umieścić na funkcji scope_config, klawiszami Ctrl+P wywołać panel funkcyjny; przejść do zmiennej Channel i klawiszem F1 wywołać okno pomocy, z którego można odczytać typ zmiennej i zakres dozwolonych wartości. Każdy argument funkcji scope_config posiada określony typ i zakres. W związku z tym typ definiowanej dla tego argumentu zmiennej musi być identyczny. Dodatkowo przełącznik znajdujący się na ekranie i przyporządkowany do danego parametru funkcji scope_config musi reprezentować zgodny z tym argumentem typ danych i posiadać dopuszczalny zakres przyjmowanych wartości.


Pierwszy parametr odpowiada za kanał oscyloskopu jest typu int i może mieć wartość 1 lub 2.


Zdefiniować globalnie zmienną typu int o nazwie przykładowo nrKanalu zainicjowaną wartością 1.

int nrKanalu = 1;

W edytorze płyty czołowej wybrać z menu Create -> Ring przełącznik służący zmianie kanału:

Parametry:

Constant Name: WYBORKANAL

Callback Function: CzytajKanal

Control Mode: Hot

Label:

Po przejściu do okna Label/Value Pairs... Wprowadzić pozycje przełącznika:

Etykieta: Kanał 1 Kanał 2

Wartość: \begin{matrix}\,\,\,\,\,\,\,\,\,\ 1 & \,\,\,\,\,\,\,\,\,\,2 \end{matrix}\,

Wartości te są zgodne z podanymi w pomocy do funkcji scope_config.

Po wprowadzeniu wymaganych zmian należy (w edytorze płyty czołowej) prawym klawiszem myszy nacisnąć w obrębie nowego przełącznika i wybrać z menu kontekstowego: Generate Control Callback


Po tej czynności w kodzie źródłowym należy w funkcji CzytajKanal wstawić wywołanie:

GetCtrlVal (panelHandle, PANEL_WYBORKANAL, &nrKanalu); a po niej: Przerysuj();

Pozostało jeszcze podmienić pierwszy argument funkcji scope_config i scope_read_waveform z wartości stałej równej 1 na zmienną o nazwie nrKanalu.

Uruchomiony program reaguje na zmianę wprowadzoną przez użytkownika.


Grafika:LW1_M3_Slajd10.pngGrafika:LW1_M3_Slajd11.pngGrafika:LW1_M3_Slajd12.png Prześledzić pozostałe argumenty funkcji scope_config i w sposób analogiczny do omówionego dla przypadku przełącznika kanałów dołączyć do programy pozostałe kontrolki:

wzmocnienie toru Y; wzmocnienie toru X - podstawa czasu; tryb pracy: AC, DC, GND.


Grafika:LW1_M3_Slajd13.png W funkcji Przerysuj(); po operacjach związanych z wyświetleniem przebiegu

w dziedzinie czasu należy wyznaczyć operacje mające na celu prezentację widma częstotliwościowego za pomocą funkcji:

int status = ReFFT (double x[], double y[], int n);

UWAGA! Funkcja wykonuje operacje “w miejscu” tzn. do argumentów zawierających pierwotnie dane wejściowe zapisywane są wyniki (dane wejściowe) funkcji. Funkcja ta pobiera jako dane wejściowe wektor wartości rzeczywistych (w ogólnym przypadku są to wartości zespolone umieszczone w dwóch wektorach) i liczbę elementów.

x - wektor wej/wyj. Jako parametr wejściowy podaje się bufor zawierający analizowany sygnał. Jako parametr wyjściowy zawiera część rzeczywistą widma Fouriera.

y - parametr wyjściowy zawierający część urojoną widma Fouriera.

n - liczba elementów, która musi spełniać zależność 2^n\,.


Pełny opis funkcji ReFFT dostępny jest poprzez pomoc środowiska LabWindows/CVI. W tym celu należy wybrać menu Help -> Contents i po otworzeniu okna pomocy ze Spisu treści wybrać Library Reference -> Advanced Analysis -> Alphabetical List of Functions -> ReFFT.


Grafika:LW1_M3_Slajd14.png Wartości widma zwracane są we współrzędnych kartezjańskich, a pożądaną postacią są współrzędne biegunowe, gdyż poszukiwana jest bezpośrednia postać widma amplitudowego.

Symbole na slajdzie: A\, - wektor widma amplitudowego, \Phi\, - wektor widma fazowego.


Grafika:LW1_M3_Slajd15.png Konwersji do układu współrzędnych biegunowych dokonuje funkcja:

int status = ToPolar1D (double x[], double y[], int n, double mag[], double phase[]);

x - wejściowy wektor zawierający część rzeczywistą transformaty Fouriera.

y - wejściowy wektor zawierający część urojoną transformaty Fouriera.

n - liczba elementów tablic wejściowych.

mag - wektor wyjściowy zawierający widmo amplitudowe.

phase - wektor wyjściowy zawierający widmo fazowe.

Pełny opis funkcji ToPolar1D znajduje się w pliku advanlys.pdf (strona 2-395).


Grafika:LW1_M3_Slajd16.png Powyższą postać widma amplitudowego można już wyświetlić w oknie oscyloskopowym (powinno być to nowe dedykowane dla widma okno). Ponieważ jednak prezentacja widma w skali liniowej ma ograniczoną zdolność do wizualizacji szerokiego zakresu amplitud składowych harmonicznych widma, przydatne jest przeskalowanie widma do skali logarytmicznej.

Grafika:LW1_M3_Slajd17.png Operację przeskalowania opisuje równanie przedstawione na slajdzie gdzie:
widmo_{log}\, - wynikowe widmo w skali logarytmicznej;
widmo_{lin}\, - widmo w skali liniowej (wektor mag);
maksimum(widmo_{lin})\, - wartość maksymalna widma liniowego.
log_{10})\, - w programie należy wykorzystać funkcję log10.

UWAGA!! Aby uniknąć błędów pojawiających się przy próbie wyznaczania logarytmu dla argumentów: 0 oraz n/0 można dokonać sztucznego „zaszumienia” sygnału wejściowego w dziedzinie czasu. Poziom szumów niech wynosi do 1% amplitudy tego sygnału.


Grafika:LW1_M3_Slajd18.png Do wyświetlenia widma warto użyć funkcji PlotWaveform. W funkcji tej w pozycji Initial X należy wstawić wartość 0 (składowa stała). W pozycji X Increment, korzystając ze znajomości okresu próbkowania t_{pr}\, należy wyznaczyć i umieścić wartość rozdzielczości widmowej: r = f_{pr}/N; (f_{pr}\, -częstotliwość próbkowania, N\, - liczba próbek w widmie). Argument Plot Style należy ustawić na vertical bar.

UWAGA!! Ponieważ dla sygnałów rzeczywistych widmo jest symetryczne należy wyświetlać jedynie jego połowę


Grafika:LW1_M3_Slajd19.pngGrafika:LW1_M3_Slajd20.png Kursory są bardzo popularnym elementem składowym wyświetlaczy ekranowych współczesnej aparatury pomiarowej. Podstawowe ich funkcje wymienione są na slajdzie.

Wszystkie parametry związane z kursorami ustawia się w oknie edycyjnym ekranu oscyloskopowego, na którym mają one zostać umieszczone.

Aby zdefiniować kursor należy przede wszystkim:

  • ekran oscyloskopowy, na którym będą włączone kursory ustawić w tryb pracy Hot.
  • operacje związane z obsługą kursorów wykonywane są w ramach funkcji CALLBACK ekranu oscyloskopowego, na którym te kursory się znajdują. Należy zatem ją zdefiniować (Callback Function)
  • konfiguracji kursorów dokonuje się po przejściu z głównego okna edycyjnego ekranu (przyciskiem Cursors) do okna Edit Cursors

Włączenie kursorów następuje poprzez wybranie ich liczby (różnej od zera). Dostępne opcje pozwalają wybrać sposób wyświetlania kursora oraz tryb jego pracy:

  • Free Form - kursor może być umieszczony w dowolnym miejscu ekranu
  • Snap to point - kursor „przykleja się” do przebiegu.

Grafika:LW1_M3_Slajd21.pngGrafika:LW1_M3_Slajd22.png Dla ekranu z przebiegiem czasowym należy zdefiniować 2 kursory. Po odczytaniu ich pozycji odpowiednie wartości wyświetlić w stosownych polach odczytowych (Numeric). Dodatkowo wyznaczyć wartości deltaX = abs(X1 - X2) oraz deltaY = abs(Y1 - Y2) i wyświetlić w dedykowanych polach odczytowych. Podobnie należy postąpić z ekranem prezentującym widmo i utworzyć 1 kursor. Po odczytaniu jego współrzędnych odpowiednie wartości wyświetlić w stosownych polach odczytowych (Numeric). Operację wyświetlania należy wykonać za pomocą znanej już funkcji SetCtrlVal. Jej opis znaleźć można w pomocy środowiska LabWindows/CVI. W tym celu należy wybrać menu Help -> Contents i po otworzeniu okna pomocy ze Spisu treści wybrać Library Reference -> User Interface Library -> Functions -> Alphabetical List of Functions -> SetCtrlVal.


Pozycje kursorów odczytuje się za pomocą funkcji:

int status = GetGraphCursor (int panelHandle, int controlID,

int cursorNumber, double *x, double *y);


Pełny opis funkcji GetGraphCursor dostępny jest poprzez pomoc środowiska LabWindows/CVI. W tym celu należy wybrać menu Help -> Contents i po otworzeniu okna pomocy ze Spisu treści wybrać Library Reference -> User Interface Library -> Functions -> Alphabetical List of Functions -> GetGraphCursor.

Jako identyfikator elementu (controlID) podaje się identyfikator ekranu oscyloskopowego, na którym znajduje się dany kursor; w polu cursorNumber podać należy numer kursora, którego pozycję funkcja ma odczytać. Wynik zwracany jest w zmiennych x oraz y (należy je zdefiniować). Kursory rozróżniane są jedynie po numerze. Dla każdego kursora należy wykonać osobne wywołanie funkcji GetGraphCursor i zdefiniować osobną parę zmiennych do przechowywania współrzędnych.



Grafika:LW1_M3_Slajd23.pngGrafika:LW1_M3_Slajd24.png Dysponując odczytem pozycji 2 kursorów na ekranie z przebiegiem czasowym można uzyskać powiększenie wybranego fragmentu. Fragment ten określony jest przez odpowiednie współrzędne kursorów.

Na rysunku przedstawiono przykładową sytuację. Obszarem zainteresowania (przeznaczonym do powiększenia) jest czerwony prostokąt, ograniczony kursorami (niebieskim i zielonym). Prostokąt ten można opisać dwiema parami współrzędnych: (X_{min}, Y_{min})\, oraz (X_{min}, Y_{min})\, oznaczonych odpowiednio zielonymi kółkami. Dysponując współrzędnymi kursorów: pierwszego (X_1,Y_1)\, oraz drugiego (X_2,Y_2)\, należy wyznaczyć: X_{min}\, , X_{max}\, , Y_{min}\, oraz Y_{max}\, . Będą one potrzebne do przeskalowania osi X\, oraz Y\, ekranu z przebiegiem czasowym. Włączaniem i wyłączaniem powiększenia niech steruje przełącznik typu Binary Switch. Jeżeli X_1=X_2 lub Y_1=Y_2 wtedy należy dezaktywować przełącznik powiększenia. W funkcji CALLBACK tego przełącznika trzeba obsłużyć dwa przypadki: włączenie i wyłączenie powiększenia. W obu należy wykorzystać funkcję:

int status = SetAxisScalingMode (int panelHandle, int controlID, int axis, int axisScaling, double min, double max);


Pełny opis funkcji SetAxisScalingMode dostępny jest poprzez pomoc środowiska LabWindows/CVI. W tym celu należy wybrać menu Help -> Contents i po otworzeniu okna pomocy ze Spisu treści wybrać Library Reference -> User Interface Library -> Functions -> Alphabetical List of Functions -> SetAxisScalingMode.

Funkcję tę należy wykonać osobno dla osi X i Y. Parametr axis dla osi X ustawić należy na bottom X axis dla osi Y musi to być left Y axis. Argument axisScaling należy ustawić następująco: dla włączenia powiększenia - manual; dla wyłączenia - auto. Działanie tego parametru wygląda następująco: manual - odczytuje współrzędne graniczne z pozycji Min oraz Max; tryb auto natomiast powoduje, że wyświetlany jest cały bufor danych taki jaki wyświetlony został funkcją PlotWaveform. Wartości Min oraz Max są ignorowane tym nie mniej nie wolno ich w funkcji opuścić.



Grafika:LW1_M3_Slajd25.png Włączanie i wyłączanie przyrządu można zrealizować poprzez przełącznik dwupozycyjny typu Binary Switch. Wszystkie elementy panelu przyrządu, które użytkownik może nastawiać, przełączać, wybierać lub naciskać należy wcześniej zmodyfikować (nie dotyczy to włącznika). W oknach właściwości należy kolejno uaktywniać opcję Initially Dimmed. Spowoduje to, iż po uruchomieniu aplikacji wszystkie elementy z zaznaczonym wyżej parametrem nie będą aktywne (slajd 25 po lewej stronie). Aktywacji tych elementów można dokonać w ramach funkcji Callback przyporządkowanej do przełącznika dwupozycyjnego.

Operacji tej można dokonać przy użyciu funkcji SetCtrlAttribute, która służy do ustawiania właściwości elementów pulpitu użytkownika. Drugi jej argument powinien zawierać identyfikator elementu podlegającego zmianie. W trzecim właściwość, która ma być zmieniona. W czwartym zaś wartość tej właściwości. Zatem po rozwinięciu drzewa własności w polu trzeciego parametru w gałęzi Control Settings... odnaleźć i wybrać należy pozycję Dimmed. Korzystając z okna podpowiedzi dla parametru czwartego ustawić wartość aktywującą przycisk. (Należy pamiętać o wstawieniu funkcji do kodu)

Pełny opis funkcji SetCtrlAttribute dostępny jest poprzez pomoc środowiska LabWindows/CVI. W tym celu należy wybrać menu Help -> Contents i po otworzeniu okna pomocy ze Spisu treści wybrać Library Reference -> User Interface Library -> Functions -> Alphabetical List of Functions -> SetCtrlAttribute.

Wywołanie funkcji musi zostać powtórzone dla każdego składnika ekranu przyrządu, który miał ustawiony atrybut Initially Dimmed.

Wartość początkową przełącznika powinno być 0 (Off). Po włączeniu (wartość 1) należy wywoływać dla każdego żądanego elementu funkcję SetCtrlAttribute. Funkcja Callback przyporządkowana dla „włącznika zasilania” może mieć postać instrukcji waunkowej if .... else . Schematyczny zapis tej instrukcji znajduje się poniżej:

if( stanPrzełącznika == 1)

{

SetCtrlAttribute
................

}

else

{

QuitUserInterface(0);
//koniec programu (w tym przypadku zbędny jest przycisk
//KONIEC i przyporządkowana mu funkcja CALLBACK

}


Grafika:LW1_M3_Slajd26.pngGrafika:LW1_M3_Slajd27.png Identycznie do sposobu utworzenia przycisku Koniec utworzyć kolejny przycisk Zapis, którego naciśnięcie spowoduje zapis danych (wektora z wygenerowanymi próbkami przebiegu) do pliku. Pomocna będzie funkcja ArrayToFile (w menu Library: Formatting and I/O Library -> File I/O). Pełny opis funkcji SetCtrlAttribute dostępny jest poprzez pomoc środowiska LabWindows/CVI. W tym celu należy wybrać menu Help -> Contents i po otworzeniu okna pomocy ze Spisu treści wybrać Library Reference -> Formatting and I/O -> Alphabetical List of Functions -> ArrayToFile.

Dla sprawdzenia poprawności zapisu można utworzyć jeszcze jeden przycisk, tym razem o nazwie Odczyt (ustawienia takie jak dla przycisku Zapis) uaktywniany dopiero po zapisie danych. W funkcji callback stowarzyszonej z tym przyciskiem należy odczytać dane z pliku korzystając z FileToArray (w menu Library: Formatting and I/O Library -> File I/O). Wyświetlenie danych na ekranie odbywa się w omówiony już sposób. Rozważyć warto stworzenie nowego programu czytającego zapisane do pliku sygnały.

Pełny opis funkcji SetCtrlAttribute dostępny jest poprzez pomoc środowiska LabWindows/CVI. W tym celu należy wybrać menu Help -> Contents i po otworzeniu okna pomocy ze Spisu treści wybrać Library Reference -> Formatting and I/O -> Alphabetical List of Functions -> FileToArray. Po odczytaniu danych z pliku należy je wyświetlić w dodatkowym ekranie oscyloskopowym.


Grafika:LW1_M3_Slajd28.png Środowisko LabWidows/CVI pozwala w bardzo szerokim zakresie rozbudować funkcjonalność przedstawionego przyrządu wirtualnego. Istnieje możliwość generacji złożonych sygnałów odkształconych a także sygnałów definiowanych przez użytkownika. Oprócz podstawowej analizy czasowej i częstotliwościowej istnieje możliwość cyfrowego przetwarzania sygnałów w oparciu o analizę czasowo-częstotliwościową np.: STFT czy analizę falkową. Dysponując odpowiednim sprzętem (karta DAQ lub interfejs GPIB) można dokonać akwizycji sygnałów rzeczywistych.