Forum: Compiler & IDEs Arduino Serial ganze wörter abfragen


von max2d (Gast)


Lesenswert?

Hallo,

ich möchte an den Arduino über einen PC daten senden und diese 
entsprechend auswerten.

Es soll z.B. P13_ON vom pc gesendet werden, worauf der Arduino Pin 13 
einschaltet.

Ich habe auch schon ein Programm für den Arduino geschrieben, nur leider 
reagiert es nur auch einstellige befehle, also 0-9, oder a-z.
Wie muss ich meine Variable deklarieren dass ich wörter auswerten kann?



Hier mein Code, der auch funktioniert, nur will ich halt statt 1 oder 0 
P13_ON bzw. P13_Off senden.
1
int ledPin = 13;
2
int incomingByte;
3
4
void setup ()
5
{
6
  Serial.begin(9600);
7
  pinMode (ledPin, OUTPUT);
8
}
9
10
void loop ()
11
{
12
   if (Serial.available() >0)
13
   {
14
   incomingByte = Serial.read();
15
    if (incomingByte == '1')
16
    {
17
      digitalWrite (ledPin, HIGH);
18
    }
19
20
    if (incomingByte == '0')
21
    {
22
      digitalWrite (ledPin, LOW);
23
    }
24
}

: Verschoben durch User
von Karl H. (kbuchegg)


Lesenswert?

max2d schrieb:

> Ich habe auch schon ein Programm für den Arduino geschrieben, nur leider
> reagiert es nur auch einstellige befehle, also 0-9, oder a-z.
> Wie muss ich meine Variable deklarieren dass ich wörter auswerten kann?

Dann lies mal in deinem C++ oder C Buch im Kapitel über 'String' nach.

von Falk B. (falk)


Lesenswert?

@ Karl Heinz (kbuchegg) (Moderator)

>Dann lies mal in deinem C++ oder C Buch im Kapitel über 'String' nach.

Oder ein Buch über Quantenphysik, Kapitel "Stringtheorie" ;-)

von stefanus (Gast)


Lesenswert?

Schau hier:  http://stefanfrings.de/mikrocontroller_buch/index.html

In Band 2, Kapitel 10.1 und 10.2

Falls Dir das noch zu "hoch" ist, dann lies erstmal die vorherigen 
Programmieranleitungen beginnend ab Band 1.

von foo (Gast)


Lesenswert?

Über einen Kommunikationskanal, hier die serielle werden oft "nur 
einstellige Befehle" übertragen. Macht aber nix... Man überlegt sich ein 
Protokoll, oder benutzt ein bestehendes.
Einzelne Befehle werden dann z.B. zu Nachrichten/Messages
Vorteilhaft sind fixe Start und Endmarkierungen.
Z.B: #PxxE+
Könnte heißen # start
P Port
xx Portnummer
E ein, oder A aus
und + Ende

Nun liest du am uc die Einzelbefehle und speicherst sie in einem array, 
oder du fütterst gleich eine Statemachine damit, die das Zeichen 
verarbeitet.
So wie, wenn # dann start, dann muss P kommen usw... wenn die message 
fertig ist port schalten.. wenn dazwischen # wieder von vorne.

von embrio (Gast)


Lesenswert?

Hi  max2d,

ich hatte zufällig vor kurzem mit dem selben "Problem" zu kämpfen.

Hier ist mein Lösungsvorschlag:

1
string cmd; //Befehlsbuffer
2
3
void setup() {
4
  
5
  //Serielle Datenübertragung starten
6
  Serial.begin(19200); //USB
7
  
8
}
9
10
void loop() {
11
  
12
  cmd=""; //Buffer "leeren"
13
  
14
  if (Serial.available() > 0) {
15
  int h=Serial.available();   
16
17
  for (int i=0;i<h;i++){
18
    cmd += (char)Serial.read();
19
    }
20
  }
21
22
  if ( cmd == "LEDon" ) {
23
    digitalWrite(led, HIGH); //Led einschalten
24
    Serial.print( cmd + "OK" + "\n" ); //Kommando quittieren
25
  }

von embrio (Gast)


Lesenswert?

Hab mal wieder zu schnell abgesendet.

Die String-Deklaration muss 'String' nachtürlich groß geschrieben werden 
(zumindest in der Arduino-IDE):
1
String cmd;


und am Ende vom Code fehlt noch ein '}'

von X. Y. (elektro_henkler)


Lesenswert?

embrio schrieb:
> Hab mal wieder zu schnell abgesendet.
>
> Die String-Deklaration muss 'String' nachtürlich groß geschrieben werden
> (zumindest in der Arduino-IDE):
>
1
String cmd;
>
>
> und am Ende vom Code fehlt noch ein '}'

Dein gesamter Code funktioniert nicht, er wird zwar fehlerfrei 
Übertragen, jedoch bleibt dieser ohne funktion

von Karl H. (kbuchegg)


Lesenswert?

K. M. H. schrieb:

> Dein gesamter Code funktioniert nicht, er wird zwar fehlerfrei
> Übertragen, jedoch bleibt dieser ohne funktion


Ohne Funktion ist er sicher nicht. Nur ist sowas
1
  if (Serial.available() > 0) {
2
  int h=Serial.available();   
3
4
  for (int i=0;i<h;i++){
5
    cmd += (char)Serial.read();
6
    }
7
  }
dann halt schon etwas naiv programmiert. Da muss alles 100% klappen. 
Beim kleinsten Fehler kommt dieses 'Protokoll' nie wieder auf die 
'Füsse'.
Mir ist schon klar, dass nach der Möglichkeit der Übertragung von 
Wörtern gefragt war. Und das erfüllt dieser Code eindeutig. Nur gibt es 
bei Übertragungen noch mehr, worauf man achten muss. Aber im Grunde 
stimmt es schon: Ein Wort wird übertragen, indem man Buchstabe für 
Buchstabe überträgt. Klarerweise muss es einen Mechanismus ergeben, mit 
dem der Empfänger feststellen kann, dass ein Wort zu Ende ist. Da gibt 
es mehrere Möglichkeiten, von denen die Vorab-Übertragung der Wortlänge 
sicherlich eine ist.

: Bearbeitet durch User
von Amateur (Gast)


Lesenswert?

Am besten Du packst das Ganze in ein einfaches Protokoll.

Also z.B. Jeder Befehl besteht aus ???? gefolgt von einem Zeilenumbruch.

Deine Empfangsroutine empfängt dann "endlos" bis ein Zeilenumbruch 
vorbeikommt. Natürlich kann es an dieser Stelle nicht schaden ein paar 
Überprüfungen der Zeichen und der Anzahl zu machen.

Das Zeilenende Zeichen kannst Du zum Start der Erkennung der 
Zeichenfolge nutzen.

Ab hier gibt es zwei Möglichkeiten:
1. Du zerlegst die Zeichenketten in z.B.  "P", "13", "_" und "ON".
   Damit erschlägst Du Ähnlichkeiten wie: "P", "8", "_" und "ON".
   oder                                   "P", "13", "_" und "OFF".
2. Du legst feste Zeichenketten im FLASH ab und nutzt die
   Vergleichsfunktionen, die C bietet

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:

Ooops, jetzt sehe ich es erst.

Das hier
>
1
>   if (Serial.available() > 0) {
2
>   int h=Serial.available();
3
> 
4
>   for (int i=0;i<h;i++){
5
>     cmd += (char)Serial.read();
6
>     }
7
>   }
8
>

ist natürlich ziemlicher Unsinn. Man kann nicht davon ausgehen, dass dem 
Programm mittels 'available' die Wortlänge zugespielt wird. In den 
meisten Fällen wird das nicht der Fall sein. 'available' erlaubt 
lediglich eine Aussage darüber, wieviele Zeichen zum Zeitpunkt des 
Funktionsaufrufs bereits übertragen wurden. Aber das müssen ja beileibe 
nicht alle sein. available interessiert sich ja nicht dafür, ob der 
Sender noch Zeichen hat, die er übertragen möchte, die also noch gar 
nicht auf dem Arduino eingelangt sind. Datenübertragung benötigt Zeit! 
Und zwar wesentlich mehr Zeit als Rechenarbeit. Der Ardunio wird also 
viele tausend male drauf kommen, dass 0 Zeichen übertragen wurden, 
unterbrochen von ein paar einzelnen Fällen, in denen 1 Zeichen 
übertragen wurde. Warum? Weil der Rest des Wortes (die restlichen 
Buchstaben) vom Sender noch gar nicht auf die Leitung gegeben wurde.

: Bearbeitet durch User
von Jürgen S. (jurs)


Lesenswert?

Karl Heinz schrieb:
> Ohne Funktion ist er sicher nicht. Nur ist sowas
> ...
> dann halt schon etwas naiv programmiert.

Nein, ich als Arduino-Programmierer sage:
Der Code ist völlig ohne Funktion.

Ein Code nach der Logik
1
if (Serial.available() > 0) {
2
  // Verarbeite ganz viele Zeichen
3
}
ist direkt zum Scheitern verurteilt. Damit kann immer nur genau ein 
einziges Zeichen verarbeitet werden, so wie er es macht.

Einen anfängergeeigneten Einlesecode für serielle Befehle unter Arduino 
kann man so machen:
1
if (Serial.available() > 0) {
2
  delay(25);
3
  while (Serial.available()>0) {
4
    // Verarbeite bis zu 25 Zeichen
5
  }
6
}

Wenn das Protokoll dann so aussieht, dass zwischen den einzelnen 
"Befehlen" immer Sendepausen sind, sorgt man mit dem "delay(25)" nach 
dem Empfang des ersten Zeichens dafür, dass noch weitere Zeichen im 
seriellen Eingangspuffer eintreffen. Nach Ablauf von 25 Millisekunden 
kann man diese Zeichen dann aus dem seriellen Eingangspuffer in einem 
Rutsch weg verarbeiten.

Der weiter oben gepostete Code ist ziemlich daneben.
Die unter Arduino möglichen "String-Objekte" wie bei seiner Variablen 
"cmd" verwendet man üblicherweise nicht einmal als 
Arduino-Programmierer, da diese String-Objekte zum Rest der Libraries 
vollkommen inkompatibel sind, und zwar sowohl zur AVR libc Library als 
auch zu fast allen Funktionen der speziellen Arduino-Libraries.

von max2d (Gast)


Lesenswert?

> Einen anfängergeeigneten Einlesecode für serielle Befehle unter Arduino
> kann man so machen:
>
1
> if (Serial.available() > 0) {
2
>   delay(25);
3
>   while (Serial.available()>0) {
4
>     // Verarbeite bis zu 25 Zeichen
5
>   }
6
> }
7
>
Also mein Programm sieht jetzt wie folgt aus:
1
if (Serial.available () > 0)
2
{
3
delay (25);
4
5
while (Serial.available () > 0)
6
{
7
   if (Serial.read)= == '13On')
8
   {
9
     led = true;
10
   }
11
}
12
13
}

Leider tut sich nichts. Muss ich also doch noch was ändern? Also in 
einen String schreiben wobei:

> Die unter Arduino möglichen "String-Objekte" wie bei seiner Variablen
> "cmd" verwendet man üblicherweise nicht einmal als
> Arduino-Programmierer, da diese String-Objekte zum Rest der Libraries
> vollkommen inkompatibel sind, und zwar sowohl zur AVR libc Library als
> auch zu fast allen Funktionen der speziellen Arduino-Libraries.

mir ja was anderes sagt.

von Karl H. (kbuchegg)


Lesenswert?

Jürgen S. schrieb:

> Einen anfängergeeigneten Einlesecode für serielle Befehle unter Arduino
> kann man so machen:
>
1
> if (Serial.available() > 0) {
2
>   delay(25);
3
>   while (Serial.available()>0) {
4
>     // Verarbeite bis zu 25 Zeichen
5
>   }
6
> }
7
>
>
> Wenn das Protokoll dann so aussieht, dass zwischen den einzelnen
> "Befehlen" immer Sendepausen sind,

Ich will dir nicht zu nahe treten, aber das ist immer noch Quatsch.
In dem Moment, in dem eine serielle Übertragung auf Sendepausen zur 
Synchronisierung beruht, ist das nicht mehr anfängertauglich.

Nicht ohne Grund wird seit Urzeiten auf jedem Keyboard die 
'Return'-Taste verbaut, mit der man dem Rechner andeutet "Ich habe meine 
Eingabe fertig, jetzt bist du dran, sie auszuwerten".
Das ist auch für einen Anfänger der vernünftigste Weg: Jede Übertragung 
wird mit einem speziellen Zeichen als "Jetzt ist alles fertig 
übertragen" markiert. Ob das dann 'Return' ist, oder irgendein anderes 
Zeichen, welches normalerweise nicht vorkommt (HPGL verwendet zb einen 
';' um ein Kommando zu begrenzen), darüber kann man diskutieren. Aber 
nicht über die grundsätzliche Vorgehensweise, wenn Kommandos in Textform 
übertragen werden.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

max2d schrieb:

> Also mein Programm sieht jetzt wie folgt aus:
>
1
> 
2
> if (Serial.available () > 0)
3
> {
4
> delay (25);
5
> 
6
> while (Serial.available () > 0)
7
> {
8
>    if (Serial.read)= == '13On')
9
>    {
10
>      led = true;
11
>    }
12
> }
13
> 
14
> }
15
>
>
> Leider tut sich nichts.

Das wundert mich nicht. Das compiliert noch nicht einmal.
Dieser Code ist sowohl aus Sicht der Behandlung der UART Unsinn, als er 
auch aus ganz banaler C-Syntaxsicht Unsinn ist. Tu dir selbst einen 
Gefallen und kauf dir ein C-Buch (oder ein C++-Buch) und arbeite das 
erst mal auf einem PC durch. Solange du wesentliche Sprachelemente von C 
(bzw. C++) nicht sicher anwenden kannst und einfache 'Algorithmen' 
umsetzen kannst, hat das doch keinen Sinn. Ich schreib 'Algorithmen' 
absichtlich in Hochkomma, weil das ja eigentlich noch kein wirklicher 
Algorithmus ist, der irgendwie schwer ist. Das ist einfach nur: sammle 
Zeichen, bis die Endekennung kommt und wenn die da ist, dann werte die 
gesammelten Zeichen (vulgo: den String) aus, wobei dir die C++ Klasse 
'string' schon mal einen Löwenanteil der tatsächlich schwierigen Arbeit, 
nämlich das Verwalten des Speichers in dem Zeichen gesammelt werden, 
abnimmt. Egal ob das jetzt auf einem kleinen Arduino für den µC 
Schwerarbeit ist oder nicht, für dich als Anfänger ist das erst mal eine 
große Hilfe, also benutze sie auch.

Aus dem Vorschlag von embrio kann man sich schon einiges herleiten bzw. 
ansehen. Wenn auch die Logik etwas verquert ist.

Und lös dich von der Vorstellung, dass irgendwie magisch dein 'Wort' in 
einem Rutsch übertragen wird. Dein 'Wort' wird ganz normal Buchstabe für 
Buchstabe übertragen. Dein Arduino Programm muss die Buchstaben wieder 
zusammensetzen, bis die Kennung kommt 'Wort zu Ende'. Danach kann die 
übertragene Zeichenkette ausgewertet werden. Ja, nicht für alles gibt es 
auf dem Arduino fertige Funktionen, die man einfach nur aufrufen 
braucht. Manchmal muss man doch tatsächlich richtig programmieren.

1
string cmd; //Befehlsbuffer
2
3
void loop()
4
{
5
  char nextChar;
6
7
  if (Serial.available() > 0) {
8
9
    nextChar = Serial.read();
10
11
    Serial.print( "Zeichen: '" );
12
    Serial.print( nextChar );
13
    Serial.print( "'\n" );
14
15
    if( nextChar == ';' ) {
16
      Serial.print( "Kommando vollständig '" );
17
      Serial.print( cmd );
18
      Serial.print( "'\n" );
19
      
20
      if( cmd == "LEDon" )
21
        digitalWrite(led, HIGH);
22
      else if( cmd == "LEDoff" )
23
        digitalWrite(led, LOW);
24
 
25
26
      cmd = "";
27
    }
28
29
    else {
30
      cmd += nextChar;
31
32
      Serial.print( "Kommando bis jetzt " );
33
      Serial.print( cmd );
34
      Serial.print( "\n" );
35
    }
36
  }
37
}

Noch ein Tip (der im Code schon ein wenig eingearbeitet ist): Um das 
Stochern im Dunkeln zu vermeiden, was denn eigentlich der µC macht, ist 
es ab und an ganz hilfreich, wenn man sich einfach mal ein paar Dinge 
ausgeben lässt, damit man am Terminal sieht, was der µC empfangen hat 
und was er mit dem Zeichen anstellt.

Im obigen hab ich ein ';' als Trennzeichen benutzt. Kommandos werden 
also zb. in der Form
1
LEDon;LEDoff;
eingegeben, wobei der ';' das Ende der Eingabe markiert. Du kannst auch 
jedes andere Trennzeichen benutzen (zb einen '\n'). Aber um Probleme mit 
der Carriage_Return / Line_Feed Problematik zu vermeiden, hab ich der 
Einfachheit halber erst mal einen ';' gewählt.

: Bearbeitet durch User
von chris (Gast)


Lesenswert?


von Jürgen S. (jurs)


Lesenswert?

max2d schrieb:
> Also mein Programm sieht jetzt wie folgt aus:
>
1
> 
2
> if (Serial.available () > 0)
3
> {
4
> delay (25);
5
> 
6
> while (Serial.available () > 0)
7
> {
8
>    if (Serial.read)= == '13On')
9
>    {
10
>      led = true;
11
>    }
12
> }
13
> 
14
> }
15
>
>
> Leider tut sich nichts. Muss ich also doch noch was ändern?

Damit es einen gruselt, brauchst Du nichts ändern. Es IST GRUSELIG!

Damit es funktioniert, mußt Du mehrere Dinge ändern.

Du hast extreme Defizite, selbst für einen Anfänger mit 
C/C++-Programmierung.

Erstens hast Du nicht verstanden, wie man eine Funktion aufruft:

Wenn Du im Code schreibst "Serial.read", dann ist das die Adresse im 
RAM, wo die Funktion liegt. Wenn Du die Funktion aufrufen und ausführen 
möchtest, gehören die Parameter in Klammern dahinter, auch wenn die 
Parameterliste leer ist. Also Funktionsaufruf mit "Serial.read()".

Als nächstes sind Dir offenbar die Variablentypen und Objekte vollkommen 
unklar, mit denen Du hantierst. Eine char-Variable ist ein einzelnes 
Zeichen, und einzelne Zeichen stehen in einzelnen Hochkommas!

Was machst Du? '13On' ist kein einzelnes Zeichen.
Einzelne Zeichen wären '1' oder '3' oder 'n' oder sowas.

Strings stehen dagegen zwischen DOPPELTEN HOCHKOMMAS.
Eine Stringkonstante sieht im Quelltext daher aus wie "13On" zwischen 
doppelten Hochkommas.

Dir fehlt es an den allerkleinsten Grundlagen der Programmiersprache.

Vielleicht solltest Du mal ein Anfänger-Tutorial durcharbeiten!

Als Code für Arduino und den seriellen Monitor habe ich Dir mal das 
vorbereitet:
1
String cmd; // ==> ein String-Objekt, der verkackteste Datentyp überhaupt!!!
2
3
void setup(){
4
  Serial.begin(9600);
5
}
6
7
void loop(){
8
  if (Serial.available () > 0) // Es ist ein Zeichen im Puffer
9
  {
10
    delay (25); // Warten bis alle Zeichen im Puffer
11
    cmd="";
12
    while (Serial.available()> 0) // Alle Zeichen einzeln auslesen
13
    {
14
      char c=Serial.read();
15
      if (c>=32) cmd=cmd+c; // Nur Zeichen mit ASCII-Code >=32 
16
    }
17
    // Jetzt ist der empfangene String komplett und wird ausgewertet
18
    if (cmd=="ledON") Serial.println("Kommando: ledON");
19
    else if (cmd=="ledOFF") Serial.println("Kommando: ledOFF");
20
    else Serial.println("Unbekanntes Kommando");
21
  }
22
}

Wenn Du mit Zeichen und Strings arbeitest, solltest Du Dir auch ein paar 
Grundlagen über den ASCII-Code aneignen. Steuerzeichen und sichtbare 
Zeichen.

Den seriellen Monitor von Arduino kannst Du nämlich so einstellen, dass 
er nach jeder Eingabe als "Zeilenende" noch zusätzliche Steuerzeichen 
sendet, die Du nicht einzugeben brauchst, und zwar CR (Zeilenumbruch) 
oder NL (New Line) oder beides.

Mit diese Zeile in meinem Code:
      if (c>=32) cmd=cmd+c; // Nur Zeichen mit ASCII-Code >=32
werden diese Steuerzeichen am Ende herausgefiltert und nicht in den 
empfangenen String eingebaut, so dass dieser Code funktioniert, egal wie 
Du den seriellen Monitor eingestellt hast, wie er das Zeilenende senden 
soll, ob als CR, NL oder CR+NL.

Ach ja: Die Eingabe der Kommandos erfordert die Beachtung von Groß- und 
Kleinschreibung. Wenn die Kommandos auch in Abweichender 
Groß-/Kleinschreibung erkannt werden sollen, müßte man den Code etwas 
ändern.

Die Auswertung nach dem Vorschlag von Karl Heinz, mit dem eindeutigen 
ASCII-Zeichen als Befehlsende-Zeichen und ohne auf die vollständige 
Befehlsübertragung zu warten, ist für Anfänger natürlich auch möglich. 
Am besten filterst Du dann aber entweder die Steuerzeichen aus, ohne sie 
in den String aufzunehmen, oder Du konfigurierst den seriellen Monitor 
in Arduino so, dass keine Zeilenende-Zeichen gesendet werden. Sonst 
bekommst Du nämlich bei den Debugausgaben über Serial das Problem, dass 
Du die unsichtbaren Steuerzeichen nicht korrekt dargestellt bekommst.

von Alexander K. (minjaman)


Lesenswert?

Ich benutze das hier erfolgreich:

https://github.com/scogswell/ArduinoSerialCommand

von Karl H. (kbuchegg)


Lesenswert?

Jürgen S. schrieb:

> Die Auswertung nach dem Vorschlag von Karl Heinz, mit dem eindeutigen
> ASCII-Zeichen als Befehlsende-Zeichen und ohne auf die vollständige
> Befehlsübertragung zu warten, ist für Anfänger natürlich auch möglich.
> Am besten filterst Du dann aber entweder die Steuerzeichen aus, ohne sie
> in den String aufzunehmen,

Ich wollte es mal nicht zu kompliziert machen.
Je nachdem, wer am anderen Ende der Leitung sitzt, kann man da noch 
einiges an Aufwand investieren. Vor allen Dingen, wenn ein Mensch am 
anderen Ende der Leitung sitzt. Denn dann müssen vorlaufende und 
nachlaufende Leerzeichen, Tabulatoren ausgefiltert werden, während sie 
aber mitten im Text erhalten bleiben sollen. Und eine Behandlung von 
Backspace wär auch nicht schlecht.

Alles in allem sind das aber Grundfertigkeiten, die jeder beherrschen 
muss, egal ob er dann in einem realen Programm eine vorgefertigte 
Parsing-Komponente benutzt oder nicht. Hier geht es einfach in erster 
Linie um eine Grundtechnik. So wie jeder Mechaniker am Anfang seiner 
Ausbildung den Umgang mit Säge und Feile lernt, egal ob er später dann 
an einer CNC Maschine steht oder nicht.

: Bearbeitet durch User
von Jürgen S. (jurs)


Lesenswert?

Karl Heinz schrieb:
> Ich wollte es mal nicht zu kompliziert machen.
> Je nachdem, wer am anderen Ende der Leitung sitzt, kann man da noch
> einiges an Aufwand investieren. Vor allen Dingen, wenn ein Mensch am
> anderen Ende der Leitung sitzt. Denn dann müssen vorlaufende und
> nachlaufende Leerzeichen, Tabulatoren ausgefiltert werden, während sie
> aber mitten im Text erhalten bleiben sollen. Und eine Behandlung von
> Backspace wär auch nicht schlecht.

Der serielle Monitor von Arduino ist kein richtiges Terminalprogramm. 
Sondern der serielle Monitor hat einen Zeileneditor, in dem man eine 
Befehlszeile vor dem Absenden manuell editieren kann wie man möchte, und 
erst beim Drücken von Return oder auf den Senden-Button wird die ganze 
eingegebene Zeile in einem Rutsch gesendet.

Daher funktioniert für Anfänger auch der oben von mir gepostete Code mit 
dem delay(25), der nach dem Empfang des ersten Zeichens etwas wartet.

Dein Code ist auch für die Verwendung mit manueller Bedienung über ein 
klassisches Terminalprogramm geeignet: Die Zeichen werden so lange 
gesammelt bis das spezielle abschließende Zeichen kommt. Egal wie lange 
es dauert.

Mit Ausfilterung von Steuerzeichen und den fehlenden Deklarationen für 
die setup() Funktion und "led" als OUTPUT gesetzt. Dein Code als 
kompletter Arduino-Sketch:
1
String cmd;
2
byte led=13;
3
4
void setup(){
5
  Serial.begin(9600);
6
  pinMode(led,OUTPUT);
7
}
8
9
void loop()
10
{
11
  char nextChar;
12
  if (Serial.available() > 0) 
13
  {
14
    nextChar = Serial.read();
15
    Serial.print( "Zeichen: '" );
16
    Serial.print( nextChar );
17
    Serial.print( "'\n" );
18
19
    if( nextChar == ';' ) {
20
      Serial.print( "Kommando vollstaendig '" );
21
      Serial.print( cmd );
22
      Serial.print( "'\n" );
23
      
24
      if( cmd == "LEDon" )
25
        digitalWrite(led, HIGH);
26
      else if( cmd == "LEDoff" )
27
        digitalWrite(led, LOW);
28
      cmd = "";
29
    }
30
31
    else if (nextChar>=32){
32
      cmd += nextChar;
33
      Serial.print( "Kommando bis jetzt " );
34
      Serial.print( cmd );
35
      Serial.print( "\n" );
36
    }
37
  }
38
}

> Alles in allem sind das aber Grundfertigkeiten

ACK.

Die Verwendung von String-Objekten, wie es der TO mit der Variablen 
"cmd" beabsichtigt, ist aber auch unter Arduino kontraproduktiv. Die 
Dinger sind voll die Pest: Es gibt eine handvoll Funktionen, die man 
damit einem Anfänger supereinfach erklären kann. Zum Beispiel ein 
Zeichen oder einen String mit dem Pluszeichen hinten dranhängen. Aber zu 
den zig Libraryfunktionen der AVR libc sind diese Stringobjekte genau so 
inkompatibel wie zu den Arduino eigenen Libraries. Da sollte er lieber 
gleich lernen, wie C-Strings in Form von char-Arrays funktionieren.

von Karl H. (kbuchegg)


Lesenswert?

Jürgen S. schrieb:
> Karl Heinz schrieb:
>> Ich wollte es mal nicht zu kompliziert machen.
>> Je nachdem, wer am anderen Ende der Leitung sitzt, kann man da noch
>> einiges an Aufwand investieren. Vor allen Dingen, wenn ein Mensch am
>> anderen Ende der Leitung sitzt. Denn dann müssen vorlaufende und
>> nachlaufende Leerzeichen, Tabulatoren ausgefiltert werden, während sie
>> aber mitten im Text erhalten bleiben sollen. Und eine Behandlung von
>> Backspace wär auch nicht schlecht.
>
> Der serielle Monitor von Arduino ist kein richtiges Terminalprogramm.
> Sondern der serielle Monitor hat einen Zeileneditor, in dem man eine
> Befehlszeile vor dem Absenden manuell editieren kann wie man möchte, und
> erst beim Drücken von Return oder auf den Senden-Button wird die ganze
> eingegebene Zeile in einem Rutsch gesendet.

Ah. Das wusste ich nicht.
Ich dachte, das wäre ein ganz normales Terminal.

Nun denn - wenn dieser serielle Monitor das so handhabt, dann wundert es 
mich nicht mehr, dass Arduino Programme zwar mit diesem Werkzeug 
funktionieren, im realen Einsatz mit beliebigen anderen Progammen aber 
kläglich scheitern.

von Jürgen S. (jurs)


Lesenswert?

Karl Heinz schrieb:
> Nun denn - wenn dieser serielle Monitor das so handhabt, dann wundert es
> mich nicht mehr, dass Arduino Programme zwar mit diesem Werkzeug
> funktionieren, im realen Einsatz mit beliebigen anderen Progammen aber
> kläglich scheitern.

In Arduino-Programmen funktioniert genau das, was man programmiert.

Der von mir mit Posting um 12:34 gepostete Sketch empfängt Befehle, die 
"in einem Rutsch" gesendet werden, aber mit Sendepausen dazwischen. Und 
das funktioniert auch, wenn der Befehl zum Beispiel von einem 
PC-Programm gesendet wurde, das mit Java, Visual Basic, Delphi, Wiring 
oder sonstwas programmiert wurde.

Der von mir mit Posting um 13:24 gepostete Sketch mit Deiner Logik und 
einem Semikolon als abschließendes Zeichen nach dem Befehl funktioniert 
auch bei manueller Eingabe mit Terminalprogrammen.

Allerdings ist der Sketch mit Deiner Logik anfällig für 
"Denial-of-Service" Attacken. Während mein vorher geposteter Code in 25 
Millisekunden bei 9600 Baud nur 25 Zeichen empfangen kann, womit das 
String-Objekt auf maximal 25 Zeichen Länge begrenzt wird, würde bei 
Deiner Logik ein Datenstrom ohne Semikolon dazu führen, dass immer noch 
ein und noch ein Zeichen an das String-Objekt drangehängt wird, bis der 
RAM-Speicher überläuft.

Aber das ist ja hier nicht das Thema, Fehleingaben vernünftig 
abzufangen, der TO wollte ja überhaupt nur einmal so etwas wie Befehle 
empfangen können. Und dazu hat er jetzt zwei Codes zur Auswahl.

von max2d (Gast)


Lesenswert?

1
 String cmd;
2
 byte led=13;
3
 void setup(){
4
   Serial.begin(9600);
5
   pinMode(led,OUTPUT);
6
 }
7
 
8
 void loop()
9
 {
10
   char nextChar;
11
   if (Serial.available() > 0)
12
   {
13
     nextChar = Serial.read();
14
     Serial.print( "Zeichen: '" );
15
     Serial.print( nextChar );
16
     Serial.print( "'\n" );
17
 
18
     if( nextChar == ';' ) {
19
       Serial.print( "Kommando vollstaendig '" );
20
       Serial.print( cmd );
21
       Serial.print( "'\n" );
22
 
23
       if( cmd == "LEDon" )
24
       digitalWrite(led, HIGH);
25
       else if( cmd == "LEDoff" )
26
       digitalWrite(led, LOW);
27
       cmd = "";
28
     }
29
 
30
     else if (nextChar>=32){
31
       cmd += nextChar;
32
       Serial.print( "Kommando bis jetzt " );
33
       Serial.print( cmd );
34
       Serial.print( "\n" );
35
     }
36
   }
37
 }

Ich habe jetzt mal diesen Code genommen.
Funktioniert auch soweit, wenn ich im Serial Monitor das entsprechende 
Kommando setze (LEDon;).
Wenn ich jetzt aber mit meinem Programm, dass ich über Visual Studio in 
C# geschrieben habe den gleichen Befehl sende tut sich nichts!
Erst wenn ich das ; durch einen buchstaben ersetze funktioniert es. 
Warum?!

von Jürgen S. (jurs)


Lesenswert?

max2d schrieb:
> Ich habe jetzt mal diesen Code genommen.
> Funktioniert auch soweit, wenn ich im Serial Monitor das entsprechende
> Kommando setze (LEDon;).
> Wenn ich jetzt aber mit meinem Programm, dass ich über Visual Studio in
> C# geschrieben habe den gleichen Befehl sende tut sich nichts!
> Erst wenn ich das ; durch einen buchstaben ersetze funktioniert es.

Das kann ich irgendwie nicht glauben.
Hast Du vielleicht in Deinem C#-Programm Semikolon und Doppelpunkt 
verwechselt?

Der Arduino-Sketch erzeugt jedenfalls umfangreiche Debug-Ausgaben.

Kopiere mal die Ausgabe aus dem seriellen Monitor heraus, wenn Du 
dreimal nacheinander von Deinem Programm aus den Befehl mit 
abschließendem Semikolon gesendet hast!

von Karl H. (kbuchegg)


Lesenswert?

Jürgen S. schrieb:

> Der Arduino-Sketch erzeugt jedenfalls umfangreiche Debug-Ausgaben.

erstens das (nur müsste er die eben in seinem C# Programm auch sichtbar 
machen oder sonst irgendeine Möglichkeit haben, sich die relevanten 
Informationen anzeigen zu lassen)

zweitens kommt an dieser Stelle der Tip mit der Installation eines 
'Serial Port Monitors' auf dem PC ins Spiel, der einem Byte für Byte 
exakt anzeigt, was eigentlich über die Serielle Schnittstelle rausgeht 
bzw. rein kommt.

: Bearbeitet durch User
von Masl (Gast)


Lesenswert?

Ich empfehle, das Programm "HTerm" zu benutzen (ja, ich weiß, ist kein 
Terminal im engen Sinne).
Da siehst du genau was auf der Leitung passiert, und kannst dir die 
Werte auch in verschiedenen Darstellungen anzeigen lassen (Hex, bin, 
ASCII, dez).

Wenn du mit diesem Tool festgestellt hast, dass dein Arduino-Programm 
stabil läuft kannst du beginnen dein C# Programm zu schreiben.

Dieses Vorgehen ist einfacher als der Versuch, zwei Programme zu 
verheiraten von denen du bei beiden nicht weißt, ob sie funktionieren.
Benutzt du erstmal HTerm oder ein anderes fertiges PC-Tool kannst du dir 
im Fehlerfall sicher sein, dass sich der Fehler im Arduino-Programm 
befindet.
=> systematisches Vorgehen

von Daniel A. (daniel-a)


Angehängte Dateien:

Lesenswert?

Man kann die Kommandos auch empfangen und ausführen ohne diese 
Zwischenspeichern zu müssen. Zudem könnte man die Kommandos in einer 
Liste speichern, um das Hinzufügen neuer Kommandos einfacher zu machen. 
Ich habe für alle interessierten ein kleines Beispiel geschrieben.

von Jürgen S. (jurs)


Angehängte Dateien:

Lesenswert?

Daniel A. schrieb:
> Man kann die Kommandos auch empfangen und ausführen ohne diese
> Zwischenspeichern zu müssen. Zudem könnte man die Kommandos in einer
> Liste speichern, um das Hinzufügen neuer Kommandos einfacher zu machen.
> Ich habe für alle interessierten ein kleines Beispiel geschrieben.

Danke für den Code, funktioniert einwandfrei!

Für Arduino-Interessierte habe ich mal kurz einen über Serial laufenden 
Arduino-Sketch draus gemacht.

von Josef N. (josine01)


Angehängte Dateien:

Lesenswert?

Hallo,

auch wenn der Thread schon ein paar Jahre alt ist, hat er mir sehr 
geholfen, meine eigenen Routinen für das Auslesen des Seriellen Monitors 
zu schreiben.

Nachdem hier ja echte Profis unterwegs sind, möchte ich meine Routinen 
mal online stellen und freue mich sowohl über Kritik als auch Lob.

Das Zusammenfassen der Befehle bitte ich zu entschuldigen, ist für mich 
übersichtlicher.

von Joachim B. (jar)


Lesenswert?

Josef N. schrieb:
> Nachdem hier ja echte Profis

bin ich nicht

aber Titel Arduino

Serial.event ist die Lösung
https://www.arduino.cc/en/Tutorial/SerialEvent

mein Codeschnipsel (fehlendes ergänzen)

sonst war das ja Leichenfledderei von 2014
1
void inSTR_Auswertung(void)
2
{ if(strlen(serial_in_command))
3
  { Serial.println(); Serial.print(serial_in_command);  Serial.println(F(" -> erkannt")); 
4
    // ..... hier gehts weiter
5
    if( !strcmp(serial_in_command, "help") || ( !strcmp(serial_in_command, "?") && strlen(serial_in_command)==1 ) )               // 26672 Bytes
6
    { // help screen
7
      Serial.println(F("\ncommands: \n"));
8
         
9
      Serial.println(F("reset"));
10
      Serial.println(F(""));
11
    }
12
    else if( !strcmp(serial_in_command, "reset") )
13
    { // "reset"
14
      delay(250);
15
      {asm("ldi r30,0"); asm("ldi r31,0"); asm("ijmp");}
16
    } // if( !strcmp(serial_in_command, "reset") )
17
  } // if(strlen(serial_in_command))
18
  memset(&serial_in_command[0], 0, sizeof(serial_in_command)); 
19
  stringComplete = false;
20
} // void inSTR_Auswertung(void)
21
22
23
void serialEvent(void) 
24
{ while (Serial.available()) 
25
  { char incomingByte = (char)Serial.read();
26
    if(incomingByte == '\r')  // Wenn das Enter ankommt
27
    { serial_in_buff[chr_cnt] = '\0';
28
      strcpy(serial_in_command, serial_in_buff);
29
      memset(&serial_in_buff[0], 0, sizeof(serial_in_buff));
30
      chr_cnt = 0;
31
      stringComplete = true;
32
    }
33
    else // Falls kein Enter kommt muss der Text gespeichert werden in dem inText Array
34
    { if(isprint(incomingByte))
35
      { if(chr_cnt<(MAXBUFFER-2))
36
          serial_in_buff[chr_cnt++] = incomingByte;
37
        else
38
        { Serial.println(F("serBUF ov-> DEL"));
39
          *serial_in_command=0;
40
          chr_cnt=0;
41
        } // if(chr_cnt<(MAXBUFFER-2))
42
      } // if(isprint(incomingByte))
43
    } // if !(incomingByte == '\r')
44
  } // while (Serial.available()) 
45
} // void serialEvent()

von Josef N. (josine01)


Lesenswert?

Hallo,

vielen Dank für die Antwort.

Das Tutorial zu Serial.Event habe ich verstanden, allerdings tut es im 
wesentlichen ja auch nichts anderes als mein Code, fragt lediglich 
zusätzlich auf Zeilenende ab. Mir war der Ansatz wichtig, die Abfrage in 
Subs zu lösen und nicht im Hauptprogramm.

Deinen Code habe ich nicht verstanden, der ist mir viel zu komplex. Ich 
vermute, Du baust auf irgendwelchen Objekten auf. Nachdem mein einfacher 
Code aber auch funktioniert, würd ich mir diese mühsame Einarbeitung 
sparen, aber vielen Dank trotzdem.

von Joachim B. (jar)


Lesenswert?

Josef N. schrieb:
> vielen Dank für die Antwort.

kein Problem aber da bei dir überall
1
do {} while (Serial.available() <1); char nextChar;

steht was umständlich und überflüssig ist,

hatte ich auch mal, nutze doch einfach Serial.event

Das läuft im Interrupt nur wenn ein Zeichen eintrudelt!

Mein Code ist eine Mischung aus C und Arduino, weil ich das mit der 
Arduino Stringklasse noch nicht verstanden habe.

Ich definiere einen Buffer
1
char serial_in_command[MAXBUFFER]={0};
2
char serial_in_buff[MAXBUFFER]={0};
3
char s_out_str[MAX_S_OUT_BUFF]={0};
4
unsigned char chr_cnt=0;

lese solange Serial.event aufgerufen wird, sammele Zeichen in 
serial_in_buff ein bis zum Enter,
kopiere die in serial_in_command[MAXBUFFER]

und springe zur Auswertung
1
void inSTRauswertung(void)
2
{
3
//Serial.print(F("0_____")); Serial.println(serial_in_command);                  
4
  if( !strcmp(serial_in_command, "help") || ( !strcmp(serial_in_command, "?") && strlen(serial_in_command)==1 ) )
5
  { // help screen
6
    Serial.println(F("\ncommands: \n"));
7
8
    Serial.println(F("hell, hellp, hellm, hellxx(0-10)"));
9
    Serial.println(F("conmin(50-60)"));
10
    Serial.println(F("conmax(75-80)"));
11
    Serial.println(F("con, conp, conm, conxx(55-75)"));
12
    Serial.println(F("date, dateJJJJ/MO/TA"));
13
    Serial.println(F("time, timeHH:MM:SS"));
14
        
15
    Serial.println(F("up, up?, up07:30"));
16
    Serial.println(F("down"));
17
          
18
    Serial.println(F("stat"));
19
    Serial.println(F("fstat"));
20
    Serial.println(F("i2ceep"));
21
        
22
    Serial.println(F("reset"));
23
    Serial.println();
24
//    memset(&serial_in_command[0], 0, sizeof(serial_in_command));
25
  }
26
27
// hier kommen nun soviel else if wie es Menüpunkte gibt
28
29
  else if( strstr(serial_in_command, "hell") )
30
  { // hell9
31
    if( strlen(serial_in_command)==4 || (strlen(serial_in_command)==5 && strstr(serial_in_command, "?") ) ) 
32
    { Serial.print(F("hell= ")); Serial.print(MY_EEP_VAR.backlicht); Serial.println(); }
33
    else if( !strcmp(serial_in_command, "hellp") )
34
      backlight_p();
35
    else if( !strcmp(serial_in_command, "hellm") )
36
      backlight_m();
37
    else if( !strncmp(serial_in_command, "hell", 4) && strlen(serial_in_command)>4 && strlen(serial_in_command)<7 )
38
    { // "hell??"
39
      uint8_t tmp_hell = atoi( (char *)serial_in_command + 4);
40
      if( tmp_hell>=MIN_BL && tmp_hell<=MAX_BL )
41
      {  MY_EEP_VAR.backlicht = tmp_hell;
42
         analogWrite(NOK_BL, pgm_read_byte(&pwmtable_11C[MY_EEP_VAR.backlicht])); 
43
         con_hell_stat(true);  
44
      }
45
      else
46
      { sprintf_P(s_out_str, PSTR("hellxx (%d)-(%d)"), MIN_BL, MAX_BL); Serial.println(s_out_str); } 
47
    } // if( !strcmp(serial_in_command, "hell??") )
48
  } // if( strstr(serial_in_command, "hell") )
49
50
  #include "c-source\eif_con.c"      
51
  #include "c-source\eif_date.c"      
52
  #include "c-source\eif_time.c"      
53
  #include "c-source\eif_up.c"      
54
  #include "c-source\eif_down.c"      
55
  #include "c-source\eif_auto.c"      
56
  #include "c-source\eif_reset.c"      
57
58
// wenn alles erledigt ist loesche serial_in_command
59
  *serial_in_command=0;
60
  // memset(&serial_in_command[0], 0, sizeof(serial_in_command));
61
} // void inSTRauswertung(void)

von Josef N. (josine01)


Lesenswert?

Joachim B. schrieb
> kein Problem aber da bei dir überall
> do {} while (Serial.available() <1); char nextChar;
> steht was umständlich und überflüssig ist,
> hatte ich auch mal, nutze doch einfach Serial.event
> Das läuft im Interrupt nur wenn ein Zeichen eintrudelt!

Vielen Dank, jetzt hab ich's begriffen und die Interrupt-Lösung ist 
natürlich schon wesentlich eleganter.

Dein Code wird mir inzwischen auch etwas klarer, ist halt ein universell 
einsetzbarer Code, der vorallem Sinn macht für störbehaftete 
Übertragungen. Werd ich später mal genauer anschauen, aber ich brauch 
den seriellen Monitor derzeit nur zum Debugging meiner Alarmanlage, um 
zu schauen, wann die Infrarot- und Radarsensoren auslösen, weil ich 
derzeit noch zu viel Fehlalarme habe. Und die Routinen waren eigentlich 
nur dazu gedacht, die jeweils aktuelle Uhrzeit beim Hochladen des 
Sketches etwas eleganter einzugeben als jeweils im Code die 
Uhrzeitvariablen manuell zu setzen.

Vielen Dank nochmal für Deine Mühe, wenns Dich interessiert, meine 
Alarmanlage ist auf

https://youtu.be/KvP90prt6rM zu sehen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.