Text eingabe und ausgabe über serielle Schnittstelle

Hallo liebe Forumsmitglieder,

ich bin im Bereich Mikrocontroller ein absoluter Anfänger und benötige deshalb Hilfe.

Ich habe schon mit meinem Arduino Mega über die serielle Schnittstelle(RS232) eine LED anhand eines Übungsbeispiels zum leuchten gebracht und konnte die Programmierung nachvollziehen.

Jetzt möchte ich einen Text über die serielle Schnittstelle (im seriellen Monitor) eingeben und dieser Text soll dann gleichzeitig seriell wieder ausgegeben werden.
Wie sieht dabei der Programmcode aus? Welche Befehle benötige ich dafür? Ist es auch möglich, Umlaute im eingebenen Text umzuwandeln (z.B. ä in ae, ö in öe, etc.)?

Ist es weitergehend auch möglich diesen Text aus einer Textdatei zu lesen und auszugeben? auch mit Umwandlung der Umlaute?
Ich habe im Anhang zwei Textdateien die jeweils eingelesen werden sollen. Bei der Datei “Text mit Umlaut” sollen die Umlaute umgewandelt werden.

Ich würde mich über Hilfe freuen.
Vielen Dank.

Text mit Umlaut.txt (33 Bytes)

Text ohne umlaut.txt (27 Bytes)

Jetzt möchte ich einen Text über die serielle Schnittstelle (im seriellen Monitor) eingeben und dieser Text soll dann gleichzeitig seriell wieder ausgegeben werden.
Wie sieht dabei der Programmcode aus?

if (Serial.available() ) Serial.write(Serial.read());

Bei deiner Umwandlung musst du testen, was SerialMonitor bei deinen Umlauten sendet.
Da gibt es mehrere Möglichkeiten mit 1 oder 2 Byte für das einzelne Zeichen.
Bei Dateien ( auf einer SD ?) ist die Frage, wie der Text in die Datei gekommen ist.
Da sind eventuell sogar "einfache" ASCII - Zeichen als 2 Byte gespeichert, und die Umlaute als Unicode mit dem 2. Byte != 0

Google mal Unicode, UTF-8, Code page

ich klinke mich mal mit ein finde das auch sehr Interessant :slight_smile:

wie meinst du die Dateien Einlesen?
sind die auf dem Computer? oder wie muss man das verstehen?

Ich soll für das Studium einen RS232 Umsetzer bauen, d.h. der Arduino soll Parameter eines Programmes verändern, das er über die serielle Schnittstelle von einem Gerät erhält um es per anderer serieller Schnittstelle auf dem Empfängergerät kompatibel zu machen.

Die oben beschriebenen Aufgaben sind nur zur Übung für mich gedacht.

Wenn ich jetzt ä z.B. umwandeln will, kann ich dann sagen

if(serial.read ==ä)
{ serial.read = ae;}

?

Zeigt alles von Serial1 auf Serial an, optional mit Debug Ausgaben

if (Serial1.available())
{
   char c = Serial1.read()
   // Serial.print (c, HEX) ; Serial.write(" : ");  // Debug
   switch (c) 
   {
    case 'ä':   // Ist aber fraglich, ob der Arduino ein 'ä' sieht ...
       Serial.print("ae");
       break;
    // ... andere Sonderfälle
   default:
      Serial.write (c); // Normalfall
      break;
   }
   // Serial.println();  // Debug
}

Wäre zu testen, ob ein Umlaut nicht ( UTF-8 ) als 2 Zeichen ankommt ( das erste wäre evtl. dann eines < 0 ) …
Viel Spass !

char c;

void setup()
{
  Serial.begin(9600);
  Serial.println("Arduino bereit!");
}

void loop()
{
  if (Serial.available())
  { c = Serial.read();
  switch(c)
  {case 196:
  Serial.print("Ae");
  break;
  
  case 228:
  Serial.print("ae");
  break;
  
  case 214:
  Serial.print("Oe");
  break;
  
  case 246:
  Serial.print("oe");
  break;
  
  case 220:
  Serial.print("Ue");
  break;
  
  case 252:
  Serial.print("ue");
  break;
  
  case 223:
  Serial.print("ss");
  break;
  
}
  
    Serial.write(c);
    }
    
}

Mit diesem Code ersetzt mir der Arduino den jeweiligen Umlaut, dazu muss bei case der umlaut mit dem ascii zeichen definiert werden.

Mein Problem ist jetzt....gebe ich z.B. das Wort Übung ein schreibt mir der Arduino "UeÜbung"...wie schaffe ich das er das Ü dann nicht mehr anzeigt?

Serial.write(c);

Ich glaube das darfst du nur machen wenn keiner der anderen Fälle zutrifft. Am besten über default:

c muss übrigens keine globale Variable sein.

Stimmt, du hast Recht! Habe jetzt den default Befehl mit in die switch bedingung reingenommen reingenommen und das Serial.write(c); danach entfernt und es funktioniert.

Gibt es jetzt auch eine Möglichkeit mehrere Zeichen zu ersetzen?
Z.B. das wenn der Arduino die Sequenz "Hallo" erhält, dass er dann "Tschüss" ausgibt.

Das müsste doch irgendwie über Arrays realisierbar sein oder?

Ja. Siehe hier:

Du brauchst die Funktion serialStringRead() und den Teil in loop() der den Rückgabewert abfragt

Strings kann man mit strcmp() vergleichen wenn sie mal eingelesen wurden:
http://www.nongnu.org/avr-libc/user-manual/group__avr__string.html#ga46f3cbd2de457c0fb340a1f379fc33ba
http://www.cplusplus.com/reference/cstring/strcmp/

Ist es auch irgendwie so möglich?
Der Arduino soll in einem Text auf eine Startsequenz ("Start") warten, bevor er die empfangenen Daten ausgibt. Dies soll er tun bis er eine Stoppsequenz ("Stopp") gefunden hat. Während der Übertragung soll er nach dem Wort "Hallo" suchen und es mit "ollaH" ersetzen.

char Startsequenz[6]={'S','t','a','r','t','\0'};
char Stoppsequenz[6]={'S','t','o','p','p','\0'};
char Einlesen[6];
char Filter1[6]={'H','a','l','l','o'};


void setup()
{
	Serial.begin(9600);
	Serial.println("Arduino bereit!");
}


void loop()
{

if(Serial.available() > 0)
{
	Einlesen[6]=Serial.read();

	
	if(Einlesen[6]==Startsequenz[6])
	{ Serial.write(Serial.read());

		if(Einlesen[6]==Filter1[6])		
		{Serial.write("ollaH");
		Serial.write(Serial.read());			}
	}
		


	if([Einlesen[6]==Stoppsequenz[6])
	{ Serial.end;}
}
}

Nein. Du hast zwar den String super ausführlich deklariert, aber überhaupt nicht verstanden was Arrays und Strings in C überhaupt sind.

Erst mal, geht das viel einfacher so:

char startSequenz[] = "Start";

Deine Version ist nicht falsch, aber auch unnötig aufwendig hier

Dann sind Arrays in C lediglich Zeiger auf das erste Element. Die Variable startSequenz ist &startSequenz[0].

startSequenz[6] ist nicht der String, sondern das 6. Element. Hier der Null-Terminator. Und das ist alles was du da vergleichst. Du kannst auch nicht einfach einlesen == startSequenz machen. Damit vergleichst du nur ob die Zeiger gleich sind (nicht mal den Inhalt). Um Strings zu vergleichen gibt es eine Funktion namens strcmp (string compare), die 0 zurückgibt wenn die Strings gleich sind:
http://www.cplusplus.com/reference/cstring/strcmp/
http://www.nongnu.org/avr-libc/user-manual/group__avr__string.html#ga46f3cbd2de457c0fb340a1f379fc33ba
Oder strncmp() um nur dir ersten n Zeichen zu vergleichen

Dann musst du den String aber auch korrekt einlesen. Was du da machst ist Unsinn. Du musst den Index mitzählen und dann immer in den aktuellen Index des Arrays schreiben. Das habe ich oben fertig verlinkt. Hier ist eine etwas einfachere Version davon:

const int SERIAL_BUFFER_SIZE = 31;
char serialBuffer[SERIAL_BUFFER_SIZE];

char startSequenz[] = "Start";

void loop()
{
   if(readSerial() == true)
   {
       if(strcmp(serialBuffer, startSequenz) == 0)
       {
              Serial.println("Start!");
       }
   }
}

bool readSerial()
{
	static int index;

        if(Serial.available() > 0)
	{		
		char c = Serial.read();
		
		if(c != '\n' && index < SERIAL_BUFFER_SIZE - 1)
		{
			serialBuffer[index++] = c;
		}
		else
		{
			serialBuffer[index] = '\0';
                        index = 0;
			return true;
		}
	}
	return false;
}

Einfacher wird es nicht. Das geht davon aus, dass am Ende ein Linefeed ‘\n’ gesendet wird. Also den Serial Monitor entsprechend einstellen! Da gibt es ein Dropdown Menü dafür. Wenn am Ende kein LF kommt geht das nicht, aber man muss halt irgendwie das Ende mitbekommen.
Man kann auch ein CR nehmen. Dann muss man im Code das ‘\n’ durch ein ‘\r’ austauschen. Das bietet sich an wenn man wirklich LFs speichern will.

Dein Problem wie beschrieben ist komplizierter, aber du musst erst mal verstehen wie man normal eine Zeile einliest bevor du da solche Sachen machst.

Nach Start und Stopp kann man wie gesagt mit strcmp() abfragen, wenn diese einzeln gesendete Zeilen mit einem LF am Ende sind. Nach einem Teil-String in einem größeren String sucht man mit strstr():
http://www.cplusplus.com/reference/cstring/strstr/
Das gibt dir einen Zeiger auf das erste Zeichen des Teil-Strings zurück.
Danach kann man mit strncpy() einen anderen String da rein schreiben (strncpy weil die n-Version keinen Null-Terminator schreibt):
http://www.cplusplus.com/reference/cstring/strncpy/

Aber wie du siehst braucht man String Funktionen. Du kannst nicht einfach mit == vergleichen und = ganze Strings schreiben, da du immer nur Zeiger auf einzelne Zeichen hast!

@Serenifly: readSerial in loop ist checkSerial, nehme ich mal an ...

Diese Funktion liefert die laufende Texteingabe als Pakete von ganzen Zeilen. Das ist in der Regel und auch hier sinnvoll, da Garfield sich explizit nicht die Schwierigkeit ausgedacht hat, dass "Start" "Stopp" "Hallo" durch Zeilenwechsel getrennt werden.

Während der Übertragung soll er nach dem Wort "Hallo" suchen und es mit "ollaH" ersetzen

Für solche Übungsaufgaben, und was zu tun ist, wenn "Start" nicht am Anfang kommt, sollte man sich die Funktion strstr ansehen.
-- Nachdem Serenifly's Einführung klar ist :wink:

bla blah Start123 34 irgendein
Text mit HalloHallo Stop mitten
drinStopp und noch ein Stopp mehr

soll diese Ausgabe werden:

123 34 irgendein
Text mit ollaHollaH Stop mitten
drin

Mir ist noch unklar, was nach "Stopp" weiter passieren soll: vermutlich wieder alles ignorieren, bis erneut "Start" kommt.
Dafür bräuchte man noch einen Merker ...

Alternativ könnte man ( Was ist ein "Wort" ? ) nicht nur '\n' oder '\r' sonderbehandeln, sondern auch Leerzeichen... )

Ausgebessert :slight_smile: Wollte die Funktion etwas besser benennen und habe es dann nicht überall gemacht

Hier gibt es einige Varianten, je nachdem wie der Text genau aussieht. Aber so oder so muss man erst mal verstehen wie man allgemein eine Zeile ausliest.

strcmp() ist wenn die Vergleichs-Strings absolut identisch sind. strncmp() ist um die erste n Zeichen zu vergleichen. strstr() ist wenn der zu suchende String irgendwo steht. Dabei mit dem Rückgabe-Wert aufpassen. Der ist bei strstr() anders als bei strcmp()

Für das herumdrehen kann man auch einfach strrev() verwenden:
http://www.nongnu.org/avr-libc/user-manual/group__avr__string.html#gacfdb3ab0c1f988f86d04d706d8e0ce3f
Das dreht den String in situ (in place/im Ort).
Je nachdem wie man das einliest muss man dann den Anfangs-Zeiger entsprechend wählen (z.B. damit er nach "Start" ist, falls das im Array steht) und eventuell den String terminieren, so dass das "Stopp" nicht dabei ist. Für letzteres kann man '\0' auf die Speicherzelle schreiben die bei strstr() mit "Stopp" geliefert wird.

ok ich habe jetzt mal versucht, die einkommenden Zeichen in ein Feld zu schreiben und dieses auszulesen.
Jedoch erhalte ich bei Eingabe (hier:Hallo) einen Fehler (y mit zwei punkten).
Was ist das für ein Fehler?

Wieso hältst du dich nicht an fertigen Code? Das hat schon seinen Sinn. Die serielle Übertragung ist sehr langsam. Bei 9600 Baud dauert ein Zeichen ca. 1ms! Wenn du auf einfach auf available() > 0 abfrägst fängt er nach dem ersten Zeichen schon an und versucht Zeichen einzulesen die noch gar nicht im Eingangspuffer stehen.

Man muss also nach dem ersten Zeichen auf den Rest warten. Das kann man bei so primitiven Programmen mit einem Delay machen, aber in komplexen Programmen blockiert das den Rest des Codes. Oder man fragt auf eine größere Zahl ab. Das ist aber auch meistens schlecht, da die Übertragenen Strings selten konstant lang sind (gibt aber auch Anwendungen wo das der Fall ist). Oder man fragt ständig ab ob was im Puffer steht, und liest solange ein bis ein Endzeichen kommt.

Serenifly:
Wieso hältst du dich nicht an fertigen Code?

Um ehrlich zu sein habe ich versucht den Code nachzuvollziehen, habe aber nicht ganz verstanden was die einzelnen Schritte machen und wollte daher nochmal bei 0 beginnen :frowning:

Wenn du nicht verstehst was du machen musst, ist das aber auch zum Scheitern verurteilt. Du hast z.B. auch nicht beachtet dass C Strings unbedingt mit ‘\0’ oder 0 terminiert werden müssen. Sonst lesen die String Funktionen über das Ende hinaus.

Der Code ist eigentlich recht einfach. Man zählt einen Index mit wie viele Zeichen man schon eingelesen hat. Wenn der Index 0 ist, setzt man den gesamten Puffer auf 0 (alternativ kann man auch am Ende einmal ‘\0’ in den letzten Index schreiben. Beides sorgt für die korrekte Terminierung). Dann wird die Funktion ständig aufgerufen und schaut nach ob Zeichen im Eingangspuffer stehen. Wenn ja, liest man ein Zeichen. Wenn dieses ungleich der Endekennung ist (hier Linefeed → != ‘\n’) und noch Platz im Puffer ist, schreibt man das Zeichen in den aktuellen Index und inkrementiert diesen danach. Wenn die Endekennung gelesen wurde oder der Puffer keinen Platz mehr für das aktuelle Zeichen und den Terminator hat (index < SERIAL_BUFFER_SIZE - 1) hört man auf einzulesen. Statt dessen wird der Index wieder auf 0 gesetzt.

Die Funktion hat einen Rückgabewert. Sie gibt false zurück wenn man noch nicht am Ende ist und true wenn man fertig ist. Diesen Wert kann man dann in loop() abfragen (if(readSerial() == true)) und den Puffer auswerten. Die Funktion wird dann ständig durch loop() aufgerufen. Deshalb kann man zwischendurch auch andere Sachen machen ohne dass da was blockiert.

Das ganze funktioniert aber nur wenn du im Serial Monitor “Endekennung” auf Linefeed/LF schaltest. Oder CR und den Code auf ‘\r’ ändern. Dann wird bei jeder Eingabe am Ende automatisch in LF oder CR angehängt. Dadurch weiß man auf dem Arduino wann man fertig ist. In deinem Screenshot steht das auf “kein Zeilenende”. Dadurch bekommt das Ende nicht mit.

Für das Auswerten und Bearbeiten des Strings musst dir dann wie gesagt String Funktionen wie strcmp(), strncmp() oder strstr() ansehen. Es gibt auch noch anderen Funktionen um nach Zeichen zu suchen, Teil-Strings in Zahlen zu wandeln (atoi() und Konsorten) oder Strings nach Trennzeichen aufzusplitten. Das ist am Anfang vielleicht etwas gewöhnungsbedürftig, da die Funktionen etwas anders arbeiten als die String Klassen in höher gezüchteten Sprachen. Wenn man nach Teil-Strings oder Zeichen sucht, bekommt man z.B. in der Regel einen Zeiger auf das gefunde Element zurück. Da Arrays Zeiger auf das erste Element sind, kann man diese wie eigene Teil-Strings behandeln.
z.B. geht wenn man diesen String eingibt “xxxSuchyyyy” und man das macht:

char* ptr = strstr(serialBuffer, "Such");
Serial.println(ptr);

Wird “Suchyyyy” ausgegeben.

Oder man kann das machen:

char* ptr = strstr(serialBuffer, "stop");
*ptr = '\0';
Serial.println(serialBuffer);

Wenn der String dann “12345stop6789” ist wird dabei “12345” ausgegeben.
Wieso? strstr() liefert einen Zeiger auf den Anfang von “stop”, also das ‘s’. Diesen Zeiger dereferenziert man mit mit dem * und schreibt ein ‘\0’ an die Stelle um den String zu terminieren. Damit wird der Rest abgeschnitten.

Garfield282:
Was ist das für ein Fehler?

Das ist überhaupt kein Fehler, sondern genau das, was Du programmiert hast!

Du prüft per Serial.available, ob mehr als null Zeichen im seriellen Eingangspuffer vorhanden sind.
Diese Bedingung ist üblicherweise dann erfüllt, wenn sich genau EIN einziges Zeichen im Eingangspuffer befindet.
Und dann versuchst Du, fünf Zeichen aus dem Eingangspuffer auszulesen, in dem nur ein Zeichen drin ist.

Dabei passiert folgendes:
Das erste (tatsächlich vorhandene) Zeichen wird ausgelesen und angezeigt.
Bei den nachfolgenden vier Zeichen ist nichts mehr im Puffer und Serial.read() ergibt die Integer-Zahl "-1" als Ergebnis. Das niederwertigste Byte davon ist das, was Du ausgibst. Und bei einem Vorzeichenlosen Byte (Wertebereich 0 bis 255) ergibt 0-1 den Wert 255, also einen Zahlenunterlauf. Du gibst dann viermal das Zeichen mit dem ASCII-Code 255 aus, das ist das ÿÿÿÿ.

Und wenn das nächste Zeichen im seriellen Eingangspuffer eintrifft (was bei 9600 Baud nach circa 0,001 Sekunden der Fall ist), machst Du wieder exakt dasselbe: Ein Zeichen ist da, Du versuchst fünf Zeichen auszugeben, davon ist das erste das tatsächlich vorhandene Zeichen und viermal dahinter das fehlerhaft weil aus dem leeren Puffer gelesene ÿÿÿÿ.

Nur weil es Dir so vorkommt, als würdest Du fünf Zeichen gleichzeitig absenden, ist es noch lange nicht der Fall, dass die Zeichen beim Arduino gleichzeitig eintreffen. Tatsächlich liegen zwischen den Zeichen immer 0,001 Sekunden, und das ist für einen Arduino mit 16 MHz Systemtakt eine lange, lange, Zeit, in der er eine ganze Menge erledigen kann. Z.B. diverse male einen Fehlercode aus einem leeren Eingangspuffer auslesen und zurücksenden, wenn Du das so programmierst.

Kann mir jemand mal den Code schreiben, dass wenn ich die Textzeile "HalloStartWetterStopHallo" , dass der Arduino mir nur ab der Startsequenz "Start" die bis zur Stopsequenz "Stop" eingelesenen Bytes ausgibt? :~
also nur "StartWetterStop".