Reduzierung der Verzögerung zwischen dem Arduino und einer Verarbeitungsskizze auf meinem Computer


13

Ich bin derzeit auf Projekt # 14 des Arduino-Projektbuchs.

Ich versuche, mit meinem Arduino eine Verarbeitungsskizze auf meinem Laptop zu steuern. Dies wird erreicht, indem ein Potentiometer verwendet wird, um den Hintergrund eines Bildes zu steuern.

Arduino-Code:

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

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

Wird bearbeitet:

//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);
}

Nun, während der Code funktioniert und sich die Hintergrundfarbe ändert, wenn ich das Potentiometer drehe, gibt es eine große Verzögerung zwischen dem Drehen des Potentiometers und dem Erkennen der Hintergrundänderungsfarbe, und die Werte vom Arduino / Potentiometer ändern sich auf dem seriellen Monitor der Verarbeitung.

Was ich versucht habe:

  • Ändern der Geschwindigkeit der seriellen Kommunikation

Ich habe festgestellt, dass sich die Verzögerung zwischen dem Drehen des Potentiometers und der Änderung auf meinem Laptop auf etwa 1 Sekunde verringert, wenn ich die Geschwindigkeit der seriellen Kommunikation verringere, z. B. um 100. Wenn ich jedoch die Geschwindigkeit der seriellen Kommunikation weiter absenke, z. B. auf den Wert 1, erhöht sich die Verzögerung erneut.

Auf der anderen Seite ist die Verzögerung bei der Standardgeschwindigkeit von 9600 sehr groß, ungefähr 5 Sekunden ++, bevor die Änderungen am Potentiometer auf dem Laptop / der Verarbeitung angezeigt werden.

Warum wird durch Verringern der Kommunikationsgeschwindigkeit (bis zu einem bestimmten Punkt) die Zeitverzögerung verringert und durch Erhöhen die Zeitverzögerung erhöht? Auch gibt es überhaupt ich kann es fast sofort machen?


3
Sie geben jedes Mal eine Lesung im Arduino aus loop(). Es ist gut möglich, dass Ihr Verarbeitungsprogramm nicht schnell genug läuft, um damit Schritt zu halten. Versuchen Sie, eine Verzögerung in loop()Ihren Arduino-Code einzufügen, um ihn zu verlangsamen. zB delay(50).
Peter Bloomfield

Hallo Peter, danke für die prompte Antwort, das Hinzufügen einer kleinen Verzögerung hat mein Problem wirklich gelöst. Nur eine weitere kleine Frage: Kann ich die Geschwindigkeit meines Verarbeitungsprogramms in Zukunft bestimmen, um ein erneutes Auftreten zu verhindern? Oder kann das Problem durch eine bessere Laptop- / Verarbeitungsgeschwindigkeit behoben werden? Auch, warum die Eingabe einer Kommunikationsgeschwindigkeit von 250 oder 300 die Messwerte vom Arduino durcheinander bringt (die Messwerte, die ich erhalte, sind abwechselnd zwischen dem Messwert und Null, z. B. 147,0,147,0)
Kenneth .J

Antworten:


11

Sie geben jedes Mal einen Messwert auf dem Arduino aus loop(), sodass es wahrscheinlich ist, dass Ihr Verarbeitungsprogramm nicht schnell genug läuft, um damit Schritt zu halten. Versuchen Sie, eine Verzögerung in loop()Ihren Arduino-Code einzufügen, um ihn zu verlangsamen, zB:

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

Soweit ich weiß, zielt die Verarbeitung auf eine konsistente Bildrate ab, die Sie mit der frameRate()Funktion ändern können. Standardmäßig sind es 60 Bilder pro Sekunde, obwohl es auf älteren Systemen möglicherweise langsamer läuft (oder wenn Sie ein intensives Programm ausführen). Sie können überprüfen, wie schnell es läuft, indem Sie die frameRateVariable lesen .

Das Einfügen einer Verzögerung von 50 Millisekunden in die Arduino-Schleife bedeutet, dass sie etwas weniger als 20 Mal pro Sekunde aktualisiert wird. Das bedeutet, dass es für Zwecke der Benutzeroberfläche schnell genug sein sollte, aber auch innerhalb der Fähigkeiten Ihres Verarbeitungsprogramms liegen sollte.

Wenn Sie die Baudrate (Kommunikationsgeschwindigkeit) beliebig anpassen, kann dies zu unvorhersehbaren Ergebnissen führen. Dies liegt daran, dass die Hardware nur bestimmte Geschwindigkeiten unterstützt und der Versuch, etwas anderes zu verwenden, dazu führen kann, dass die Daten am anderen Ende unkenntlich gemacht werden. In der Serial.begin()Dokumentation finden Sie weitere Informationen zu den unterstützten Baudraten.


14

Wie bereits erwähnt, sagt Ihr Arduino zu viel zu schnell. Das Hinzufügen delay()verlangsamt die Verarbeitung, brüllt sie jedoch weiterhin an. Im Idealfall soll Processing nach dem gewünschten Wert fragen und dann eine Antwort von Ihrem Arduino erhalten.

Eintreten SerialEvent().

Im Gegensatz zu loop()auf Ihrem Arduino und draw()in der Verarbeitung wird alles im Inneren serialEvent()nur dann ausgeführt, wenn sich etwas Neues im seriellen Puffer befindet. Anstatt also Fragen so schnell wie möglich zu stellen und Ihr Arduino noch schneller zurückschreit, können sie eine nette, höfliche (asynchrone) Unterhaltung führen.

Sowohl Processing als auch Arduino haben ein serialEvent. Dies ist serialEvent () auf dem Arduino und dies ist serialEvent () in der Verarbeitung. Wenn Sie serialEvent auf beiden Seiten verwenden, passiert Folgendes:

  1. Die Verarbeitung sendet ein Zeichen an die serielle Verbindung. Dies kann ein beliebiges Zeichen sein. Wenn wir jedoch ein Zeichen vorgeben, können wir unerwünschte Anforderungen herausfiltern, die z. B. durch ein verrauschtes Signal verursacht werden. In diesem Beispiel senden Vwir jedes Mal ein, wenn wir eine neue Anzeige Ihres Potentiometers wünschen. Nachdem der Charakter gesendet wurde, setzen wir unser Geschäft wie gewohnt fort. Ich warte hier nicht auf eine Antwort!

  2. Auf der Arduino-Seite passiert nichts, bis es Daten im seriellen Puffer empfängt. Es wird geprüft, ob der eingehende Charakter ein a ist V, und glücklicherweise ist es ein a. Der Arduino liest den Wert des Potentiometers einmal, gibt diesen Wert einmal seriell aus und kehrt zum Chillen zurück, wobei er sich maximal kühl entspannt. Protip: Beende den Wert mit einem Zeichen ( *in unserem Fall). Dies wird Ihnen im nächsten Schritt helfen.

  3. Die Verarbeitung erledigt ihr normales Geschäft mit Schnittstellenpixeln, wenn plötzlich eine Störung in der Erzwingung neuer Daten im seriellen Puffer auftritt. Es wechselt zu serialEvent()und beginnt mit dem Lesen der seriellen Daten, bis unsere Beendigung *eintrifft. Da wir sicher sind, dass dies das letzte lesenswerte Zeichen war, können wir den eingehenden Wert in einer Variablen speichern, die den Arduino-Messwert speichert.

  4. Das ist es. Die Verarbeitung kennt nun den neuen Sensorwert und setzt fort, was wir ihm befehlen. Währenddessen genießt Ihr Arduino das Wetter oder denkt über seine Existenz nach, bis es eingehende serielle Daten gibt.


1
Und während Sie gerade dabei sind, schalten Sie einen Kondensator parallel zu Ihrem Potentiometer. Dadurch werden geringfügige Änderungen am DAC-Eingang ausgeglichen und möglicherweise unruhige Bewegungen bei der Verarbeitung verhindert.
Tom

Vielen Dank für diese nette (und irgendwie anthropomorphe) Antwort!
Zeta.Investigator

Fragen über USB zu stellen, kann eine Idee sein. Dies liegt daran, dass USB eine viel größere Latenz aufweist als ein serieller Anschluss. Daher ist es zeitaufwändiger, eine Frage zu stellen und auf eine Antwort zu warten, als dies ansonsten der Fall wäre, insbesondere im Vergleich zu dem, was mit hohen Baudraten möglich wäre. Das Arduino ein wenig schneller laufen zu lassen ist in Ordnung (obwohl es den seriellen Teil der Verbindung nicht auslasten sollte); Der Haken dabei ist, dass die Verarbeitungsskizze die Arduino-Daten entleert, sobald sie verfügbar sind, und den letzten vollständigen Wert festhält, der verwendet wird, wenn er benötigt wird.
Chris Stratton

7

Ihre Abfrageschleife läuft mit der vollen Geschwindigkeit Ihres Prozessors und schreibt in jeder Runde auf den seriellen Port.

Auf diese Weise schreiben Sie viel häufiger an den seriellen Anschluss, als er verarbeiten kann.

Der Port schreibt Daten so schnell aus, wie Sie es konfiguriert haben, und puffert Daten , die von Ihrem Programm zu schnell eingehen , um sie so schnell wie möglich auszulesen. Wenn der Puffer voll ist, werden nur neue Daten abgelegt.

Wichtig ist hierbei, dass die Reihenfolge der Werte beibehalten wird: Es handelt sich um einen FIFO-Puffer , der in der Reihenfolge First In / First Out arbeitet.

Was passiert ist:
Die Schleife füllt den Port-Puffer und hält ihn zu 100% voll.
Wenn Sie das Potentiometer drehen, wird der geänderte Wert an das Ende des Puffers geschrieben . Der Port arbeitet so schnell wie möglich, um alle Elemente im Puffer auszuschreiben, die noch den alten Wert haben.

Und schließlich der Wert, an dem Sie interessiert sind. Der aktuellste Wert, den wir sofort sehen wollten, befand sich am Ende des FIFO, und first in / first out bedeutet auch last in / last out. Das Gegenteil von dem, was wir wollen.

Die maximale Häufigkeit, mit der das Lesen Ihrer Daten sinnvoll ist, ist die Häufigkeit, mit der Sie sie auslesen können. Verwenden Sie daher mindestens eine Verzögerung, die ausreicht, um die Bytes mit der aktuellen Portgeschwindigkeit auszuschreiben.


Als weitere unabhängige Maßnahme, um diese Art von Verzögerung im Allgemeinen zu verhindern, können
Sie den Schreibpuffer des Ports zusätzlich auf ein Minimum setzen.

Dies würde dazu führen, dass Daten viel früher gelöscht werden, anstatt viel zuerst zu puffern.

In vielen Anwendungen ist das natürlich nicht das, was Sie brauchen. Mit etwas Pech könnte es am Anfang trotzdem funktionieren und in einigen Situationen instabil werden, wenn sich das Timing aufgrund von Dingen wie der Prozessorauslastung ändert und nur einige zufällige Datenstichproben fallengelassen werden. Ein großer Puffer verhält sich im Allgemeinen sehr viel deterministischer. Verwenden Sie daher standardmäßig einen großen Puffer .


Richtige Idee, aber nicht ganz richtig in der Aussage "Wenn der Puffer voll ist, werden nur neue Daten abgelegt." Sobald der Puffer gefüllt ist, werden die Daten nicht gelöscht, sondern der Schreibblock, bis im Ausgangspuffer Platz ist. Dies bedeutet, dass Input und Output bald mit der gleichen Durchschnittsrate fließen, dass sich jedoch eine Pufferlatenz zwischen ihnen befindet.
Chris Stratton

6

Anstatt ständig serielle Daten zu senden, senden Sie Daten nur, wenn sich der Wert des Potentiometers über einen bestimmten Schwellenwert geändert hat.

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);
  }
}

1
Das loop()füllt den Ausgabepuffer nicht mit gleichen Samples, das ist gut. Es läuft jedoch immer noch mit der vollen Geschwindigkeit des Prozessors, die möglicherweise 100-mal so schnell ist, wie sie benötigt wird. Das heißt , es kann immer noch schnell den Puffer bis an die Grenze füllen , wenn die Eingabe häufig ändert, zB von Rauschen oben threshold, oder eine kontinuierliche Veränderung in hohen Auflösung (was nicht der Fall in der Beispielanwendung ist hier)
Volker Siegel

0

Zwei einfache Lösungen, die für jeden, der noch auf der Suche ist, garantiert funktionieren:

  1. Erhöhen Sie die Verzögerung auf 50 bis 100 Millisekunden.

  2. Fügen Sie dies nach dem Serial.begin(9600)in setup();

    Serial.setTimeout(50);

Schritt zwei ist der wichtigste. Es funktionierte nur für mich, nachdem ich den obigen Code hinzugefügt hatte. Dies wird in vielen anderen Foren, in denen ich nach genau dem gleichen Problem gesucht habe, nicht sehr oft erwähnt.


Das ist etwas falsch. Die Methode setTimeout () gilt für die Eingabe, nicht für die Ausgabe. Weitere Informationen finden
Chris Stratton,
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.