Gerät per RS232 ansteuern

Hallo,

ich würde gerne ein Bedienteil für ein videotechnisches Gerät bauen. Das Bedienteil soll zwischen verschiedenen Eingängen umschalten und mir den entsprechenden aktiven Eingang am Bedienteil per LED anzeigen.

Mit dem Befehl "RTE 1" und "RTE 2" kann ich zum jetzigen Zeitpunkt bereits zwischen Eingang 1 und 2 umschalten.

Ich möchte aber nicht nur zwischen den verschiedenen Eingängen umschalten können, sondern möchte auch für den Fall, dass direkt am Gerät (also nicht über das geplante Bedienteil) der Eingang umgeschaltet wird dafür sorgen, dass mir am Bedienteil trotzdem der richtige aktive Eingang angezeigt wird.

Das senden des Befehls "RTE?" bewirkt folgende Rückmeldung:

"=3" (bei aktiviertem Eingang 3)

Man kann also abfragen, welcher Eingang momentan aktiv ist.

Leider sendet das Gerät nicht einfach von sich aus eine Meldung, wenn am Gerät umgeschaltet wird.

Nun zu meinem Problem:

Ich weiss beim besten Willen nicht, wie ich den Code aufbauen soll.

Theoretisch müsste ja kontinuierlich "RTE?" gesandt werden.

Ich bekomme die zeitliche Abfolge irgendwie nicht in meine Birne.

Wann frage ich was ab? Sende ich eine "RTE?"-Abfrage zusätzlich mit jedem Druck auf einen Taster am Bedienteil,.. steh echt im Wald. :frowning:

Gruß Chris

Chris72622:
Leider sendet das Gerät nicht einfach von sich aus eine Meldung, wenn am Gerät umgeschaltet wird.

Nun zu meinem Problem:

Ich weiss beim besten Willen nicht, wie ich den Code aufbauen soll.

Theoretisch müsste ja kontinuierlich "RTE?" gesandt werden.

Ich bekomme die zeitliche Abfolge irgendwie nicht in meine Birne.

Wenn sich am Gerät auch keine Funktion aktivieren läßt, die die gesuchten Daten bei Änderung unaufgefordert sendet, mußt Du die Abfrage "pollen", also in zeitlichen Abständen immer wieder die Daten anfordern und die Rückantwort auswerten. Zwischen zwei Anfragen an das Gerät müßte mindestens so viel Zeit vergehen, dass die Zeit ausreicht, um die Daten zum Gerät zu senden, das Gerät die Anfrage verarbeitet und darauf reagiert und um die Daten vom Gerät zu empfangen.

Bei einem Gerät, das einige Zeichen über eine serielle Schnittstelle erhält und zurücksendet z.B. einmal pro Sekunde "RTE?" senden und die eintreffende Antwort auswerten.

Ok.

Leider hab ich keine Ahnung, wie ich nach "=x" suchen soll.

Selbst dieser Thread hat es noch nicht geschafft meinen Knoten im Kopf zu lösen:

http://forum.arduino.cc/index.php?topic=142928.msg1075606#msg1075606

Hat mir vielleicht jmd. noch einen Tipp?

Gruß Chris

PS: So kann ich das "=" ja leider nicht mitberücksichtigen:

if(Serial3.available() > 0)
{
  incomingByte = Serial3.parseInt();
  if(incomingByte == '1')
  {
    digitalWrite(LED_1, HIGH);
    Serial.println("Eingang 1 geschaltet!");
  }
}

Chris72622:
Hat mir vielleicht jmd. noch einen Tipp?

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

void perRS232ansteuern()
{
  static unsigned long letzteSekunde;
  boolean gleichheitsZeichenGelesen=false;
  char c;
  if (millis()/1000==letzteSekunde) return; // nichts zu tun weil noch keine Sekunde vergangen
  letzteSekunde=millis()/1000;
  if (letzteSekunde%2==0) // in geraden Sekunden Befehl senden
  {
    Serial.println("RTE?"); 
  }
  else // in ungeraden Sekunden die Antworten auslesen
  {
    while (Serial.available())
    {
      c=Serial.read();
      Serial.write(c);
      if (gleichheitsZeichenGelesen) // das nächste Zeichen ist was wir suchen
      {
        Serial.println();
        if (c=='1') Serial.println("Eingang 1");
        else if (c=='2') Serial.println("Eingang 2");
        else if (c=='3') Serial.println("Eingang 3");
        else if (c=='4') Serial.println("Eingang 4");
      }
      else if (c=='=') gleichheitsZeichenGelesen=true;
    } 
  }
}


void loop() {
  perRS232ansteuern();
}

Das ist jetzt nur mal ein Testcode für den "Seriellen Monitor" von Arduino, bei dem Du die simulierte Antwort selbst über die Eingabezeile eingeben kannst. Für die Ansteuerung eines seriellen Geräts anstelle des seriellen Monitors müßten folgende Änderungen vorgenommen werden.

Entweder Du schreibst diese Zeile:
Serial.println("RTE?");
und alle read() Anweisungen auf eine andere serielle Schnittstelle um, an der das externe Gerät angeschlossen ist.
Entweder eine zweite serielle Hardwareschnittstelle (hat z.B. ein MEGA) oder eine Software-Serial.

Oder Du löschst alle anderen print()-Ausgaben (außer der Zeile Serial.println("RTE?"); ) aus dem Programm heraus, damit sie das Kommando-Interface des angeschlossenen Geräts nicht irritieren. Und machst Kontrollausgaben z.B. auf einem angeschlossenen LCD-Display.

Hallo,

kann mir jmd. erklären, welche Funktion das return hier hat, bzw. ob ich es auch einfach weglassen könnte?

if (millis()/1000==letzteSekunde) return;
  letzteSekunde=millis()/1000;

Danke.

Gruß Chris

Sogar auf deutsch: http://arduino.cc/de/Reference/Return

Die Funktion wird wenn die if-Bedingung erfüllt ist einfach abgebrochen, alles nachstehende wird nicht ausgeführt.
Ist diese Bedingung nicht erfüllt, geht es unmittelbar mit den nachfolgenden Codezeilen weiter.

Chris72622:
kann mir jmd. erklären, welche Funktion das return hier hat, bzw. ob ich es auch einfach weglassen könnte?

  if (millis()/1000==letzteSekunde) return;

letzteSekunde=millis()/1000;

"return" beendet eine Funktion vorzeitig und falls "return" ausgeführt wird, wird sämtlicher nachfolgender Code nicht mehr ausgeführt. Die Ausführung von "return" stellt also das Ende der Funktion dar.

Der Ausdruck "(millis()/1000" bildet ein Zähler, der alle 1000 Millisekunden um 1 hochzählt. Solange die Funktion immer wieder mit demselben Stand des Sekundenzählers ausgeführt wird wird die aufgerufene Funktion sofort wieder verlassen. Dadurch wird der nachfolgende Code der Funktion nur ein einziges mal pro Sekunde ausgeführt, egal wieviel zigtausendmal in einer Sekunde die Funktion aufgerufen wird.

In der nächsten Zeile wird dann mit "letzteSekunde=millis()/1000;" der aktuelle Stand des Sekundenzählers in einer statischen Variablen festgehalten, damit die Funktion weiß, welches die letzte Sekunde war, in der der Code ausgeführt wurde und in der der Code daher auf keinen Fall nochmals ausgeführt werden soll.

Läßt Du das "return" in der ersten Zeile weg oder sogar die ganze erste Zeile, dann wird der nachfolgende Code nicht mehr einmal pro Sekunde ausgeführt, sondern so oft, wie die Funktion aufgerufen wird. Also unter Umständen zigtausende male pro Sekunde.

(Erst) jetzt leuchtet mir ein, warum ich das Konstrukt nicht verstanden habe!!!!

Juhuuu- endlich hab ich es kapiert. :slight_smile:

Vielen, vielen Dank!!

Werd mir in den nächsten Wochen wohl mal die komplette Reference durchlesen.

Gruß Chris

Hallo,

da sich die Anforderungen geändert haben, muss ich mittlerweile nicht nur nach einer Zahl suchen, sondern nach einer Zeichenabfolge.

Um es mir so einfach wie möglich zu machen (sprich ohne Arrays, Zeigern usw.) dachte ich mir, ich löse das so:

void rueckmeldungen()
{
  if(Serial3.available() == 10)
  {
    if(Serial3.find("RTE 1\r\n\r\n#") == 1)
    {
      Serial.println("blabla");
    }
  }
}

Leider scheint es nicht zu funktionieren. Da ich nirgends im Netz ein Tutorial zu Serial.find finden kann, stecke ich mal wieder fest.

Vielleicht könnte sich ja nochmals jmd. erbarmen.. :cold_sweat:

Zum Einsatz kommt übrigens ein Arduino Mega.

Gruß Chris

Serial.find() sieht interessant aus, hab ich aber noch nie verwendet.

The function returns false, if it times out.

Wenn also z.B. "RTE1" statt "RTE 1" kommt, bleibt das Ganze erstmal im Serial.find() hängen, fürchte ich.
Wenn der Sender andauernd irgendwas sendet, hängt find() evtl. ewig.
Wie lang ist das Timeout ?

Tutorials zu "Arduino: Texte von Serial lesen und auswerten" benutzen nicht die Funktion find(),
sondern werten entweder jedes Zeichen direkt aus oder lesen die erwartete Anzahl Zeichen und machen dann strcmp oder so

Um es mir so einfach wie möglich zu machen (sprich ohne Arrays, Zeigern usw.)...

Es ist wohl auf Dauer einfacher mit "Arrays, Zeigern usw. ". Nur Mut, dabei können wir helfen.

So- jetzt hab ich es endlich!

Konnte einen Codefetzen aus diesem Thread.. http://forum.arduino.cc/index.php/topic,137669.0.html ..auf meine Anforderungen hin umbauen.

Serial.find() konnte ich nicht verwenden, da es mir alles ausgebremst hat.

Das Ganze sieht nun (ohne den restlichen Code) so aus:

char commandbuffer[7];
char c;
int i=0;

void setup()
{
  Serial.begin(9600);	                                     
  Serial3.begin(19200);
  Serial3.setTimeout(0);
}

void loop()
{
  
//  <- An dieser Stelle steht mein kompletter restlicher Code, der auch noch verarbeitet werden muss.
  
  if (Serial3.available())
  {
    c=Serial3.read();
    commandbuffer[i] = c;
    i++;
  }
  if (strcmp(commandbuffer,"Libelle")==0)
  {
    Serial.println("Libelle gefunden");
    i = 0;
    memset(commandbuffer,0,sizeof(commandbuffer));
  }
}

Der Vorteil ist, dass ich nach der Aufnahme des ersten Zeichens nicht abwarte (und Zeit verliere), bis das nächste Zeichen eintrifft, sondern einfach mit dem Code weitermache.

Mein Problem ist nun jedoch, dass mir beim Eintreffen falscher Zeichen das commandbuffer-Array mit Müll vollgeschrieben wird und es keine sinnvollen Befehle mehr aufnehmen kann.

Wie könnte ich das umgehen, ohne das aktuell eingehende Zeichen nur darauf hin zu kontollieren, ob es ein L ist. Würd ich das machen, hätte ich ja spätestens dann ein Problem, wenn die Zeichenabfolge "Libellle" reinkommen würde.

Gruß Chris

Mehrere Möglichkeiten:
a) es gibt ein Endezeichen ( z.B. '\n' ) : dann wird auch der Puffer gelöscht und neu angefangen
b) Du prüfst gleich jedes Zeichen auf den passenden Partner im Vergleichsstring
Dann erkennst du auch z.B. "xLibyzLibelle"

const char comparevalue[]="Libelle";
if (Serial3.available())
{
    c=Serial3.read();
    commandbuffer[i] = c;
    if (commandbuffer[i] == comparevalue[i])
    {
        i++;
        if (i == sizeof(comparevalue))
        {  
            //OK
             Serial.println("Libelle gefunden");
              i = 0;
              memset(commandbuffer,0,sizeof(commandbuffer));
        }
   }
   else
   { 
         // BAD
          Serial.print(commandbuffer); Serial.println ("  ist falsch");
          i = 0;
          memset(commandbuffer,0,sizeof(commandbuffer));
   }
}

im Beispiel "xLibyzLibelle" erhältst du
x ist falsch
Liby ist falsch
z ist falsch
Libelle gefunden

aber damit LibLibelle funktioniert, musst du noch ein bisschen feilen, denn so gibts
LibL ist falsch
i ist falsch
b ist falsch
e ist falsch
...

Aber du willst sicher gar nicht so was, sondern lieber mit festen (Zeilen-)Anfängen arbeiten.

edit: besser, aber immer noch ungetestet; die Idee sollte aber klar sein :wink:

Siehst Du- das Problem ist rel. schnell beschrieben, eine Lösung (für mich bis dato) nicht in Sicht.

Insbesondere dann, wenn auf verschiedenste Tiernamen (um bei dem Libellenbeispiel zu bleiben) reagiert werden soll, wird weder a) noch b) wirklich funktionieren.

Trotzdem vielen Dank für Deine Mühen!

Gruß Chris

a) ( Text von Anfang bis Zeilenende mit einer Liste von Texten vergleichen, und die Elementnummer der Vergleichstabelle, oder -1 falls nicht gefunden, liefern ) ist einfach.
Noch einfacher wäre es, wenn es nicht um Tiernamen, sondern um Kommandos mit fester Länge geht.
Etwas mühsamer wird es, wenn die konstante Vergleichsliste nicht im RAM, sondern aus Optimierungsgründen im FLASH liegen soll...

Vielleicht erbarmt sich jurs und liefert eine Musterlösung :wink:

Chris72622:
So- jetzt hab ich es endlich!
...
Mein Problem ist nun jedoch, dass mir beim Eintreffen falscher Zeichen das commandbuffer-Array mit Müll vollgeschrieben wird und es keine sinnvollen Befehle mehr aufnehmen kann.

Wie denn nun: "Du hast es endlich" oder "Du hast ein Problem".

Oder "Du hast endlich ein Problem"? :grin:

Wenn Du empfangene Zeichen auswerten möchtest, dann müßtest Du das genaue serielle Protokoll kennen, mit dem gesendet wird.

Insbesondere kannst Du nicht endlos immer einen Befehl empfangen, sondern es muß irgendeine Abbruchbedingung geben;
a) entweder ein abschließendes Zeichen, z.B. Zeilenende-Zeichen als Zeichen dafür, dass eine Befehlszeile zuende ist oder
b) eine Timeout-Zeit, nach der man einen ggf. unvollständig oder falsch empfangenen Befehl abbricht und einen neuen Lesevorgang beginnt

Ohne das genaue serielle Protokoll zu kennen, nach dem empfangen (und ggf. auch gesendet) werden soll, mit allen verschiedenen Befehlen, Befehlstypen, Sendeschemas und Optionen, kannst Du auch sinnvollerweise keine Ausleseroutine programmieren.

Weiter oben las es sich so, als wenn Du immer nur "RTE?" senden und als Antwort "=1" oder "=2" empfangen wolltest. Heute schreibst Du, dass sich die Anforderungen geändert haben. Aber wie hat es sich geändert? Was soll gesendet, was empfangen werden, und nach welchem Sendeschema?

Hallo,

es tut mir leid, wenn ich mich bei der Suche nach Lösungen aus Eurer Sicht im Kreis gedreht habe. Ich wollte es allerdings vermeiden, für jede "Kleinigkeit" einen neuen Thread zu eröffnen.

Was ist nun mein Ziel:

Ich suche nach einer Möglichkeit, seriell eingehende Zeichen permanent auf verschiedenste Zeichenabfolgen hin zu überprüfen, ohne dabei delay zu verwenden. Hierfür konnte ich, wie bereits weiter oben in dem Libellencode-Beispiel dargestellt, einen Code von jurs modifizieren.

Hier mal zwei Zeichenabfolgen wie sie dann auch empfangen werden sollen:

RTE 1\r\n\r\n# (Hex: 52 54 45 20 31 0D 0A 0D 0A 23)

oder

FREEZ?\r\n\r\n=0\r\n\r\n# (Hex: 46 52 45 45 5A 3F 0D 0A 0D 0A 3D 30 0D 0A 23)

Das sendende Gerät schickt am Ende einer Zeichenfolge immer ein # um zu signalisieren, dass es wieder bereit ist.
Ich würde den Code aber gerne von Anfang an so konzipieren, dass ich ihn auch auf Geräte die sich anders verhalten übertragen kann.

Nun zu meinen noch bestehenden Problemen:

  1. Der Libellencode hat den Nachteil, dass er nicht mehr funktioniert, sobald zuvor fehlerhafter Code empfangen wurde, weil gesuchte Zeichenabfolgen dann u.U. von der Buffergröße beschnitten werden. In diesem Zusammenhang weiss ich auch nicht was ich machen soll/kann, wenn das Ende des Buffers erreicht ist.

  2. Ich möchte ab dem Moment, wo Daten zum ersten Mal eintreffen zunächst nicht erst eine bestimmte Zeitdauer warten, um erst dann den Speicher auszulesen, sondern der Speicher soll kontinuierlich ausgelesen werden. Auch bei großer Buffergröße stoße ich dann ja leider irgendwann auf das Problem, dass ich die Grenze des Buffers erreiche.

Die Buffergröße scheint somit mein Problem zu sein. Ich bräuchte praktisch so etwas wie einen unendlich großen Speicher, dessen Inhalt erst dann gelöscht wird, wenn eine der gesuchten Zeichenabfolgen erkannt wurde.

Gruß Chris

Chris72622:
Die Buffergröße scheint somit mein Problem zu sein. Ich bräuchte praktisch so etwas wie einen unendlich großen Speicher, dessen Inhalt erst dann gelöscht wird, wenn eine der gesuchten Zeichenabfolgen erkannt wurde.

Nein, der Puffer muss nur so gross sein, dass er die längste von Dir auszuwertende Zeichenfolge aufnehmen kann, plus eins für das Zeilenendezeichen bei C-Strings ('\0').

Grund: Alle auszuwertenden Zeichenfolgen stehen entweder zwischen "\r\n" und "\r\n" oder zwischen "#" und "\r\n". Du kannst also jedesmal wenn "\r\n" oder wenn "#" empfangen wird, den Eingangspuffer löschen und auf Null zurücksetzen. Wenn der Eingangspuffer vollgeschrieben ist, ohne dass als nächstes "#" pder "\r\n" kommt, ignorierst Du einfach alle weiteren eintreffenden Zeichen. Weil: Wenn die Zeichenfolge nicht in den Puffer passt, handelt es sich nicht um eine Zeichenfolge, die Du auswerten möchtest. Denn Du hast den Puffer ja groß genug definiert, dass die längste auszuwertende Zeichenfolge hineinpasst.

Und jedesmal nach Empfang eines "\r\n" oder "#" kannst Du den Eingangspuffer auf bestimmte Zeichenfolgen prüfen.

jurs:
Wenn der Eingangspuffer vollgeschrieben ist, ohne dass als nächstes "#" pder "\r\n" kommt, ignorierst Du einfach alle weiteren eintreffenden Zeichen.

Wenn ich dann aber alle weiteren eintreffenden Zeichen ignoriere, kommt ja gar keine Auswerung mehr zustande. :astonished:

Angenommen ich werte so wie von Dir vorgeschlagen aus und es kommt eine gültige Zeichenkette reingeflattert, so löst diese durchs abschließende # die entspr. Funktion aus.

Wenn von nun an aber nur noch wirres Zeug reinkommt, wie soll ich dann auf die nächste gültige Zeichenkette reagieren können?

Gruß Chris

Chris72622:
Wenn ich dann aber alle weiteren eintreffenden Zeichen ignoriere, kommt ja gar keine Auswerung mehr zustande. :astonished:

Wieso nicht? Angeblich wird doch jede Sendung mit "#" beendet?
Dann muss ja irgendwann nach weiteren eintreffenden Zeichen auch wieder "#" kommen?
Wenn das der Fall ist, wird der Eingangspuffer gelöscht und resettet.
Und was danach kommt, wird wieder einwandfrei empfangen.

Chris72622:
Angenommen ich werte so wie von Dir vorgeschlagen aus und es kommt eine gültige Zeichenkette reingeflattert, so löst diese durchs abschließende # die entspr. Funktion aus.

Wenn von nun an aber nur noch wirres Zeug reinkommt, wie soll ich dann auf die nächste gültige Zeichenkette reagieren können?

Hm, also das Gerät sendet jetzt plötzlich "wirres Zeug" anstatt Daten, Zeilenende-Zeichen und "#" gemäß Sendeprotokoll?
Oder hast Du so eine gestörte Datenleitung, so dass auf der Leitung nicht nur Daten gemäß Sendeprotokoll laufen, sondern auch "Datenmüll" durch Störungen entstehen kann?

In dem Fall mußt Du zusätzlich in der Empfangsroutine auch noch eine Timeout-Funktion integrieren, also wenn das "#" als abschließende Zeichen nicht innerhalb einer vernünftigen Zeit empfangen wird, dann wird nach dem Timeout eben so getan, als wäre es empfangen worden. Und der Puffer geleert und neu angefangen Zeichen zu zählen.

Mit Datenverlusten auf der Leitung mußt Du immer dann rechnen, wenn Deine Leitungen nicht störungsfrei verkabelt sind.

Wenn Du zum Empfangen erwartest:
RTE 1\r\n\r\n#
und das kommt stattdessen an:
RasdfawerTadasfgfghE234523451fgh\rxcvb\nasdf\retret\nasdfasdf!
dann steckt das zwar auch irgendwo "RTE 1\r\n\r\n" drin, aber es gibt keine Auswerteroutine der Welt, die Dir das da genau so wieder herauszieht, bei solchen wirren Zeichen auf der Leitung.

Wenn Du kein mit CRC-Summen gesichertes Datenprotokoll mit Datenblöcken fester Länge hast, dann kannst Du bei Störungen eintreffende Zeichen nur verwerfen, wenn sie nicht gültig ausgewertet werden können. Und die einzige Möglichkeit zur Störungserkennung, wenn das Ende-Zeichen "#" nicht korrekt über die Leitung kommt, ist ein Timeout. D.h. wenn statt:
RTE 1\r\n\r\n#
das falsche Zeilenende-Zeichen ankommt, z.B.
RTE 1\r\n\r\n!
dann würde ein nachfolgendes Timeout dem Empfangspuffer zurücksetzen. Genauso wenn das Ende-Zeichen "#" überhaupt nicht ankommt:
RTE 1\r\n\r\n
dann würde ein nachfolgendes Timeout den Empfangspuffer ebenfalls zurücksetzen.
Und wenn überhaupt keine Timeouts auftreten und auch die richtigen Zeilenende-Zeichen nicht im Datenstrom vorkommen, dann bleibt empfangener Datenmüll bis zur Puffergröße im Eingangspuffer und weiterer eintreffender Datenmüll wird verworfen. Solange bis entweder wieder ein "#" im Datenstrom vorkommt. Oder bis ein Sende-Timeout auf der Leitung auftritt und keine Zeichen mehr ankommen.

Ok, verstanden.

Mein Code funktioniert einwandfrei, ich wollte durch permanentes Auslesen jedoch mehr Sicherheit und Geschwindigkeit erreichen.

Das bei einem nicht richtig interpretierten # irgendwann ein weiteres # kommen muss, stimmt natürlich.

Werde nun nicht mehr weiter drüber nachdenken.

Danke für die vielen Erklärungen!

Gruß Chris