Podstawy programowania aplikacji w środowisku graficznym LabView.
Budowa i zasada działania programu VI.

Spis treści:

1. Podstawowe składniki programu VI.
2. Zasady wykonywania kodu programu.
3. Konstrukcje sterujące - sekwencje, konstrukcje wyboru, pętle, formuły.
4. Zmienne lokalne i globalne.

1. Podstawowe składniki programu VI.

Program LabVIEW jest nazywany przyrządem wirtualnym ( virtual instrument ) lub krótko programem VI. Określenie to wynika z analogi wizualnego obrazu aplikacji oraz jej działania do fizycznego przyrządu takiego jak oscyloskop czy multimetr. Każda aplikacja VI używa elementy nastawcze, które wprowadzają dane z interfejsu użytkownika lub innych źródeł oraz elementy prezentacyjne, które wyświetlają dane wyjściowe na pulpicie interfejsu użytkownika lub wyprowadzają je do innych odbiorców. Program VI składa się z trzech komponentów:

- Pulpitu (front panel), który pełni rolę interfejsu użytkownika;

- Diagramu (block diagram), który jest graficznym zapisem kodu programu. Definiuje funkcjonalność aplikacji w języku graficznym G.

- Ikony i złącza (icon & connector pane). Ikona identyfikuje VI tak, że dany VI można użyć w innym programie VI. Tak użyty program VI jest określany podprogramem VI ( subVI ). Odpowiada on procedurze w tekstowych językach programowania. Złącze ( connector pane ) definiuje wejścia i wyjścia podprogramu oraz ich przyporządkowanie i odpowiada definicji argumentów procedury w językach tekstowych.

Rys.1. Pulpit aplikacji VI.

Pulpit jest graficzną formą interfejsu użytkownika. Wizualnie odpowiada rozwiązaniom płyt przednich urządzeń pomiarowych. Buduje się go za pomocą elementów kontrolnych i prezentacyjnych, które stanowią odpowiednio końcówki wejściowe i wyjściowe programu VI. Elementami kontrolnymi są pokrętła nastawcze, przyciski, przełączniki itp. Symulują one elementy nastawcze fizycznych urządzeń i dostarczają danych wejściowych do diagramu VI. Do elementów prezentacyjnych zalicza się wyświetlacze numeryczne, alfanumeryczne, LED-y, wyświetlacze graficzne (wykresy), tabele itp. Symulują one elementy prezentacyjne fizycznych urządzeń i wyświetlają dane dostarczane przez diagram.

Rys.2. Diagram aplikacji VI.

Diagram jest zapisem kodu programu w języku graficznym G. Do zapisu programu wykorzystuje się końcówki ( terminals ), węzły ( nodes ), przewody ( wires ) oraz konstrukcje sterujące ( structures ):

- Końcówki są portami wejściowymi i wyjściowymi przesyłającymi informacje pomiędzy pulpitem i diagramem. Dane wprowadzone do elementów kontrolnych pulpitu wpływają do diagramu przez końcówki wejściowe (kontrolne). Dane wpływające do końcówek wyjściowych (wskaźnikowych) opuszczają diagram i przechodzą do odpowiednich elementów prezentacyjnych pulpitu. Końcówka jest generowana automatycznie w diagramie w momencie postawienia elementu kontrolnego lub prezentacyjnego na pulpicie. Reprezentuje ona port do określonego elementu pulpitu oraz typ danych wchodzących lub wychodzących, np. DBL -double.

- Węzły są obiektami wyposażonymi w wejścia i wyjścia danych realizującymi określone operacje podczas działania programu. W języku graficznym pełnią podobne role jak wyrażenia, operatory, funkcje i procedury w językach tekstowych.

- Przewody służą do budowania połączeń (dróg przepływu danych) pomiędzy elementami diagramu. Każde połączenie może mieć tylko jedno źródło danych lecz może być rozgałęzione do wielu punktów odbioru danych. W zależności od typu przesyłanych danych połączenia są wyróżniane kolorami, stylem linii łączącej oraz jej grubością. Złe połączenia są zaznaczane czarną linią przerywaną.

- Konstrukcje sterujące języka graficznego zapewniają specyficzny sposób wykonania fragmentów kodu. Pozwalają realizować powtarzanie bloków kodu (pętle), warunkowy wybór wykonania bloków kodu (case) lub określić sekwencję wykonania bloków kodu.

Rys.3. Ikona i złącze aplikacji VI.

Ikony i złącza programu VI. Jeśli opracowany program VI ma służyć jako podprogram dla innych aplikacji VI, konieczne jest zbudowanie unikalnej ikony stanowiącej jego graficzną reprezentację oraz zdefiniowanie złącza. W diagramach aplikacji wykorzystujących taki podprogram występuje on w postaci zdefiniowanej dla niego ikony. Złącze definiuje wejścia i wyjścia podprogramu i tym samym umożliwia wykonanie odpowiednich połączeń w diagramie programu wykorzystującego go jako podprogram. Złącze jest zestawem końcówek odpowiadających określonym elementom kontrolnym i prezentacyjnym danego podprogramu. Zaciski wejściowe złącza przekazują dane do diagramu podprogramu za pośrednictwem elementów kontrolnych pulpitu podprogramu. Z kolei zaciski wyjściowe złącza otrzymują dane z diagramu za pośrednictwem elementów prezentacyjnych pulpitu podprogramu.

2. Zasady wykonywania kodu programu.

Końcówki i węzły połączone liniami przepływu danych tworzą diagram programu VI (rys.4). Rozpoczyna się on końcówkami wejściowymi elementów kontrolnych pulpitu (np. Input 1 i 2) oraz węzłami stałych (np. Const DBL), które są źródłem stałych wartości określonego typu. Linie łączące źródła danych oraz odbiorniki wyznaczają drogi przepływu danych. Zbiegają się w wielowejściowych węzłach (np. w węźle Subtract), jak również rozgałęziają doprowadzając dane z określonego źródła do wejść różnych węzłów (np. wyjście węzła Decrement).

Rys.4. Diagram aplikacji VI.

Każdy węzeł diagramu wykonuje tylko raz swoje operacje. Kolejność wykonania operacji przez węzły diagramu jest określona przepływem danych (data flow). Węzeł diagramu rozpoczyna działanie po otrzymaniu wszystkich danych wejściowych. Po wykonaniu charakterystycznych dla niego operacji dostarcza wyniki na swoje wyjścia. Przewody przenoszą dane wyjściowe do następnych węzłów diagramu, które rozpoczną działanie po uzyskaniu wszystkich danych wejściowych.

Rys.5. Kolejne etapy (1,2,3) wykonania prostego diagramu VI.

Wykonywanie diagramu można rozpatrywać jako kolejne fazy opracowania danych przez węzeł i ich przekazania do węzłów odbierających. Zasady obowiązujące dla każdej z nich można ująć następująco:

- Węzeł rozpoczyna działanie po otrzymaniu wszystkich danych wejściowych.

- Dane na wyjściach węzła (węzeł może mieć kilka wyjść) pojawiają się jednocześnie po wykonaniu charakterystycznych dla niego operacji.

- Uzyskane dane wyjściowe są jednocześnie dostarczone do węzłów odbierających.

Diagram z rys.5 rozpoczyna działanie od jednoczesnego dostarczenia danych z terminali wejściowych do węzła sumowania oraz wartości stałej do węzła dzielenia (rys.5-1). Teraz działanie podejmie węzeł sumowania (rys.5-2), ponieważ tylko on ma komplet danych wejściowych. Po wykonaniu sumowania działanie podejmie węzeł dzielenia, który dostarczy daną do końcówki wyjściowej (rys.5-3).

Rys.6. Przykład kolejności wykonania węzłów diagramu VI z równoległymi gałęziami
(numery przy węzłach podają kolejność wykonania).

W przypadku prostego diagramu określenie kolejności wykonywanych operacji nie budzi istotnych wątpliwości. Rzeczywiste diagramy są bardziej złożone. Mogą mieć wiele rozgałęzień dróg przepływu danych jak również węzłów korzystających z wielu wejść. W rezultacie może on mieć wiele równoległych gałęzi obejmujących szeregowo połączone węzły (rys.6). LabView jest środowiskiem wielozadaniowym i może wykonywać pseudo-jednocześnie wiele operacji. Węzły, które otrzymają w danej chwili wszystkie dane są wykonywane pseudo-jednocześnie. Nie można jednak zakładać kolejności wykonania operacji przez węzły, które uzyskały stan gotowości. Środowisko samo decyduje o kolejności wykonania stosując technikę arbitralnego przeplotu. W ten sposób węzły lub grupy węzłów różnych gałęzi są wykonywane przemiennie co skutkuje równoległością ich realizacji.

Program VI może składać się z wielu niezależnych poddiagramów (rys.7). Podobnie jak równoległe gałęzie diagramu, są one wykonywane współbieżnie. Również w tym przypadku nie można zakładać kolejności wykonania operacji przez węzły będące w stanie gotowości. Technika wyboru z przeplotem gwarantuje jednak, że niezależne poddiagramy są wykonywane przemiennie co skutkuje równoległością ich realizacji (rys.7). Przytoczona kolejność wykonania węzłów diagramu z rys. 7 może być inna w kolejnym wykonaniu.

Rys.7. Przykład kolejności wykonania węzłów dwóch niezależnych poddiagramów programu VI
(numery przy węzłach podają kolejność wykonania).

Węzły diagramu jednocześnie uzyskujące komplet danych wejściowych wykonują się w nieznanej programiście kolejności. Jeżeli istotna jest kolejność ich wykonania to musi to zapewnić przepływ danych pomiędzy nimi. Konieczność zapewnienia określonej sekwencji działań dotyczy między innymi operacji plikowych lub operacji na urządzeniach pomiarowych, np. najpierw operacja przesłania zapytania do urządzenia a następnie operacja odczytu odpowiedzi z urządzenia. Stąd węzły funkcji I/O posiadają wejścia deskryptorów i błędów oraz takie same wyjścia. Po wykonaniu operacji, taki węzeł dostarcza między innymi danych wyjściowych na wyjście deskryptora i wyjście błędu. Połączenie tych wyjść z odpowiadającymi wejściami kolejnego podobnego węzła zapewnia żądaną kolejność ich wykonania wynikającą z przepływu danych (rys.8).

Rys.8. Przykład zapewnienia kolejności wykonania węzłów funkcji I/O
za pomocą przepływu danych (Deskryptor i błąd).

Jeśli wymaganej kolejności nie daje się uzyskać bezpośrednio z przepływu danych trzeba zastosować specjalne konstrukcje sterujące języka G. Dotyczy to uzyskania żądanej sekwencji operacji w warunkach, gdy nie daje się jej zrealizować przepływem danych a także tworzenia pętli wykonujących wielokrotne powtarzanie operacji przez określone fragmenty kodu oraz warunkowej realizacji określonych fragmentów kodu.

3. Konstrukcje sterujące graficznego języka programowania G.

Konstrukcja sekwencji operacji.

Konstrukcja sekwencyjna wygląda jak ramka filmu i składa się z jednej lub kilku ramek ( klatek filmu ). Wykonuje ona kolejno kody programu umieszczone w ramkach 0, 1, 2, 3 itd. Konstrukcje sekwencyjne stosuje się do wymuszenia określonej kolejności wykonywania fragmentów kodu, gdy nie daje się tego uzyskać przepływem danych. Diagramy kodu umieszczone w ramkach konstrukcji są realizowane kolejno, zgodnie z numerami ramek. Sekwencję można rozbudowywać do dowolnej liczby ramek.

Tunele wejść i wyjść konstrukcji tworzą się automatycznie podczas prowadzenia połączeń przez kontury konstrukcji. Dane wejściowe konstrukcji są dostępne dla wszystkich jej ramek. Wyjścia danych z konstrukcji mogą mieć tylko jedno źródło informacji, czyli każde z wyjść struktury jest związane z jedną z ramek, ale tunele wyjściowe są widoczne we wszystkich ramkach. Dane opuszczają strukturę w momencie, gdy ostatnia ramka zakończy wykonanie zawartego w niej kodu. Oznacza to, że dane wyjściowe określonej ramki opuszczają strukturę po jej całkowitym wykonaniu, a nie kiedy skończy się wykonanie danej ramki.

Rys.9. Przykład konstrukcji sekwencyjnej oraz kolejne jej ramki.

Przekazywanie danych z jednej ramki do ramek występujących po niej realizuje się za pomocą terminala zwanego lokalną sekwencją. Do uzyskania lokalnej sekwencji, wykorzystuje się operację Add Sequence Local ze specjalnego menu, uaktywnianego przyciśnięciem prawego klawisza myszki. Lokalna sekwencja jest zaznaczona końcówką przenoszenia danych we wszystkich ramkach sekwencji. W ramce będącej źródłem danych dla lokalnej sekwencji punkt przekazywania danych jest zaznaczony zewnętrznym zwrotem strzałki a w ramce odbiorczej strzałką zwróconą do jej wnętrza. Ramki poprzedzające ramkę będącą źródłem danych nie mogą oczywiście korzystać z danych przenoszonych lokalną sekwencją i w nich punkt przenoszenia nie jest zaznaczony strzałką. Konstrukcja sekwencyjna może wykorzystywać kilka lokalnych sekwencji przekazywania danych.

Rys.10. Ramki konstrukcji sekwencyjnej z lokalną sekwencją przekazywania danych.

Rys.10 pokazuje cztero-klatkową konstrukcję z lokalną sekwencją przekazywania danej wypracowanej przez diagram klatki numer 1. Kolejne klatki (tutaj 2 i 3) dostają daną za pośrednictwem terminala wejściowego lokalnej sekwencji ale nie muszą jej wykorzystywać. Ramka numer 0 wykonuje się przed ramką numer 1 i nie może korzystać z terminala sekwencji lokalnej.

Konstrukcja sekwencyjna pozwala szeregować wykonywanie różnych operacji. Warto ją stosować, jeśli nie daje się tego uzyskać przepływem danych. Rysunek 11 pokazuje jej zastosowanie do odmierzenia czasu wykonania pewnego kodu (np. zbudowanego podprogramu subVI), który należy umieścić w ramce 0 pokazanej konstrukcji. Tunel wejściowy dostaje stan zainicjalizowanego licznika milisekund. Po wykonaniu ramki 0, rozpoczyna się realizacja ramki 1. Węzeł odejmowania dostaje aktualny stan licznika oraz stan początkowy z tunelu wejściowego. Różnica jest czasem wykonania diagramu z ramki 0 ( dokładność pomiaru zależy od wielu czynników! ).

Rys.11. Przykład wykorzystania konstrukcji sekwencyjnej do odmierzenia czasu
wykonania kodu umieszczonego w ramce 0.

Konstrukcja wyboru (case).

Konstrukcja sterująca case umożliwia alternatywne wykonywanie bloków kodu objętych tą konstrukcją. Funkcjonalnie odpowiada instrukcji if...then...else lub switch języka C. Konstrukcja posiada minimum dwie ramki. Każda ramka zawiera blok programowy realizujący określone operacje oraz deklarację wartości wybierających (rys.12). Wykonanie konstrukcji polega na wykonaniu kodu jednej z jej ramek. Wybór ramki jest realizowany na podstawie danej dostarczonej do wejścia selekcyjnego konstrukcji case.

Rys.12. Postać graficzna konstrukcji case.

Wejście selektora może przyjmować dane boolowskie (domyślny typ), całkowite, stringowe oraz enumeryczne. W przypadku, gdy wejście selektora korzysta z danych boolowskich konstrukcja posiada dwie ramki odpowiednio dla wartości FALSE i TRUE. Jeśli selektor korzysta z pozostałych typów danych struktura może mieć do 2^32-1 przypadków (ramek).

Rys.13. Konstrukcja case z selektorem Int32 oraz przykładem deklaracji wartości wybierających.

Dla każdej z możliwych wartości selektora musi być przypisana jedna z ramek konstrukcji case. Określenie wartości wybierających daną ramkę realizuje się przez wpisanie ich listy w okienku wartości wybierających. W konstrukcji case z selektorem boolowskim ramkom przypisane są wartości True i False. W sytuacji wyboru przy użyciu danych całkowitych pole wartości wybierającej daną ramkę może mieć postać:

- Pojedynczej wartości całkowitej, np. 12 - ramka jest wykonywana, gdy selektor konstrukcji otrzyma wartość 12.

- Listy wartości całkowitych, np. 2, 4, 6, 7 - ramka jest wykonywana, gdy selektor konstrukcji otrzyma jedną z wartości podanej w liście.

- Listy wartości określonych zakresem, np. 5..25 - ramka jest wykonywana, gdy selektor konstrukcji otrzyma wartość z podanego zakresu.

- Wartości domyślnej (Default) - ramka jest wykonywana, gdy selektor konstrukcji otrzyma wartość różną od wartości określonych dla innych ramek konstrukcji.

Konstrukcja wyboru musi obsłużyć wszystkie możliwe przypadki. Zatem przy wyborze różnym od boolowskiego, kiedy liczba możliwych przypadków jest bardzo duża, jednej z ramek należy przypisać wszystkie wartości nieokreślone dla pozostałych ramek. Przykładowo w konstrukcji złożonej z czterech ramek wybieranych odpowiednio wartościami 1, 2, 3, 4 należy jednej z nich np. pierwszej przypisać wartości wybierające 1,Default. Oznacza to, że ramka pierwsza będzie wykonywana, gdy selektor uzyska wartość całkowitą różną od 2, 3 lub 4.

Tunele wejść i wyjść konstrukcji tworzą się automatycznie podczas prowadzenia połączeń przez kontury konstrukcji. Tunele wejść danych tworzą wejścia dla wszystkich ramek konstrukcji, ale diagram danej ramki korzysta tylko z tych, które są potrzebne. Struktura realizuje swoje zadania po uzyskaniu danych na wszystkich wejściach. Wszystkie wejścia danych oraz wejście selektora muszą być dołączone do źródeł danych.

Każda ramka struktury definiuje wyjścia danych, które są wyjściami struktury. Wyjście danych utworzone w jednej ramce jest widoczne w innych ramkach struktury. Struktura po wykonaniu swoich zadań musi dostarczyć dane na wszystkich wyjściach, również tych, które dotyczą wyjść z ramek aktualnie nie realizowanych. Dlatego diagram danej ramki musi dostarczyć dane na wszystkie wyjścia struktury, także utworzone dla potrzeb innej ramki i formalnie nieistotne dla algorytmu realizowanego dla danego przypadku. Wartość danej dostarczanej na takie wyjście zależy od ogólnej koncepcji rozwiązania danego programu; często wyprowadza się stałe określonego typu. Oczywiście, jeśli ramki struktury wyprowadzają dane tego samego typu i rozmiaru to mogą korzystać z tego samego wyjścia (rys.14).

Rys.14. Konstrukcja case w zastosowaniu do obliczania pierwiastka kwadratowego,
oba przypadki wykorzystują ten sam tunel wyjściowy.

Konstrukcje pętli programowych (for i while).

Konstrukcję pętli for stosuje się w celu cyklicznego wykonania wybranego bloku kodu, gdy liczba wymaganych powtórzeń (iteracji) wykonania jest znana w danym miejscu programu. W środowisku LabVIEW pętla for ma postać ramki obejmującej poddiagram stanowiący blok kodu programu, który ma być wykonany określoną ilość razy.

Rys.15. Konstrukcja pętli for.

Ramka pętli for posiada predefiniowane wejście liczby iteracji oraz terminal wyjścia licznika iteracji. Wejście liczby iteracji jest typu long integer. Dołączenie danej numerycznej zmiennoprzecinkowej do wejścia liczby iteracji spowoduje zaokrąglenie tej liczby do wartości całkowitej. Licznik iteracji jest zerowany w momencie rozpoczęcia działania pętli, dostarcza aktualny numer wykonywanej iteracji (od 0 do N-1) i jest inkrementowany po każdej iteracji. Sprawdzenie warunku zakończenia (i<N) jest wykonywane przed rozpoczęciem kolejnej iteracji, dlatego dla N=0 pętla nie wykonana ani razu swojego diagramu i zakończy działanie.

Rys.16. Konstrukcja pętli while.

Konstrukcję pętli while stosuje się w celu cyklicznego wykonania wybranego bloku kodu, gdy liczba wymaganych powtórzeń (iteracji) wykonania nie jest znana. Pętla while ma postać ramki obejmującej diagram stanowiący blok kodu programu, którego wykonanie jest powtarzane aż do momentu przerwania działania pętli. Ramka pętli while posiada predefiniowany terminal wyjścia licznika iteracji oraz terminal warunku kontynuowania działania pętli. Licznik iteracji jest zerowany w momencie rozpoczęcia działania pętli, dostarcza aktualny numer wykonywanej iteracji (od 0) i jest inkrementowany po każdej iteracji. Terminal kontynuacji działania pętli korzysta z wartości logicznych (boolowskich) wypracowanych przez diagram pętli. Konfigurowanie terminala kontynuacji pozwala ustalić wartość logiczną przerywającą działanie pętli (Continue IF True lub Stop If True). Sprawdzenie warunku zakończenia jest realizowane po wykonaniu każdej iteracji, dlatego zawsze jest wykonana przynajmniej jedna iteracja pętli. Działanie pętli jest podobne jak pętli do...while języka C.

Każda iteracja pętli polega na wykonaniu bloku kodu objętego ramką konstrukcji pętlowej. Blok kodu może być pojedynczym diagramem lub zestawem niezależnych poddiagramów (bez przepływu danych między nimi). Niezależne poddiagramy pętli są wykonywane równolegle. W jednej iteracji każdy z nich jest wykonywany tylko raz. Jeżeli jeden z takich poddiagramów zawiera pętlę (pętla zagnieżdżona) to kolejne iteracje pętli zewnętrznej rozpoczną się po wykonaniu pętli zagnieżdżonej. Czas realizacji poddiagramu z pętlą może być długi i wówczas pozostałe poddiagramy wykonają swoje zadania i pozostają zawieszone do momentu rozpoczęcia nowej iteracji przez pętlę zewnętrzną.

Tunele wejściowe i wyjściowe konstrukcji pętlowych, auto-indeksacja.

Konstrukcje pętli for i while mogą posiadać wejścia i wyjścia danych. Tunel wejściowy danych tworzy się automatycznie w momencie prowadzenia połączenia pomiędzy wyjściem węzła znajdującego się na zewnątrz pętli a wejściem węzła poddiagramu pętli. Podobnie tunel wyjścia danych powstaje podczas łączenia wyjścia węzła poddiagramu pętli z wejściem węzła znajdującego się poza konstrukcją pętli.

Rys.17. Pętla for i while z tunelami wejściowymi.

Pętla rozpoczyna działanie po otrzymaniu wszystkich danych wejściowych. W tym momencie są one też przekazywane do pętli i ich wartości są takie same we wszystkich iteracjach pętli. Jeśli zatem do tunelu wejściowego pętli jest dołączony terminal obiektu nastawczego to pętla dysponuje jego stanem uzyskanym w momencie rozpoczęcia swojego działania. Zatem zmiany stanu tego obiektu nastawczego w czasie działania pętli nie są w niej zauważane. Jeśli pętla ma reagować na aktualny stan obiektu nastawczego, to jego terminal musi być umieszczony wewnątrz pętli. Wtedy każda iteracja pętli dysponuje aktualnym stanem nastawnika.

W przypadku tuneli wejściowych przekazujących tablice można ustalić charakter wejścia tak, aby pętla uzyskała całą tablicę (Disable Indexing) lub określone elementy tablicy dla kolejnych iteracji (Enable Indexing). Auto-indeksowanie wejściowe rozpoczyna się od zera i polega na przekazaniu kolejnym iteracjom pętli danych o rozmiarze zmniejszonym o 1 z N wymiarowej tablicy wejściowej (rozkładanie tablicy). W przypadku tablicy N wymiarowej iteracje dostają kolejne podtablice o rozmiarze N-1. Iteracja i otrzymuje i-ty skalarny element z jednowymiarowej tablicy, i-tą jednowymiarową tablicę z dwuwymiarowej tablicy, itd.

Rys.18. Rozbiór tablicy na elementarne składniki za pomocą wejść z auto-indeksacją.

Wejście z auto-indeksowaniem wpływa na liczbę iteracji pętli for, która jest wtedy określona przez wartość z wejścia liczby iteracji lub rozmiaru tablicy wejściowej. Obowiązuje mniejsza z tych wartości. Wejście liczby iteracji może pozostać niepołączone, jeśli pętla korzysta z auto-indeksowanego wejścia. W przypadku kilku wejść z auto-indeksowaniem liczba iteracji jest określona rozmiarem najmniejszej tablicy. Stąd jeśli dwie tablice 20- i 50-elementowa poprzez wejścia z auto-indeksacją inicjują działanie pętli for z zadeklarowaną liczbą 25 iteracji, to pętla realizuje 20 iteracji wykorzystując w nich wszystkie elementy tablicy pierwszej oraz 20 pierwszych elementów tablicy drugiej.

Auto-indeksowanie dotyczy także tuneli wejściowych pętli while (rys.19). Obowiązują te same zasady z wyjątkiem ograniczenia liczby iteracji. Rozmiar tablicy nie ma wpływu na liczbę iteracji pętli while, ponieważ są one realizowane dopóki terminal przerwania otrzymuje określoną wartość boolowską. Kiedy liczba iteracji przekroczy rozmiar tablicy, wejście dostarcza wartości domyślne tego samego typu jak dostarczane dotąd. W przypadku jednowymiarowej tablicy double są to zerowe wartości numeryczne.

Rys.19. Wyjścia pętli z i bez auto-indeksacji.

Dane wyjściowe są generowane po zakończeniu działania pętli. Tunel wyjściowy pętli for i while może pracować z auto-indeksacją lub bez niej, niezależnie od rodzaju danych doprowadzonych z wnętrza pętli. Jeśli pracuje bez auto-indeksacji dostarcza wartość wyprowadzoną na wyjście podczas ostatniej iteracji wykonanej przez pętlę (rys.19). Tryb auto-indeksacji tworzy na wyjściu tablicę z danych dostarczanych na wyjście po każdej iteracji pętli. Tablica wyjściowa dostępna po zakończeniu działania pętli ma rozmiar równy liczbie wykonanych iteracji. Jej wymiar zależy od wymiaru danych produkowanych w iteracjach. W przypadku produkcji danych skalarnych powstają tablice jednowymiarowe. Jednowymiarowe tablice są gromadzone w dwuwymiarowej, itd.

Pętle for są wydajniejsze od pętli while w obsłudze operacji tablicowych. Wynika to stąd, że liczba iteracji pętli for jest znana w momencie rozpoczęcia jej działania i można zarezerwować odpowiednie obszary pamięci dla tworzonych tablic. W przypadku pętli while w każdej iteracji trzeba rozszerzyć rozmiary tworzonych tablic czyli musi być wykonana realokacja pamięci oraz przeniesienie dotychczasowych danych do nowego obszaru. Skutkuje to mniejszą wydajnością operacji tablicowych. Mimo tego mankamentu pętle while są niezbędne do realizacji algorytmów, w których nie można z góry określić liczby wymaganych iteracji.

Pętle for są domyślnie przystosowane do tworzenia tablic. Kreowany tunel wyjściowy pętli for jest domyślnie ustawiany w trybie auto-indeksacji niezależnie od rodzaju wyprowadzanych danych. Również tunele wejściowe przekazujące tablice uzyskują domyślnie tryb auto-indeksacji. Tunele wejściowe i wyjściowe pętli while domyślnie pracują bez auto-indeksacji.

Operator rejestru przesuwnego konstrukcji pętlowych (shift registers).

Operator rejestru przesuwnego służy do przenoszenia danych pomiędzy kolejnymi iteracjami pętli for i while. Składa się z dwóch terminali umiejscowionych na przeciwległych stronach ramki konstrukcji pętli. Terminal ze strzałką skierowaną w górę jest wejściem rejestru. Do niego realizuje się połączenie z wyjściem wybranego węzła poddiagramu pętli, który dostarcza danej przekazywanej do następnej iteracji. Wyjściem rejestru jest terminal ze strzałką skierowaną w dół. Dostarcza on danej z poprzedniej iteracji lub wartość początkową w pierwszej iteracji.

Rejestr można inicjalizować daną z węzła lub terminala znajdującego się na zewnątrz pętli dołączonego do terminala ze strzałką skierowaną w dół. Jeśli rejestr nie jest jawnie inicjalizowany przyjmuje domyślne wartości początkowe danego typu (0 dla typów numerycznych) lub korzysta z danych uzyskanych podczas wcześniejszego działania pętli, jeśli program wykonuje pętlę wielokrotnie. Rejestr przesuwny można stosować do dowolnego typu danych, ale utworzony rejestr dotyczy jednego typu danych.

Rys.20. Wykorzystanie rejestru przesuwnego do obliczania średniej z czterech wartości losowych
generowanych w kolejnych iteracjach pętli.

Rejestr tworzy się wybierając z menu konstrukcji pętli pozycję Add Shift Register. Można utworzyć wieloelementowy rejestr przesuwny wybierając z menu terminala rejestru pozycję Add Element. Każde dodanie elementu tworzy dodatkowy terminal ze strzałką skierowaną w dół. Terminale te są sklejone ze sobą. Utworzenie wieloelementowego rejestru pozwala korzystać z kilku danych pochodzących z kolejnych poprzedzających iteracji. Pętla może korzystać z wielu rejestrów przesuwnych.

Konstrukcja formuły węzeł Formula i Expression.

Konstrukcję formuły używa się do utworzenia węzła diagramu realizującego operacje zdefiniowane w ramce konstrukcji i zapisane w formie tekstowej, np.:
y = 3 * x 2 + x * log(x);

Węzeł Formula jest użyteczny do realizacji operacji, które korzystają z wielu zmiennych oraz produkują jeden lub więcej rodzajów danych wyjściowych. Syntaktyka zapisu bloku programu węzła Formula jest podobna do zapisu bloku instrukcji programu w języku C. Blok może zawierać deklaracje zmiennych (dostępny tylko typ float oraz int) oraz wyrażenia. Można korzystać z instrukcji sterujących if..then..else, switch..case, pętli for, while, do..while itp. Wyrażenia wykorzystują takie same operatory jak język C (te same oznaczenia i priorytety). Wyjątkiem jest dodatkowy operator podnoszenia do potęgi ( wyrażenie x**y oznacza x do potęgi y ). Można stosować komentarze.

Rys.21. Przykład wykorzystania węzła Formuła do realizacji obliczeń.

Menu konstrukcji formuły posiada pozycje służące do kreowania wejść i wyjść węzła (odpowiednio Add Input oraz Add Output). Po wykreowaniu należy w polu utworzonego wejścia lub wyjścia wpisać jego nazwę. Stanowią one zmienne formuły, domyślnie są typu float. Nazwy muszą być unikalne w ramach danej konstrukcji, ale nazwa wyjścia może być taka sama jak jednego z wejść.

Rys.22. Węzeł Expression z wyrażeniem uniemożliwiającym zadanie napięcia większego od 30V.

Węzeł Expression służy do obliczenia pojedynczego wyrażenia korzystającego z jednej zmiennej wejściowej. Węzeł posiada predefiniowane wejście i wyjście typu float. Nazwa zmiennej użyta w wyrażeniu jest automatycznie kojarzona z wejściem węzła. Wyjściu jest przypisywany wynik wyrażenia.

4. Zmienne lokalne i globalne programów VI.

Obiekty nastawcze pulpitu dostarczają dane do diagramu za pośrednictwem końcówek wejściowych a obiekty wskaźnikowe pulpitu uzyskują dane z diagramu za pośrednictwem końcówek wyjściowych. Z każdym obiektem pulpitu jest związana jedna i tylko jedna końcówka wyjściowa lub wejściowa diagramu. Aplikacja może potrzebować dostępu do tych danych w różnych miejscach a jednocześnie nie zawsze można to uzyskać przewodami przepływu danych. Problem ten rozwiązują zmienne lokalne i globalne. Zmienne lokalne zapewniają dostęp do danych obiektu pulpitu w różnych miejscach pojedynczego programu VI, natomiast zmienne globalne udostępniają i przenoszą dane pomiędzy różnymi, jednocześnie realizowanymi programami VI. Nie zaleca się stosowania zmiennych w programach VI, ponieważ przesłaniają one przepływ danych a tym samym utrudniają analizę kodu i usuwanie ewentualnych błędów. Mogą być też przyczyną wielu dodatkowych problemów (większe narzuty, niejednoznaczne zachowanie programu itp.). Istnieją jednak sytuacje, gdy zastosowanie zmiennych lokalnych lub globalnych jest w pełni uzasadnione. Przykładowo aplikacja może stosować dwie równolegle działające pętle while. W jednej z nich są generowane dane, z których na bieżąco korzystają operacje drugiej, równolegle działającej pętli. Nie ma innej metody udostępnienia danych z wnętrza działającej pętli niż przekazanie ich za pomocą zmiennej.

Zmienna lokalna pozwala zapisać lub odczytać daną obiektu panelu niezależnie czy jest on obiektem sterującym czy wskaźnikowym. Operacje te funkcjonują tak samo jak przeniesienie danych do lub z końcówki obiektu w diagramie z tym, że przykładowo zapis zmiennej jest dostępny również w stosunku do obiektów sterujących pulpitu, które formalnie są źródłami danych. Dzięki temu, zmienne lokalne można wykorzystać nie tylko do uzyskania aktualnego stanu określonego obiektu sterującego w różnych miejscach diagramu programu ale także do modyfikacji jego ustawienia, np. ustawienia przełącznika w pewnym stanie. To samo dotyczy obiektów wskaźnikowych panelu.

Zastosowanie zmiennej lokalnej w diagramie wymaga jej wykreowania oraz powiązania z określonym obiektem pulpitu i określenia jej przeznaczenia ( odczyt lub zapis stanu obiektu). Do utworzenia zmiennej lokalnej można wykorzystać ikonę znajdującą się w palecie struktur. Po jej przeciągnięciu do edytora diagramu pojawia się symbol zmiennej za znakiem zapytania wskazującym na potrzebę jej powiązania z konkretnym obiektem pulpitu. Powiązanie i określenie przeznaczenia zmiennej wykonuje się wykorzystując menu zmiennej (pozycje Select Item oraz Change To Read/Write).

Rys.23. Zastosowanie zmiennej lokalnej związanej z obiektem Stop do przerwania działania niezależnych pętli.

Proste zastosowanie zmiennej lokalnej ilustruje przykład diagramu wykorzystującego równolegle działające pętle while, których działanie przerywa przycisk STOP (rys.23). Końcówka wejściowa obiektu STOP znajduje się wewnątrz jednej z pętli. Z powodu założenia równoległości działania obu pętli, nie można zastosować połączenia przepływu danych z tej końcówki do terminala kontynuacji drugiej pętli. Można jednak utworzyć zmienną lokalną związaną z obiektem STOP i wykorzystać ją w drugiej pętli. Po zakończeniu działania obu pętli przełącznik jest ustawiany w stanie spoczynkowym (odwrotne wykorzystanie zmiennej lokalnej). Wykorzystanie zapisu do zmiennej powoduje, że przełącznik zachowuje się tak jak przycisk.

Zmienne globalne zapewniają dostęp do wspólnych danych wykorzystywanych przez kilka programów VI realizowanych równocześnie. Są one wbudowanymi obiektami LabView. Kreowanie zmiennej globalnej powoduje automatyczne wytworzenie specjalnego globalnego VI, który posiada tylko panel i nie ma diagramu. Wprowadzenie obiektów nastawczych i wskaźnikowych do tego panelu określa typy danych i składniki takiego obiektu globalnego. Zmienna globalna może mieć zatem postać złożoną, zapewniającą przekazywanie bardzo różnych danych. Jej panel można traktować jako kontener, z którego mogą korzystać różne programy VI.

Rys.24. Zastosowanie zmiennej globalnej do przekazywania danych pomiędzy dwoma programami VI.

Utworzenie zmiennej globalnej realizuje się przez wybranie ikony GLOB z palety struktur i przeciągnięcie jej do diagramu aplikacji. Zostaje utworzony symbol graficzny zmiennej w postaci prostokąta ze znakiem zapytania sygnalizującym brak jej pełnego zdefiniowania oraz niewidoczny VI z pustym pulpitem. W celu zdefiniowania zmiennej należy otworzyć pulpit (z menu zmiennej wybrać pozycję Open Front Panel) i umieścić na nim odpowiednie obiekty odpowiadające typom danych, które składają się na tworzoną zmienną globalną. Tak utworzony VI należy zapisać pod unikalną nazwą (tak jak zwykły program VI). Zdefiniowaną zmienną globalną wprowadza się do aplikacji przez otwarcie określonego globalnego VI i przeciągnięcie jego ikony do jej diagramu. W miejscu docelowym należy jeszcze skonfigurować ją w sensie określenia sposobu wykorzystania (zapis lub odczyt) oraz wybrania odpowiedniego składnika, w przypadku złożonej budowy zmiennej.

Rysunek 24 pokazuje prosty przykład zastosowania zmiennej globalnej do przekazywania danych pomiędzy dwoma równolegle wykonywanymi programami VI. Pierwszy generuje cyklicznie w odstępach jednosekundowych wartości a drugi prezentuje je na swoim pulpicie. Uaktualnianie następuje co dwie sekundy na podstawie zawartości składnika Number to Pass zmiennej globalnej. Składnik Stop Button tej samej zmiennej pozwala przerwać działanie programu Generate Number wraz z zatrzymaniem programu Display Number za pomocą przycisku Stop.

Podstawowa różnica pomiędzy zmiennymi lokalnymi i globalnymi dotyczy zakresu ich dostępności. Zmienna lokalna jest dostępna tylko w kodzie diagramu, w którym została wykreowana. Zmienna globalna może być użyta w każdym programie lub podprogramie VI, a ponieważ jest ona ładowana z pliku to dowolny program VI może mieć do niej dostęp. W rezultacie można bardzo łatwo stracić kontrolę nad danymi i programy będą korzystały z błędnych wartości. Między innymi mogą to spowodować warunki wyścigu zaistniałe, gdy kilka równolegle wykonywanych kodów równocześnie aktualizuje zmienną. W przypadku zmiennych lokalnych znane są miejsca ich modyfikacji w diagramie programu i problem danych jest łatwiejszy do śledzenia.

5. Literatura.

[1] Rick Bitter, Taqi Mohiuddin, Matthew Nawrocki; LabVIEW: Advanced Programming Techniques; CRC Press LLC,2001.
[2] National Instruments; LabVIEW: User manual; November 2001 Edition.
[3] National Instruments; LabVIEW: Getting Started with LabVIEW; November 2001 Edition.

20 maj 2003 r. | opr. dr inż. Bogdan Kasprzak
Interfejsy systemów pomiarowych | Początek