Forum: Mikrocontroller und Digitale Elektronik Zeichenkette über UART lesen. RETURN klappt nicht


von Jürgen H. (misteret)


Lesenswert?

Hallo, ich nutze die folgende Version einer UART-String-Erkennung aus 
dem Tutorial:
1
/* Zeichen empfangen */
2
uint8_t uart_getc(void)
3
{
4
    while (!(UCSRA & (1<<RXC)))   // warten bis Zeichen verfuegbar
5
        ;
6
    return UDR;                   // Zeichen aus UDR an Aufrufer zurueckgeben
7
}
8
 
9
void uart_gets( char* Buffer, uint8_t MaxLen )
10
{
11
  uint8_t NextChar;
12
  uint8_t StringLen = 0;
13
 
14
  NextChar = uart_getc();         // Warte auf und empfange das nächste Zeichen
15
 
16
                                  // Sammle solange Zeichen, bis:
17
                                  // * entweder das String Ende Zeichen kam
18
                                  // * oder das aufnehmende Array voll ist
19
  while( NextChar != '\n' && StringLen < MaxLen - 1 ) {
20
    *Buffer++ = NextChar;
21
    StringLen++;
22
    NextChar = uart_getc();
23
  }
24
 
25
                                  // Noch ein '\0' anhängen um einen Standard
26
                                  // C-String daraus zu machen
27
  *Buffer = '\0';
28
}

Wenn ich TEST + RETURN im Terminal eingebe und den erhaltenen String mit
1
strcmp(string, "TEST");
 vergleiche, erkennt er es nicht als identisch an.

Modifiziere ich den Quellcode und mach anstatt RETURN als 
Abschlusszeichen ein was weiß ich zB 'p' daraus, also
1
while( NextChar != 'p' && StringLen < MaxLen - 1 )
, dann klappt das ganze mit strcmp

Wenn ich den String ausgeben lasse, dann sieht er in beiden Fällen 
identisch aus.

Nur die Vergleichsfunktion klappt nicht....

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Jürgen Hems schrieb:
> Wenn ich TEST + RETURN im Terminal eingebe und den erhaltenen String mit
>
1
strcmp(string, "TEST");
 vergleiche, erkennt er es nicht als
> identisch an.

Die RETURN-Taste in einer Terminal-Emulation sendet ein CARRIAGE-RETURN 
(CR). Das ist in C ein '\r' und kein '\n'. Ein NEWLINE ('\n') müsstest 
Du durch Drücken von STRG-J in der Terminal-Emulation erreichen... zu 
umständlich.

Also ändere das '\n' in ein '\r' und alles wird gut.

Gruß,

Frank

von Karl H. (kbuchegg)


Lesenswert?

Frank M. schrieb:

> Die RETURN-Taste in einer Terminal-Emulation sendet ein CARRIAGE-RETURN
> (CR). Das ist in C ein '\r' und kein '\n'. Ein NEWLINE ('\n') müsstest
> Du durch Drücken von STRG-J in der Terminal-Emulation erreichen... zu
> umständlich.

um da weiter auszuholen:

Was genau ein Terminal beim Drücken der Return Taste sendet, ist bei 
einem Terminal normalerweise einstellbar.

Das kann ein \n sein, kann ein \r sein, kann aber auch beides 
hintereinander sein. Da dein Code offenbar aus der while-Schleife 
rausgekommen ist, muss also ein \n daher gekommen sein. D.h. es könnte 
sein dass dein empfangener String in Wirklichkeit so aussieht

"TEST\r"

Feststellen lässt sich das zb, in dem du den String ans Terminal 
zurückschickst und den String mit bekannten Sonderzeichen deiner Wahl 
einrahmst. Also zb

  uart_puts( "#" );
  uart_puts( string );
  uart_puts( "#" );

nur dann, wenn auf dem Terminal tatsächlich

#TEST#

steht, weisst du, dass sich da (höchst wahrscheinlich) kein 
Steuerzeichen in den String geschummelt hat.

Steht auf deinem Terminal

#TEST

oder

#TEST
#

(man beachte die Position des zweiten #), dann ist da ein Sonderzeichen 
drinnen.

Sieht die Ausgabe aber so aus

#
TEST#

dann hat das Terminal die Sequenz umgedreht und \n\r gesendet und im 
String steht

"\rTEST"

wobei das führende \r ein Überbleibsel aus der vorhergehenden 
Übertragung ist.


Eine Lösung mit der man dieses Problem entschärfen kann, dass man in 
einem gewissen Grad von der Einstellung des Terminals abhängig ist, wäre 
zb \r zu ignorieren und auf \n zu warten. Oder umgekehrt \n zu 
ignorieren und auf \r zu warten.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> Was genau ein Terminal beim Drücken der Return Taste sendet, ist bei
> einem Terminal normalerweise einstellbar.

Das ist korrekt, der Standardwert (Default) ist aber immer CR ('\r').

Die Taste heißt nicht umsonst RETURN, denn sie ist die frühere 
Wagenrücklauftaste (Carriage Return) bei den Teletypes (TTY). Und da 
konnte man das gar nicht umstellen ;-)

Um die Verwirrung perfekt zu machen, hole ich mal etwas weiter aus und 
versuche aus der Historie zu erklären, wieso es heute immer noch ein 
Problem ist:

UNIX wurde Anfang der 70er Jahre entwickelt. Als Zeilentrenner für 
Dateien wurde das NL ('\n' "NewLine") gewählt. Die Teletypes sendeten 
aber schon damals bei Drücken auf die RETURN-Taste ein CR ('\r'). Aus 
diesem Grunde mappte der Treiber einer UNIX-Console (und aller anderen 
vorhandenen TTYs) das CR ('\r') wieder zurück auf NL ('\n'). Diese 
Einstellung hat sich bis heute in allen UNIX-Derivaten gehalten - auch 
unter Linux. Nur deshalb kann man in C zum Beispiel schreiben:
1
  while ((ch = getchar()) != '\n')
2
  {
3
     // tu was
4
  }

So ein C-Programm lässt sich damit sowohl mit einem UNIX/Linux-TTY als 
auch per stdin-Redirection auf Dateien anwenden (wo unter UNIX immer 
'\n' als Zeilentrenner drin steht). Das ist nur dem CR->NL-Mapping des 
Unix-TTY-Treibers zu verdanken.

Die stty-Einstellung dazu heisst 'icrnl' und lässt sich mit 'stty 
-icrnl' in der Shell wieder abschalten. Dann "sieht" man wieder die 
Original-CR-Taste und man muss '\r' in seinem C-Programm schreiben, um 
die RETURN-Taste abzufragen.

Auf einem µC gibt es dieses CR->NL Mapping natürlich nicht. Deshalb ist 
es erstmal die richtige Vorgehensweise, das CR ('\r') als Standard 
anzunehmen.

Natürlich hast Du Recht, wenn Du schreibst, dass man hier flexibel sein 
sollte, also dass man sowohl '\r' als auch '\n' in seinen µC-Programmen 
als Zeilentrenner akzeptiert. Ich selbst checke immer sowohl auf '\r' 
als auch auf '\n' und ignoriere dann ein nachfolgendes NL ('\n'), wenn 
ich schon ein CR ('\r') empfangen habe.

Diese Kombination CR + NL ('\r' gefolgt von einem '\n') haben wir dem 
VAX/VMS-Betriebssystem vom damaligen Hersteller DEC zu verdanken. Hier 
wurde als Zeilentrenner die Kombination aus beiden Zeichen gewählt. 
Microsoft hat das damals für MSDOS dann auch so übernommen. Dies liegt 
wohl an den damaligen Druckern, die immer ein CR für den Wagenrücklauf 
und dann ein NL für den Zeilenvorschub erwarten. Auch die damaligen 
Teletypes (und später auch Terminals) erwarteten beides! Aus diesem 
Grunde mappt der TTY-Treiber eines Unix-Systems bei der Ausgabe das NL 
wieder auf CR + NL. ("onlcr" im TTY-Treiber, kann man mit "stty -onlcr" 
abschalten).

Somit kann man dann in C einfach schreiben:

  putchar ('\n');

Der TTY-Treiber hat daraus dann CR+NL gemacht :-)

Man sieht: Diese ganze Verwirrung war damals durch UNIX (woher die 
Sprache C stammt) schon vorprogrammiert. Der Input eines Terminals wurde 
von CR nach NL gemappt, der Output von NL nach CR + NL. Und das ganz nur 
aus einem einzigen Grunde: Kompatibilität mit I/O-Redirection 
(Umlenkung) auf Dateien, wo IMMER nur ein einzelnes NL als Zeilentrenner 
gespeichert wurde.

Die Entwickler von VMS und MSDOS haben diesen Hickhack nicht mitgemacht, 
sie haben sich einfach für CR + NL als Zeilentrenner in Dateien 
entschieden. Das wiederum erforderte Anpassungen des C-Runtime-Systems 
an diese beiden Betriebssysteme: bei der Ausgabe wird das '\n' aus C in 
CR + NL gewandelt und ein CR+NL aus einer Datei in ein einfaches NL. Das 
macht hier aber nicht der TTY-Treiber, sondern hier die 
C-Runtime-Bibliothek.

Dies wiederum führte zu einem weiteren Dilemma: Wenn die 
C-Runtime-Bibliothek ein CR+NL einfach stillschweigend in ein einfaches 
NL mappt, dann kann man keine Dateien mehr "raw" lesen und schreiben, 
also nicht mehr 1:1. Aus diesem Grunde wurde dann für fopen() das b-Flag 
eingeführt, also z.B.:

   fp = fopen (filename, "rb");      // read binary

Unter Unix/Linux hat dieses B-Flag keine Bedeutung und wird einfach 
stillschweigend ignoriert, denn hier liest man ein CR aus einer Datei 
immer als '\r' und ein NL als '\n'.

Ist schon lustig, was man alles anstellen muss, um diese damaligen 
Design-Entscheidungen heute noch korrekt plattformunabhängig in den 
Griff zu bekommen...

Gruß,

Frank

von Karl H. (kbuchegg)


Lesenswert?

> Diese Kombination CR + NL ('\r' gefolgt von einem '\n') haben
> wir dem VAX/VMS-Betriebssystem vom damaligen Hersteller DEC
> zu verdanken. Hier wurde als Zeilentrenner die Kombination aus
> beiden Zeichen gewählt.
> Microsoft hat das damals für MSDOS dann auch so übernommen.
> Dies liegt wohl an den damaligen Druckern, die immer ein CR
> für den Wagenrücklauf und dann ein NL für den Zeilenvorschub
> erwarten. Auch die damaligen Teletypes (und später auch Terminals)
> erwarteten beides!

Und um da wieder weiter auszuholen :-), weil das jetzt so klingt als ob 
das ja völlig bescheuert wäre.

Das war es eben nicht. Wir sind heute gewohnt, dass Drucker alles können 
inkl Fettschrift. Aber vor nicht allzulanger Zeit war es gar nicht so 
unüblich, Fettdruck dadurch zu erreichen, dass man einen CR an den 
Drucker schickte (also den Wagen nach links) und dann die Zeile noch 
einmal ausgab (ev nur in Teilen). Das Übereinanderdrucken von Zeichen 
sorgte dann für Fettdruck oder auch zur Zusammensetzung von speziellen 
Zeichen aus Einzelzeichen.

(Und druckte man ein paar mal eine Zeile mit lauter -, dann erhielt man 
auf Ketten- oder Nadeldruckern eine prima Perforation :-)

Dazu musste man dem Drucker aber CR und NL getrennt befehlen können.

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.