Processing — oscyloskop (finał)

Oscyloskop

Kontynuujemy tworzenie oscyloskopu w processingu. Skupimy się na ostatnich barkujących elementach -kreska przesuwająca się w trakcie zbierania pomiarów, napisy na ekranie oraz obsługa myszy (historia wartości). 

Animacja kreski

Chcę dodać kreskę (od góry ekranu do dołu, u mnie kolor żółty, linia bardzo cienka – na 1 pixel) przesuwającą się z lewa do prawa podczas działania naszego oscyloskopu. Przedstawia to sekwencja poniższych obrazków:

     

Rozwiązań jest wiele, a my proponujemy następujące:

  1. kasujemy starą kreskę w pozycji xPos (czyli rysujemy ją kolorem tła),
  2. rysujemy dalszą część właściwego wykresu, 
  3. rysujemy nową kreskę w pozycji xPos+skokX (naszym ulubionym kolorem).

Te trzy kroki powodują, że nie musimy „odświerzać” całego ekranu dla każdego kawałka krzywej na oscyloskopie (tj. nie musimy rysować całego wykresu od początku, bazując na wcześniej zapamiętanych wartościach otrzymywanych z Arduino). To rozwiązanie jest proste i działa fajnie, dlatego je stosujemy.

void draw(){
 int x=getValue(); 
 print("z Arduino x=",x);
 y=wspA*x;
 
  strokeWeight(1);  //grubość kreski = 1 pixel
  stroke(0);  //kasowanie = kolor ekranu 
  line(xPos, 0, xPos, height);

  strokeWeight(10);// grubość kreski dla wykresu
  stroke(0,0,255); //kolor wykresu
  line(xPos - skokX, poprzedniY, xPos, y);

  strokeWeight(1); //ponownie grubość kreski
  stroke(0,0,200);// moj ulubiony kolor
  line(xPos+skokX, 0, xPos+skokX, height);
   
   xPos=xPos+skokX;
   if (xPos>=width){
     xPos=0;
     background(0);
   }
}

Na uwagę zasługuje

  • ciągła „zabawa” z kolorami i grubościami linii – oddzielnie dla kreski, oddzielnie dla tworzonego wykresu (no i kasowanie kreski musi mieć tą samą grubość, co nasza pionowa kreska).

Napisy

Są niezbędne, aby szybko orientować się jakie wartości są rysowane – mamy wszak możliwość skalowania osi y, czyli zawężania/zwiększanie widocznego zakresu. Dodatkowo, można (a nawet wypada) pisać wartość aktualnie otrzymaną z Arduino. Modyfikujemy funkcję setup() dodając (gdzieś na koniec) następujące linie

PFont f;
f = createFont("Arial",16,true); // Arial, 16 point, anti-aliasing on
textFont(f,36);

W powyższym kodzie przygotowujemy processing do pisania teksów – posługujemy się pomocniczą zmienną f (bez większego znaczenia). Aby pisać napisy używamy 

fill(0,0,100); //zmieniamy kolor teksu, jeśli chcemy
textSize(36); //zmieniamy rozmiar tekstu, jeśli chcemy
text("gawryl", 10,100); //piszemy tekst "gawryl" w pozycji x=10, y=100

Funkcja text() ma spore możliwości, dlatego warto zajrzeć do jej instrukcji. Można nawet używać jej w ten sposób:

int i=17;
text(i, 10,100); //piszemy liczbę całkowitą i
text(3.0*i, 10, 200); //piszemy liczbę rzeczywistą
float f=3.14;
text(f, 10, 300);//piszemy liczbę rzeczywistą

Jak widać konwersja typów na napisy następuje automatycznie – to bardzo ułatwia sprawę pisania tekstów. Proponuję testować poznane możliwości – ale szybko dojdziemy do etapu, gdzie napisy nachodzą na siebie, przekrywają się, są nieczytelne i należy je wyczyścić.

Czyszczenie napisów

Postępowanie podobne do metody z animowaną linią (z początku zajęć) nie jest jednak zadawalające. Chodzi o to, że skoro zdecydowaliśmy się na wyeliminowanie czyszczenia ekranu do minimum i kasowanie poprzenich napisów polega na pisanie ich ponownie kolerm tła – to jednak pozostają jakieś pozostałości. Dlatego… proponuję ograniczyć obszar wykresu tylko do części okna, a nie do całej dostępnej szerokości x wysokości okienka. Dzięki temu zabiegowi będziemy mogli bezkarnie czyścić obszar przeznaczony do napisy (zielony prostokąt na rysunku), a pozostawiać w spokoju właściwy wykres (obszar z czarnym tłem). Aby to zobrazować popatrzmy na rysunek:

Proponuję więc, aby każdorazowo funkcja draw() rysowała prostokąt (u mnie w kolorze zielonym) w określonych pozycjach, kasując całą zawartość tam się znajdującą. Następnie nowe napisy moga być już wypisywane wewnątrz tego prostokątu, bez brzydkich artefaktów z poprzedniej metody. Dalej następuje rysowanie właściwego wykresu i pionowej kreski, od której rozpoczęliśmy nasze zajęcia.

Pomysł z zielonym obszarem oczywiście wymaga zmiany współczynników skalowania wspAwspB prostej dopasowującej wczytany x z Arduino na współrzędną y okienka processinga. Powodzenia!

Historia wykresu

Chodzi o to, aby można było zatrzymać kreślenie wykresu (to już mamy obsługiwane) a dodatkowo aby można było lewym klawiszem myszy kliknąć i sprawdzić (wypisać w zielonym obszarze) wartość wykresu. Musimy więc pamiętać poprzednie pomiary – będziemy je przechowywać w pomocniczych tablicach. Dlatego wprowadzamy zmienne globalne

int[] values;
float[] times;

void setup(){

Deklarację tablic values oraz times umieszczamy gdzieś na początku kodu, aby zmienne te były widoczne wewnątrz wszystkich funkcji poniżej – a więc setup(), getValue() oraz draw(). Następnie modyfikujemy funkcję setup() o utworzenie konkretnych rozmiarów tych tablic:

values = new int[width];
times = new float[width];

Nie wnikając w zawiłości dynamicznego przydzielania pamięci – powyższe linie tworzą teyle elementów tablicy values oraz times, jaka jest szerokość naszego okienka z wykresem. Przy okazji widać, że skoro Arduino nadaje do nas liczby z zakresu 0..1023 to na ich przechowywanie przygotowałem tablicę liczb całkowitch, natomiast druga tablica przechowuje liczby rzeczywiste. Ta druga tablica będzie notować mi czas odebrania (w sekundach) przez processing konkretnej liczby – bardzo przydatne w późniejszej pracy z oscyloskopem.

Modyfikujemy funkcję odbierającą dane z portu szeregowego o fragment wypełniający nasze tablice:

int getValue(){
  int value = -1;
  while (port.available() >= 3){
    if (port.read() == 0xff){
       value = (port.read() <<8) | (port.read());
       values[xPos]=value;
       times[xPos]=millis()/1000.0;
    }//if
  }//while
  return value;
}//getValue

Zdecydowałem się na zapisywanie liczb do tablic z wykorzystaniem zmiennej xPos – takie rozwiązanie jest sprytne, ale będzie pociągało za sobą pewne następstwa. Przy okazji poznajemy nową funkcję processinga millis() – która, podobnie jak w Arduino, zwraca czas działania programu w milisekundach – dzięki temu zapisujemy czas „odebrania” danej nadesłanej z Arduino. Liczbę tą dzielę przez 1000.0 aby mieć wartość w sekundach (z przecinkiem – jak w Polsce lubimy).

Obsługa myszy – historia

Mówiliśmy o obsłudze myszy sprawdzając zmienną mousePressed – dzięki czemu wiemy, że wciśnięto przycisk myszy. Ale taki sposób stosujemy wewnątrz funkcji draw(), a my chcemy zatrzymać kreślenie wykresu (pomijając wywołanie funkcji draw()) a jednocześnie obsłużyć zdarzenia z myszy. Dlatego proponuję zrobić to tak:

void mousePressed() {
  int i = (mouseX/skokX)*skokX;
//pomocnicze linie
  print("x= ",mouseX,i); 
  print(" historia= ",values[i]); 
  println(" ", i]);
//właściwe wypisywanie
  fill(0,200,0);//kolor czyszczenia prostokata
  rect(0,height-50, width, height);
  fill(255);//kolor dlugopisu
  text(times[i], 300, height-20);
  text(values[i], 200, height-20);
}

Specjalna funkcja o nazwie mousePressed() będzie wywoływać się automatycznie za każdym razem, gdy mysz będzie klikana. My musimy wpisać tylko odpowiedni kod.

Mój kod ma aż 3 linie pomocniczych wypisywań — do usunięcia w finalnej wersji programiku. Zmienna i potrzeba mi jest po to, aby przeliczyć współrzędną x-ową kursora myszki na odpowiedni indeks w tablicy – wszak nasz oscyloskop wyposażyliśmy w możliwość rysowania punktów rzadko lub gęsto (decyduje o tym zmienna skokX, obsługiwana z klawiatury). Skoro więc dwuktornie odwołuję się do tego samego indeksu, wypisując wartość times oraz values – nawet w finalnej wersji, pozbawionej pomocniczych print-ów –  to nie liczę tego każdorazowo, tylko przechowuję go w zmiennej.

Ja swoje napisy rysuję 20 pikseli od dołu ekranu, w pozycjach 200 i 300 (licząc od  lewej strony). To jest w obszarze mojego „zielonego prostokąta”.

Czego się dziś nauczyłeś

  • pisanie tekstów
  • tworzenie dynamicznych tablic
  • (inna) obsługa myszy

(ciągle) pozostaje kilka kwestii do rozwiązania:

  • poprawne wczytywanie liczb (z Arduino wysyłamy 0..1023, a processing „widzi” tylko jakieś małe liczby, <30?)
  • skalowanie wykresu – czyli chcemy mieć tak, by liczby z zakresu 0..1023 zawsze mieściły się na ekranie, a nie „wyskakiwały” poza aktualną wysokość ekranu
  • skalowanie minimalnej liczby (więcej skalowań)
  • przesuwanie wykresu góra/dół (więcej skalowań)
  • obsługa myszki i wyświetlanie dowolnych wartości z historii wykresu
  • pisanie napisów w oknie processinga (np. liczb z wartościami na wykresach)
  • pisanie skali na wykresie (np. mL i ML z brzegu okienka)
  • podpis osi x – można użyć czasu do opisu tej osi, będzie widać kiedy odebrano konkretną daną
  • ładniejsza grafika

Zapraszam na kolejne zajęcia, na których podłączymy nasz oscyloskop do czujki pola magnetczynego oraz do fotorezystora.

Posted in FiBot and tagged , , , , , , , , .