Zmniejszanie opóźnienia między arduino a szkicem przetwarzania na moim komputerze

13

Obecnie pracuję nad projektem nr 14 książki projektowej Arduino.

Próbuję kontrolować szkic przetwarzania na laptopie za pomocą Arduino. Dokonuje się tego za pomocą potencjometru do sterowania tłem obrazu.

Kod Arduino:

void setup(){
  Serial.begin(9600);
}

void loop(){
  Serial.write(analogRead(A0)/4);
}

Przetwarzanie:

//imports serial library
import processing.serial.*;
//setups the serial object
Serial myPort;
//creates an object for the image
PImage logo;
//variable to store background color
int bgcolor = 0;

void setup(){
  colorMode(HSB,255);
  logo = loadImage("http://arduino.cc/logo.png");
  size(logo.width,logo.height);
  println("Available serial ports");
  println(Serial.list());
  myPort = new Serial(this,Serial.list()[0],9600);
}
//equivalent of arduino's loop function
void draw(){
  if(myPort.available() > 0)
  {
    bgcolor = myPort.read();
    println(bgcolor);
  }

  background(bgcolor,255,255);
  image(logo,0,0);
}

Teraz, gdy kod działa, a kolor tła zmienia się, gdy obracam potencjometr, istnieje ogromne opóźnienie między obróceniem potencjometru a widokiem zmiany koloru tła, a wartości z Arduino / potencjometru zmieniają się na monitorze szeregowym przetwarzania.

Co próbowałem:

  • Zmiana prędkości komunikacji szeregowej

Zauważyłem, że kiedy zmniejszam prędkość komunikacji szeregowej, np. Około 100, opóźnienie między obróceniem potencjometru a jego zmianą na moim laptopie zmniejsza się do około 1 sekundy. Gdy jednak jeszcze bardziej zmniejszę prędkość komunikacji szeregowej, np. Wartość 1, opóźnienie ponownie wzrasta.

Z drugiej strony, przy standardowej prędkości 9600 opóźnienie jest ogromne, około 5 sekund ++, zanim zmiany w potencjometrze pojawią się na laptopie / procesorze.

Dlaczego zmniejszenie prędkości komunikacji (do pewnego punktu) zmniejsza opóźnienie czasowe, a zwiększenie go zwiększa opóźnienie czasowe? Poza tym, czy w ogóle mogę sprawić, że będzie to prawie natychmiastowe?

Kenneth .J
źródło
3
Odczytujesz odczyt za każdym razem w Arduino loop(). Całkiem możliwe, że Twój program przetwarzania nie działa wystarczająco szybko, aby nadążyć za nim. Spróbuj opóźnić loop()kod Arduino, aby go spowolnić; np delay(50).
Peter Bloomfield
Cześć Peter, dzięki za szybką odpowiedź, dodanie małego opóźnienia naprawdę rozwiązało mój problem. Ale jeszcze jedno małe pytanie: czy mogę w jakiś sposób określić szybkość mojego programu przetwarzającego w przyszłości, aby zapobiec powtórzeniu się tego problemu, czy też uzyskanie lepszej prędkości laptopa / przetwarzania rozwiązuje problem? Ponadto, dlaczego wprowadzenie prędkości komunikacji 250 lub 300 zakłóca odczyty z arduino? (Odczyty, które otrzymuję są na przemian między odczytem a zerem np. 147,0,147,0)
Kenneth .J

Odpowiedzi:

11

Odczytujesz odczyt za każdym razem w Arduino loop(), więc wydaje się prawdopodobne, że twój program przetwarzania nie działa wystarczająco szybko, aby nadążyć za nim. Spróbuj wprowadzić opóźnienie loop()w kodzie Arduino, aby go spowolnić, np .:

void loop(){
    Serial.write(analogRead(A0)/4);
    delay(50);
}

O ile mi wiadomo, Przetwarzanie ma działać ze stałą szybkością klatek na sekundę, którą można modyfikować za pomocą tej frameRate()funkcji. Domyślnie jest to 60 klatek na sekundę, chociaż może działać wolniej na starszych systemach (lub tam, gdzie działa intensywny program). Możesz sprawdzić, jak szybko działa, czytając frameRatezmienną.

Wprowadzenie 50-milisekundowego opóźnienia w pętli Arduino oznacza, że ​​będzie aktualizować się nieco poniżej 20 razy na sekundę. Oznacza to, że powinien być wystarczająco szybki do celów interfejsu użytkownika, ale powinien również mieścić się w zakresie możliwości Twojego programu przetwarzającego.

Jeśli chodzi o szybkość transmisji (prędkość komunikacji), dostosowanie jej o dowolne kwoty może mieć nieprzewidywalne skutki. Jest tak, ponieważ sprzęt obsługuje tylko określone prędkości, a próba użycia czegokolwiek innego może spowodować zniekształcenie danych na drugim końcu. Serial.begin()Dokumentacja ma trochę więcej informacji na temat obsługiwanych szybkości transmisji.

Peter Bloomfield
źródło
14

Jak już wspomniano, twoje Arduino mówi za dużo za szybko. Dodanie delay()go spowolni, ale nadal będzie krzyczeć na przetwarzanie. Idealnie byłoby, gdyby Processing poprosił o wartość, gdy jest to wygodne, a następnie otrzymał jedną odpowiedź z Arduino.

Enter SerialEvent().

W przeciwieństwie do loop()Arduino i draw()Przetwarzania, wszystko w środku serialEvent()wzbudza się tylko wtedy, gdy w buforze szeregowym jest coś nowego. Zamiast przetwarzać pytania tak szybko, jak to możliwe, a Twoje Arduino krzyczy jeszcze szybciej, mogą odbyć miłą, uprzejmą (asynchroniczną) rozmowę.

Zarówno Processing, jak i Arduino mają serialEvent. Jest to serialEvent () na Arduino i jest to serialEvent () w przetwarzaniu. Używając serialEvent po obu stronach, tak by się stało:

  1. Przetwarzanie wysyła znak do połączenia szeregowego. Może to być dowolny znak, ale jeśli go z góry ustalimy, możemy odfiltrować wszelkie niepożądane żądania spowodowane np. Głośnym sygnałem. W tym przykładzie wysyłajmy za Vkażdym razem, gdy chcemy nowego odczytu twojego potencjometru. Po wysłaniu postaci kontynuujemy naszą działalność jak zwykle. Nie czekam tu na odpowiedź!

  2. Po stronie Arduino nic się nie dzieje, dopóki nie odbierze danych w buforze szeregowym. Sprawdza, czy nadchodząca postać jest i V, na szczęście, jest. Arduino odczytuje raz wartość potencjometru, raz przekazuje tę wartość do numeru seryjnego i wraca do relaksu, maksymalnie relaksując się całkowicie. Protip: zakończ wartość znakiem ( *w naszym przypadku). Pomoże ci to w następnym kroku.

  3. Przetwarzanie działa jak zwykle w przypadku interfejsów pikseli, gdy nagle dochodzi do zakłócenia w wymuszaniu nowych danych w buforze szeregowym. Przełącza się na serialEvent()i zaczyna odczytywać dane szeregowe, aż do *napotkania naszego zakończenia . Wiedząc na pewno, że był to ostatni znak, który warto przeczytać, możemy teraz zapisać przychodzącą wartość w zmiennej przechowującej odczyt Arduino.

  4. Otóż ​​to. Przetwarzanie zna teraz nową wartość czujnika i kontynuuje wszystko, co mu każemy. Tymczasem Twoje Arduino cieszy się pogodą lub rozważa jej istnienie, dopóki nie pojawią się nadchodzące dane szeregowe.

Tomek
źródło
1
A kiedy już to robisz, umieść kondensator równolegle z potencjometrem. Wygładza to niewielkie zmiany na wejściu DAC-a, prawdopodobnie zapobiegając roztrzęsieniu w przetwarzaniu.
Tom
Dziękuję za tę miłą (i trochę antropomorficzną) odpowiedź!
Zeta.Investigator
Właściwie zadawanie pytań przez USB może być pomysłem. Wynika to z faktu, że USB ma dużo większe opóźnienia niż port szeregowy, więc zadawanie pytań i oczekiwanie na odpowiedź jest bardziej czasochłonną operacją niż byłoby to w innym przypadku, szczególnie w porównaniu z tym, co można by zrobić przy dużych prędkościach transmisji. Pozwolenie Arduino na trochę szybsze działanie jest w porządku (choć nie powinno nasycać seryjnej części łącza); haczyk polega na tym, że szkic przetwarzania powinien wysuszyć dane Arduino, gdy staną się dostępne, i zatrzymać ostatnią pełną wartość, która będzie używana, gdy będzie potrzebna.
Chris Stratton,
7

Pętla odpytywania działa z pełną prędkością procesora i zapisuje do portu szeregowego w każdej rundzie.

W ten sposób piszesz znacznie częściej do portu szeregowego, niż może obsłużyć.

Port wypisuje dane tak szybko, jak je skonfigurowałeś, i buforuje dane, które przychodzą z twojego programu zbyt szybko , aby zapisać je jak najszybciej. Gdy bufor jest pełny, po prostu upuszcza nowe dane.

Ważne jest tutaj, aby zachował kolejność wartości: jest to bufor FIFO , działający w kolejności pierwsze wejście / pierwsze wyjście.

Co się dzieje:
Pętla wypełnia bufor portu i utrzymuje go w 100%.
Jeśli przekręcisz potencjometr, zmieniona wartość zostanie zapisana na końcu bufora , port działa tak szybko, jak to możliwe, aby zapisać wszystkie elementy w buforze, które nadal mają starą wartość.

I wreszcie wartość, którą jesteś zainteresowany. Najbardziej aktualną wartością, którą chcieliśmy zobaczyć od razu, było na końcu FIFO, a pierwsze wejście / pierwsze wyjście oznacza również ostatnie wejście / ostatnie wyjście. Przeciwieństwo tego, czego chcemy.

Maksymalna częstotliwość, którą ma sens odczytanie danych, to częstotliwość, którą możesz zapisać, więc powinieneś użyć przynajmniej opóźnienia, które jest wystarczająco długie, aby zapisać bajty przy bieżącej prędkości portu.


Jako kolejną niezależną metodę zapobiegania tego rodzaju opóźnieniom
można dodatkowo ustawić bufor zapisu portu na minimum.

To spowodowałoby, że dane byłyby upuszczane znacznie wcześniej, zamiast buforować dużo wcześniej.

Oczywiście w wielu aplikacjach nie jest to potrzebne; Przy nieszczęściu może i tak działać na początku i stać się niestabilny w niektórych sytuacjach, gdy czas zmienia się w zależności od takich rzeczy, jak obciążenie procesora, a tylko niektóre losowe próbki danych są upuszczane. Duży bufor zwykle działa znacznie bardziej deterministycznie, więc domyślnie używaj dużego bufora .

Volker Siegel
źródło
Właściwy pomysł, ale niezupełnie zgodny ze stwierdzeniem „Gdy bufor jest pełny, po prostu upuszcza nowe dane”. Po wypełnieniu bufora dane nie są usuwane, a raczej blok zapisu, dopóki nie będzie miejsca w buforze wychodzącym. Oznacza to, że dane wejściowe i wyjściowe wkrótce będą płynąć z tą samą średnią szybkością, ale między nimi występuje opóźnienie o wartości bufora.
Chris Stratton
6

Zamiast ciągłego wysyłania danych szeregowych, wysyłaj dane tylko wtedy, gdy wartość potencjometru zmieniła się powyżej określonego progu.

int oldValue = 0;
const int threshold = 5;

void setup()
{
  Serial.begin(9600);
  pinMode(A0, INPUT)
}

void loop()
{
  if(oldValue >= analogRead(A0)+threshold || oldValue <= analogRead(A0)-threshold)
  {
    Serial.println(analogRead(A0));
    oldValue = analogRead(A0);
  }
}
Daniel Eisterhold
źródło
1
To loop()nie wypełnia bufora wyjściowego równymi próbkami, to dobrze. Ale nadal działa z pełną prędkością procesora, która może być 100 razy szybsza niż jest to potrzebne. Oznacza to, że nadal może szybko wypełnić bufor do limitu, jeśli dane wejściowe często się zmieniają, np. Z szumu powyżej thresholdlub ciągła zmiana wysokiej rozdzielczości (co nie ma miejsca w przykładowej aplikacji tutaj)
Volker Siegel
0

Dwa proste rozwiązania, które z pewnością będą działać dla każdego, kto wciąż szuka: -

  1. Zwiększ opóźnienie do 50 do 100 milisekund.

  2. Dodaj to po Serial.begin(9600)w setup();

    Serial.setTimeout(50);

Najważniejszy jest drugi krok. Działa to dla mnie dopiero po dodaniu powyższego kodu. Nie jest to często wspominane na wielu innych forach, na które patrzyłem, gdy miałem dokładnie ten sam problem.

Rishi Swethan
źródło
To jest nieco błędne. Metoda setTimeout () ma zastosowanie do danych wejściowych, a nie wyjściowych - patrz dokumentacja na stronie arduino.cc/en/Serial/SetTimeout
Chris Stratton