Forum: Mikrocontroller und Digitale Elektronik STM32F1: Wie lese ich chars zeilenweise über UART?


von Vincent (Gast)


Lesenswert?

Hallo,

ich möchte Strings "hello123\n" bis zum Zeilenumbruch lesen.

Wie stelle ich das an?

Einzel char lese ich wie folgt:
1
uint16_t receive(USART_TypeDef* USARTx) {
2
  if (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) != RESET) {
3
    x = USART_ReceiveData(USARTx);
4
  }
5
  return x;
6
}

Vielen Dank im Voraus.

von Gerd E. (robberknight)


Lesenswert?

1. Du machst Dir Gedanken wie lang eine Zeile maximal sein darf. Z.B. 
100 Zeichen.

2. Du allozierst für diese maximale Zeile Speicher

3. Du empfängst solange Zeichen und packst sie in Deinen Puffer, bis 
entweder die maximale Zeilenlänge (siehe oben) erreicht ist, oder eben 
ein Zeilenende empfangen wurde

von Daniel B. (dbuergin)


Lesenswert?


von Adam P. (adamap)


Lesenswert?

Du kannst einfach auf das '\n' NewLine prüfen oder auf CarriageReturn, 
wenn du es über Tastatur eingibst und ENTER drücken möchtest.
1
#define NL    0x10
2
#define CR    0x0d
3
4
...
5
6
ch = (uint8_t)receive(USART1);
7
8
if((ch == NL) || (ch == CR))
9
{
10
  // Zeileneingabe beendet
11
}
12
else
13
{
14
  // mach etwas mit dem empfangenen Zeichen
15
}

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Adam P. schrieb:
> #define NL    0x10
> #define CR    0x0d

Das
1
#define NL   '\n'
2
#define CR   '\r'

halte ich für lesbarer und bedeutet dasselbe.

> if((ch == NL) || (ch == CR))
> {
>   // Zeileneingabe beendet
> }
> else
> {
>   // mach etwas mit dem empfangenen Zeichen
> }

Kann man nicht unbedingt allgemein so formulieren. Mit Deiner Methode 
bekommt man bei einer Zeile, die mit CR+NL beendet wird, immer noch eine 
Leerzeile. Wenn gewährleistet ist, dass die Zeile tatsächlich mit NL 
beendet wird, dann wäre besser:
1
if (ch == NL)
2
{
3
  // Zeileneingabe beendet
4
}
5
else if (ch != CR)  // CR ignorieren
6
{
7
  // mach etwas mit dem empfangenen Zeichen
8
}

Wenn jedoch eine Tasteneingabe über eine Terminal-Emulation wie z.B. 
PuTTY zustandekommt, wäre es besser, folgendes zu schreiben:
1
if (ch == CR) // CR = ENTER
2
{
3
  // Zeileneingabe beendet
4
}
5
else if (ch != NL)  // NL ignorieren
6
{
7
  // mach etwas mit dem empfangenen Zeichen
8
}

Denn Terminal-Emulationen senden standardmäßig lediglich ein CR und kein 
NL, wenn man die Enter-Taste drückt - außer man stellt es in der 
Terminal-Emulation um.

: Bearbeitet durch Moderator
von Vincent (Gast)


Lesenswert?

Vielen Dank!!

von Stefan F. (Gast)


Lesenswert?

Ich mache das meistens so:

Eine Interruptroutine sammelt alle empfangnen Zeichen in einem 
Ringpuffer. Sie ersetzt dabei jede empfangene CR durch LF und ignoriert 
jede empfange LF wenn direkt davor ein CR empfangen wurde.

So erreiche ich, daß das Programm mit allen Varianten von 
Zeilenumbrüchen gleich klar kommt: CR, CR-LF, und LF.

Mein Programm checkt regelmäßig, ob im Puffer ein NL steht. Wenn ja, 
dann holt es alle Zeichen bis einschließlich dem NL heraus und 
verarbeitet die Zeile.

Hier ein Auszug aus meiner ISR:
1
        // If received a line feed and the previous character was a 
2
        // carriage-return (or vice versa) then ignore the new character
3
        if ((c=='\n' && prevChar=='\r') || (c=='\r' && prevChar=='\n')) {
4
            return;
5
        }
6
        prevChar=c;
7
8
        // If received a carriage-return, then replace it by a line-feed
9
        if (c=='\r') {
10
            c='\n';
11
        }
12
13
        // Put c into the ringbuffer
14
        ...

von S. R. (svenska)


Lesenswert?

Stefan U. schrieb:
> So erreiche ich, daß das Programm mit allen Varianten von
> Zeilenumbrüchen gleich klar kommt: CR, CR-LF, und LF.

Und keine Binärdaten mehr übertragen kann. Das ist der Nachteil dabei, 
daher überlasse ich diese Auswertung einer weiter oben stehenden 
Schicht.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Frank M. schrieb:
>> #define NL    0x10
>> #define CR    0x0d
>
> Das#define NL   '\n'
> #define CR   '\r'
>
> halte ich für lesbarer und bedeutet dasselbe.

Nö.  '\n' ist nämlich das Zeichen 10, oder 0x0a.

Damit wäre '\n' wenigstens korrekt, während 0x10 nicht klappt. ;-)

von DraconiX (Gast)


Lesenswert?

Jörg W. schrieb:
> Frank M. schrieb:
>>> #define NL    0x10
>>> #define CR    0x0d
>>
>> Das#define NL   '\n'
>> #define CR   '\r'
>>
>> halte ich für lesbarer und bedeutet dasselbe.
>
> Nö.  '\n' ist nämlich das Zeichen 10, oder 0x0a.
>
> Damit wäre '\n' wenigstens korrekt, während 0x10 nicht klappt. ;-)

Das alles geht im übrigen so lange gut, wie man keine Hex Values 
schickt. ;-)

Da bin ich nämlich schon des öfteren drauf reingefallen... wenn man z.b. 
eine Variable, dessen Inhalt eben 0x0a ist, per UART rausschickt. 
Deshalb habe ich einen Grundsatz für mich gefasst: Sobald Variablen 
geschickt werden - immer ein String mit fester länger verwenden. ;-) 
Selbst eine Kombo von 0x0d und 0x0a hatte mich schon mal Stunden des 
Suchens gekostet. :'D Traurig, aber ist so.

von Beziehungsweise (Gast)


Lesenswert?

>durch, respektive den ganzen Artikel.
Warum benutzen eigentlich nur Informatiker das Wort "respektive"?
In 97,4% aller Fällen ist das so.

von Mike R. (thesealion)


Lesenswert?

DraconiX schrieb:
> Das alles geht im übrigen so lange gut, wie man keine Hex Values
> schickt. ;-)
>
> Da bin ich nämlich schon des öfteren drauf reingefallen... wenn man z.b.
> eine Variable, dessen Inhalt eben 0x0a ist, per UART rausschickt.
> Deshalb habe ich einen Grundsatz für mich gefasst: Sobald Variablen
> geschickt werden - immer ein String mit fester länger verwenden. ;-)
> Selbst eine Kombo von 0x0d und 0x0a hatte mich schon mal Stunden des
> Suchens gekostet. :'D Traurig, aber ist so.

Das hat aber nicht direkt etwas mit Hex Values zu tun.
Das Problem ensteht, wenn man seine übertragenen Werte nicht als ASCII 
codiert sondern versucht direkt Byte Werte zu übertragen.
Bei so etwas sollte man sich überlegen, ob man nicht die Steuerzeichen 
aus dem ASCII Satz verwendet. Also:

0x02 als Start einer Nachricht
0x03 als Ende der Nachricht
0x10 als Escape Zeichen, wenn ein Steuerzeichen in der Nachricht 
auftritt

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Stefan U. schrieb:
> Mein Programm checkt regelmäßig, ob im Puffer ein NL steht. Wenn ja,
> dann holt es alle Zeichen bis einschließlich dem NL heraus und
> verarbeitet die Zeile.

Alternative Strategie wäre, in der ISR eine Flagge zu setzen, wenn eine 
der Zeilenendenvarianten erreicht ist. Das Hauptprogramm checkt die 
Flagge und zündet, wenn gesetzt, den Input Parser. Nach erfolgtem 
Parsing wird die Flagge gelöscht.

: Bearbeitet durch User
von Adam P. (adamap)


Lesenswert?

Frank M. schrieb:
> Kann man nicht unbedingt allgemein so formulieren.

Natürlich hast du recht, dies sollte Ihm nur verdeutlichen, wie er ein 
Zeilenende (Umbruch) ansich erkennen kann. (Dies war nämlich seine 
Frage)

Ansich gehört da noch viel mehr zu, wollte Ihm nur nicht meinen 
Hundertzeilen langen Code posten.
Erkennen von:
- Backspace
- evtl. links/rechts Pfeiltasten
- Steuerbefehle mittels STRG + Taste
- usw.

Kann beliebig viel werden, aber hast schon recht. Ausser er legt es per 
Protokollabsprache fest, dass immer ein \n das Ende markiert.

von Adam P. (adamap)


Lesenswert?

Mike R. schrieb:
> Das Problem ensteht, wenn man seine übertragenen Werte nicht als ASCII
> codiert sondern versucht direkt Byte Werte zu übertragen.

Dieses Problem ensteht jedoch auch nur, wenn du Rohdaten als Byte-Stream 
sendest sie aber als ASCII interpretierst.

Da kann man sich lieber auf einen definierten Paketaufbau einigen, z.B.:

---------------------------------
| CRC16 | valid_data_cnt | data |
---------------------------------

ASCII Werte die über das Terminal gesendet werden, sind auch nur Bytes,
ob nun Dezimal oder Hex - es liegt allein in der Interpretation der 
Daten.

von Adam P. (adamap)


Lesenswert?

Jörg W. schrieb:
> Nö.  '\n' ist nämlich das Zeichen 10, oder 0x0a.

:-D ...hast ja recht - da hab ich wohl an Dec. gedacht und in Hex 
geschrieben.
Sorry.

von S. R. (svenska)


Lesenswert?

Adam P. schrieb:
> Dieses Problem ensteht jedoch auch nur, wenn du Rohdaten als Byte-Stream
> sendest sie aber als ASCII interpretierst.

Nein. Wenn ich auf der untersten Ebene (also im UART-Treiber) schon 
bestimmte Bytes verarbeite, dann kann ich diese nicht mehr als Nutzdaten 
übertragen.

Das hat nichts mit ASCII zu tun. Entweder, ein Übertragungskanal ist 
transparent (dann kann ich beliebige Daten übertragen), oder er ist es 
nicht (dann bin ich eingeschränkt in dem, was ich übertragen kann).

Dein Beispiel mit "CRC, Länge, Daten" funktioniert mit den meisten hier 
genannten Codebeispielen nicht.

von Jack (Gast)


Lesenswert?

S. R. schrieb:
> Adam P. schrieb:
>> Dieses Problem ensteht jedoch auch nur, wenn du Rohdaten als Byte-Stream
>> sendest sie aber als ASCII interpretierst.
>
> Nein. Wenn ich auf der untersten Ebene (also im UART-Treiber) schon
> bestimmte Bytes verarbeite, dann kann ich diese nicht mehr als Nutzdaten
> übertragen.

Natürlich kann man das. Man muss sich nur auf ein Format einigen, 
welches das entsprechend handhabt. Theoretisch ist das seit den Zeiten 
des OSI-Modells eigentlich komplett abgefrühstückt und praktisch beruht 
z.B. das ganze Internet-Zeugs darauf, das jeder Layer seinen Kram 
verarbeitet und die Nutzdaten kodiert/dekodiert wie es gebraucht wird.

Eines der simpelsten Layer 2-Protokolle für serielle Schnittstellen ist 
SLIP, RFC1055 von 1988 - Steinzeit. Das eignet sich auch problemlos für 
die Übertragung von Datensätzen auf seriellen Schnittstellen wenn man 
kein IP oben drüber hat.

Hier der Code aus dem RFC:
1
   /* SLIP special character codes
2
    */
3
   #define END             0300    /* indicates end of packet */
4
   #define ESC             0333    /* indicates byte stuffing */
5
   #define ESC_END         0334    /* ESC ESC_END means END data byte */
6
   #define ESC_ESC         0335    /* ESC ESC_ESC means ESC data byte */
7
8
   /* SEND_PACKET: sends a packet of length "len", starting at
9
    * location "p".
10
    */
11
   void send_packet(p, len)
12
           char *p;
13
           int len; {
14
15
     /* send an initial END character to flush out any data that may
16
      * have accumulated in the receiver due to line noise
17
      */
18
        send_char(END);
19
20
     /* for each byte in the packet, send the appropriate character
21
      * sequence
22
      */
23
           while(len--) {
24
                   switch(*p) {
25
                   /* if it's the same code as an END character, we send a
26
                    * special two character code so as not to make the
27
                    * receiver think we sent an END
28
                    */
29
                   case END:
30
                           send_char(ESC);
31
                           send_char(ESC_END);
32
                           break;
33
34
                   /* if it's the same code as an ESC character,
35
                    * we send a special two character code so as not
36
                    * to make the receiver think we sent an ESC
37
                    */
38
                   case ESC:
39
                           send_char(ESC);
40
                           send_char(ESC_ESC);
41
                           break;
42
                   /* otherwise, we just send the character
43
                    */
44
                   default:
45
                           send_char(*p);
46
                           }
47
48
                   p++;
49
                   }
50
51
           /* tell the receiver that we're done sending the packet
52
            */
53
           send_char(END);
54
           }
Und hier die Empfangsroutine aus dem RFC:
1
   /* RECV_PACKET: receives a packet into the buffer located at "p".
2
    *      If more than len bytes are received, the packet will
3
    *      be truncated.
4
    *      Returns the number of bytes stored in the buffer.
5
    */
6
   int recv_packet(p, len)
7
           char *p;
8
           int len; {
9
           char c;
10
           int received = 0;
11
12
           /* sit in a loop reading bytes until we put together
13
            * a whole packet.
14
            * Make sure not to copy them into the packet if we
15
            * run out of room.
16
            */
17
           while(1) {
18
                   /* get a character to process
19
                    */
20
                   c = recv_char();
21
22
                   /* handle bytestuffing if necessary
23
                    */
24
                   switch(c) {
25
26
                   /* if it's an END character then we're done with
27
                    * the packet
28
                    */
29
                   case END:
30
                           /* a minor optimization: if there is no
31
                            * data in the packet, ignore it. This is
32
                            * meant to avoid bothering IP with all
33
                            * the empty packets generated by the
34
                            * duplicate END characters which are in
35
                            * turn sent to try to detect line noise.
36
                            */
37
                           if(received)
38
                                   return received;
39
                           else
40
                                   break;
41
42
                   /* if it's the same code as an ESC character, wait
43
                    * and get another character and then figure out
44
                    * what to store in the packet based on that.
45
                    */
46
                   case ESC:
47
                           c = recv_char();
48
49
                           /* if "c" is not one of these two, then we
50
                            * have a protocol violation.  The best bet
51
                            * seems to be to leave the byte alone and
52
                            * just stuff it into the packet
53
                            */
54
                           switch(c) {
55
                           case ESC_END:
56
                                   c = END;
57
                                   break;
58
                           case ESC_ESC:
59
                                   c = ESC;
60
                                   break;
61
                                   }
62
63
                   /* here we fall into the default handler and let
64
                    * it store the character for us
65
                    */
66
                   default:
67
                           if(received < len)
68
                                   p[received++] = c;
69
                           }
70
                   }
71
           }

Das ist so simpel:

0xC0 im Datenstrom markiert das Ende eines Datensatzes. Eine 
Markierung für den Anfang hat man sich gespart. Wenn was kommt geht es 
los. Wer tricksen will kann den Anfang auch mit 0xC0 markieren, das gibt 
dann zwischen den Datensätzen leere Datensätze.


0xDB im Datenstrom ist das Escape-Zeichen. Wofür braucht man das? Um 
0xC0 in den Daten kodieren zu können: Eine Sequenz von

0xDB, 0xDC kodiert ein 0xC0 im Nutzer-Datenssatz (die Escape-Sequenz 
für 0xC0 wurde bewusst nicht als 0xDB 0xC0 definiert).

Jetzt muss man nur noch eins machen, das Escape-Zeichen escapen:

0xDB, 0xDD kodiert ein 0xDB im Nutzer-Datensatz (auch hier wurde 
bewusst nicht 0xDB 0xDB als Kodierung für 0xDB verwendet).

0xDC und 0xDD im Datensatz, ohne führendes 0xDB sind unkritisch.

Beitrag #5080396 wurde von einem Moderator gelöscht.
von Darth Moan (Gast)


Lesenswert?

Jack schrieb:
>> Nein. Wenn ich auf der untersten Ebene (also im UART-Treiber) schon
>> bestimmte Bytes verarbeite, dann kann ich diese nicht mehr als Nutzdaten
>> übertragen.
>
> Natürlich kann man das. Man muss sich nur auf ein Format einigen,
> welches das entsprechend handhabt. Theoretisch ist das seit den Zeiten
> des OSI-Modells eigentlich komplett abgefrühstückt und praktisch beruht

Moin,

ich moechte an dieser Stelle nur nochmal darauf hinweisen, dass auch
das OSI Modell und die zitierte RFC die Meinung stuetzt, dass ein
Transportprotokoll eben nicht in den UART Treiber gehoert. Daher ist es
ja ein Layer 2 Protokoll. Der gepostete Code verwendet denn auch ein
recv_char(). Dahinter verbirgt sich irgendwann, mal der UART Treiber,
falls dieses Protokoll ueber UART angewendet werden soll.

Wenn man aber schon Zeichenfilter und Ersetzungen fest in den UART
Treiber einbaut, kann man darueber keine anderen Protokolle fahren,
die eben diese Zeichen benoetigen. Es ging bei dem Einwand genau darum,
dass CR/LF ggf. schon im UART Treiber ausgefiltert bzw. Ersetzt wurden.
Das waere dann fuer Binaerdaten ungeeignet, da man nicht wissen kann,
ob das Ersetzungszeichen tatsaechlich im Datenstrom war oder eben die
anderen beiden.
Wenn man also zwischen ASCII Zeilenbasierte Kommandos und
Datenuebertragung (bin) hin und herschalten will, was nun nichts
ungewoehnliches war/ist. Dann muesste man dabei den Treiber wechseln.
Das macht man eher nicht, sondern nutzt je nach Mode andere L2 
Interfaces.
Oder aber man baut alle Modi und Protokolle in den UART Treiber ein.
Und wenn man dann das selbe Protokoll nicht ueber den echten UART,
sondern Virtuellen Com Port ueber USB schieben will, dann baut man alle
diese Protokolle und Modi nochmal schnell eben in den VCP Treiber ein.
Den man ja auch immer im Source Code vorliegen hat. Ich halte das fuer
keine gute Idee.
Wenn man wild L2 Protokolle fest schachtelt, erhoeht man drastisch die
Wahrscheinlichkeit, dass kein anderes Produkt mit "mir" reden mag/kann.
Obwohl man doch nur standard Protokolle verwendet. Das kann natuerlich
auch gewollt sein.
Muss also jeder selber entscheiden.

von Stefan F. (Gast)


Lesenswert?

>> So erreiche ich, daß das Programm mit allen Varianten von
>> Zeilenumbrüchen gleich klar kommt: CR, CR-LF, und LF.

> Und keine Binärdaten mehr übertragen kann.

Ja klar, das macht so nur bei textorientierter Kommunikation Sinn.

> Da bin ich nämlich schon des öfteren drauf reingefallen... wenn man z.b.
> eine Variable, dessen Inhalt eben 0x0a ist, per UART rausschickt.

Man muss sich schon für eins entscheiden. Entweder überträgst du 
Text-Zeilen, oder Binärdaten. Die musst du ann aber logischerweise 
anders "framen", zum Beispiel indem du Pakete mit Längen-Angabe 
erstellst.

Ich hatte früher viele Anwendungen gesehen, wo man das Ende eines 
Paketes durch eine zeitliche Pause dargestellt hat. Das ist aber sehr 
unzuverlässig, da PC Programme auch vom Betriebssystem mal pasiert 
werden können. Außerdem hat sich das Timing seit Einführung der USB-UART 
Adapter sehr verändert.

> Alternative Strategie wäre, in der ISR eine Flagge zu setzen

Finde ich sehr gut, dann muss nicht im Puffer suchen, um festzustellen, 
ob eine Zeile empfangen wurde. Ist bestimmt wesentlich effizienter.

> ich moechte an dieser Stelle nur nochmal darauf hinweisen, dass auch
> das OSI Modell und die zitierte RFC die Meinung stuetzt, dass ein
> Transportprotokoll eben nicht in den UART Treiber gehoert.

Mit OSI Schichten wurde schon so manches einfache Program kaputt 
designt, vor allem, was Performacne und Speicherbedarf angeht. Keine 
Frage, das ist ein klares Modell für saubere Strukturierung. Aber nicht 
in jedem Fall angemessen. Vor allem nicht auf Mikrocontrollern mit nur 
wenigen Bytes RAM.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Darth Moan schrieb:
> Dann muesste man dabei den Treiber wechseln.
> Das macht man eher nicht

Auf unixoiden Systemen war/ist das gang und gäbe: "raw" vs. "cooked"
mode im Terminaltreiber umschalten.

Man muss ja nicht unbedingt ein komplettes termios dafür implementieren,
aber es spricht grundsätzlich nichts weiter dagegen, dass man das auch
auf einem Controller ähnlich strukturiert.

: Bearbeitet durch Moderator
von Adam P. (adamap)


Lesenswert?

Darth Moan schrieb:
> dass ein
> Transportprotokoll eben nicht in den UART Treiber gehoert.

Vielen dank, so sehe ich das auch.
UART ist nur das Übertragungsmedium, Sie soll nur Daten senden/empfangen 
und nichts interpretieren.

von Jack (Gast)


Lesenswert?

Adam P. schrieb:
> Darth Moan schrieb:
>> dass ein
>> Transportprotokoll eben nicht in den UART Treiber gehoert.

Erstens, Transport ist Layer 4, nicht das gezeigte SLIP. Das ist Layer 
2. Also was soll die Aufregung?

>
> Vielen dank, so sehe ich das auch.
> UART ist nur das Übertragungsmedium, Sie soll nur Daten senden/empfangen
> und nichts interpretieren.

Zweitens wird die Aussage nicht durch die Realität gestützt. Wer will 
kann sich mal RS232 im Detail ansehen, zum Beispiel das Senden von 
Breaks, das generieren von Parity-Bits oder das so geliebte 
Software-Flowcontrol.

Dritten werden in der Diskussion die zur Ansteuerung des URAT benötigten 
drei Zeilen Assembler (variiert geringfügig je nach MCU) zum "Treiber" 
hochstilisiert und sich dann sinnlos an einem recv_char() Platzhalter in 
Beispielsourcecode aufzuhängen. "Treiber" brauchen Arduino-Bastler.

Viertens, wenn sich schon bei der bloßen Erwähnung des OSI-Modells 
reflexartig in die Hose macht wird und bei 30 bis 40 Zeilen simpelsten 
C-Code was von

>>> kaputt designt, vor allem, was Performacne und Speicherbedarf angeht....

geredet wird, dann ist das hier Zeitverschwendung.

Für die anderen, die ein bisschen Mitdenken wollen ein Tipp. Man ersetze 
im Code
1
#define END             0300
2
#define ESC             0333
3
#define ESC_END         0334
4
#define ESC_ESC         0335
einfach durch
1
#define END             '\n'
2
#define ESC             '\\'
3
#define ESC_END         (END & 0x80)
4
#define ESC_ESC         (ESC & 0x80)
oder gar, unter Verlust einiger Protokolleigenschaften und der 
Notwendigkeit ein weitere Zeile zu ändern, durch
1
#define END             '\n'
2
#define ESC             '\\'
3
#define ESC_END         '\n'
4
#define ESC_ESC         '\\'
und überlege sich dann was bei diesem "ganz schlimmen" Layer 2 Protocol 
herauskommt.

von Darth Moan (Gast)


Lesenswert?

Jack schrieb:
> Erstens, Transport ist Layer 4, nicht das gezeigte SLIP. Das ist Layer
> 2. Also was soll die Aufregung?
>

Hoppala, stimmt auffallend. Wie peinlich. Aendert aber nichts daran, 
dass
das "Protokoll" nicht in die unterste ISR des UART gehoert.

> Dritten werden in der Diskussion die zur Ansteuerung des URAT benötigten
> drei Zeilen Assembler (variiert geringfügig je nach MCU) zum "Treiber"
> hochstilisiert und sich dann sinnlos an einem recv_char() Platzhalter in
> Beispielsourcecode aufzuhängen. "Treiber" brauchen Arduino-Bastler.

Kannst du mir schnell mal die 3 Zeilen Assembler, die den VCP ueber USB
machen, zeigen? Ich nehme da recht fette (im Vergleich zu 3 Zeillen
Assembler) Code Bloecke vom µC Hersteller (obwohl, vielleicht war auch 
mal
3P SW dabei, kann ich jetzt nicht beschwoeren). 3 Zeilen Assembler 
waeren
da viel sparsamer. :-)
Das recv_char() ist eben nur ein Platzhalter, den man nach belieben
austauscht. Deshalb baut man das "Protokoll" eigentlich nicht in das
recv_char() hinein sondern macht es ausserhalb. Das war alles, was 
gesagt
wurde. Oh, nicht ganz! Es wurde ausserdem gesagt, das dann (ausserhalb)
beliebig viele dieser "Protokolle" "gleichtzeitig das selbe recv_char()
nutzen koennen, ohne die ISR dann zur Laufzeit aedern zu muessen bzw. 
die
Umschaltung aller dieser "Protokolle" in die ISR einbauen zu muessen.

Aber dieser gigantische Aufwand (das nicht gleich in die ISR zu haengen)
scheint manchen Menschen auch irgendwie angst zu machen. ;-)
Das es Rahmenbedingungen geben kann, die Jemanden dann doch dazu
bewegen, Dinge zu tun, die der reinen Lehre nach nicht sauber sind, hat
(glaube ich) niemend bestritten.
Nicht böse sein, wir haben offensichtlich unterschiedliche Meinungen, 
was
alles in die ISR gehoert und was nicht.

von W.S. (Gast)


Lesenswert?

Jörg W. schrieb:
> Auf unixoiden Systemen war/ist das gang und gäbe: "raw" vs. "cooked"
> mode im Terminaltreiber umschalten.

was ist ein Terminaltreiber?

Mal abgesehen davon, daß der TO schon am 13. 7. ausgestiegen ist, geht 
diese Diskussion in die falsche Richtung. Terminal-Zeugs gehört in's 
jeweilige Terminal, die schieren HW-Treiber gehören jedoch nicht dazu. 
Die sollen sich auch nicht um den Inhalt der gesendeten und empfangenen 
Zeichen kümmern, sondern nur darum, daß der jeweilige Datenfluß 
ungestört passiert. Da ist die ganze hitzige Diskussion um "recv_char()" 
überflüssig.

Wenn man (was ja durchaus erwünscht ist) seine höheren Programmschichten 
weitgehend HW-unabhängig schreiben will, dann braucht es nur ein ganz 
billiges Zweischichten-Modell:

unterste Schicht: die HW-Treiber für die seriellen Peripheriecores, 
einschließlich der virtuellen (USB-CDC). Die können und sollen alle das 
gleiche Interface nach 'oben' zeigen, also in so einem Sinn:
- uart0_getchar(), uart1_getchar(),.., uart99_getchar(), usb_getchar(), 
spi1_getchar().. usw.

darüberliegende Schicht: eine Durchreicher-Schicht, die einfach nur als 
Weiche dient: getchar(name) mit name= uart0,..99, usb, stdin und so.

Auf so ein Konstrukt kann man ein beliebiges Programm draufsetzen, 
entweder ne simple Kommandozeile oder eine Terminal-Emulation oder was 
auch immer. Sowas hat dann nur noch mit
1
 if (char_avail(uart0)) 
2
 { c = getchar(uart0);
3
   mache_irgendwas(c);
4
 }
zu tun und das sollte eigentlich jeder µC-Eleve formulieren können - und 
wenn nicht, dann entweder Nase ins Buch stecken oder sich ein anderes 
Hobby suchen.

W.S.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> was ist ein Terminaltreiber?

Das Zeug, was einen Fernschreiber (tty) bedient. ;-)

von W.S. (Gast)


Lesenswert?

ich hätte lieber ne ernstgemeinte Antwort.

Bei Linux und anderen ixe/uxen ist es nicht anders als bei Windows, wo 
serielle Ströme von den Anwendungen wie ne Datei geöffnet werden - und 
Terminalprogramme sind nix als Anwendungen.

Und das, was nen Fernschreiber bedient, ist ein Kabel mit nem Stecker 
dran - der Rest im Fernschreiber ist Elektromechanik. 
ratter..ratter..ratter

W.S.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> ich hätte lieber ne ernstgemeinte Antwort.

Naja, der Terminaltreiber im Unix hat eben in der Tat einerseits
zeilenweise gepuffert und Editiermöglichkeiten angeboten (back space,
ganze Zeile löschen etc.).  In diesem Modus schickt er die komplette
Zeile erst weiter, wenn ein CR oder LF auftritt.

Andererseits kann man ihn in den raw mode schalten, bei dem er einfach
Zeichen für Zeichen nach oben durchreicht.  Kein Echo, nichts.

> Und das, was nen Fernschreiber bedient, ist ein Kabel mit nem Stecker
> dran

/dev/tty* kommt namensmäßig aber eben genau daher.

von W.S. (Gast)


Lesenswert?

Jörg W. schrieb:
> Naja, der Terminaltreiber im Unix hat eben in der Tat einerseits
> zeilenweise gepuffert und Editiermöglichkeiten angeboten

Also das ist in meinem Sprachgebrauch "Die Konsole", also eine 
Anwendung, die man auch als Dienst laufen lassen kann und die in deinen 
Gefilden ne Shell als Tochter laufen läßt. Das ist dann alles vor den 
Toren des API des OS im Bereich der Apps.

Aber das hat wohl alles nix mit dem kläglichen Versuch des TO zu tun, 
einen UART auf seinem µC benutzen zu wollen. Und der hat sich schon am 
13.7. verabschiedet.

W.S.

von S. R. (svenska)


Lesenswert?

W.S. schrieb:
> Also das ist in meinem Sprachgebrauch "Die Konsole", also eine
> Anwendung, die man auch als Dienst laufen lassen kann und die in deinen
> Gefilden ne Shell als Tochter laufen läßt.

Dann ist dein Sprachgebrauch unpräzise.
Nicht jedes Terminal ist auch eine Konsole.

von W.S. (Gast)


Lesenswert?

S. R. schrieb:
> Nicht jedes Terminal ist auch eine Konsole.

Er hat auch nicht von Terminal geschrieben, sondern von terminal_TREIBER

W.S.

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.