Forum: Mikrocontroller und Digitale Elektronik Wort über das Terminal an dem uC senden


von Jakob (Gast)


Lesenswert?

Hallo, ich hab ich jetzt mit dem UART versucht.
Ich möchte mit meinen uC über RS232 mitn PC übers Terminal Programm 
reden.
Text ausgeben und Zahlen einlesen funktioniert alles Einwand frei.
Nun möchte ich ein Wort auf dem Terminal eingeben und der uC soll es 
wieder geben zur Kontrolle. Dies funktioniert leider nicht. Ich blicke 
nicht ganz durch die gets und string Geschichte beim UART durch. ? Ich 
arbeite mit printf und scanf.

________________________________________________
Hier mal der Code aus der Main :
________________________________________________
1
int main(void)
2
{
3
    init_uart(103);      //RS232 Initialisierung 
4
    stdout = &uart_output;    //
5
    stdin = &uart_input;    //
6
          unsigned int input;          //16bit  
7
  
8
  
9
  char str[50]; //Für die Einlesung eines Wortes
10
  
11
  
12
  while(1){  
13
  puts("\033[2J");  // clear screen
14
  puts("\033[0;0H");  // set cursor to 0,0    
15
  
16
  printf("Geben sie jetzt eine Zahl ein = ");
17
  scanf("%u",&input);
18
  printf("\n\rDeine Eingabe war %u",input);
19
  _delay_ms(2000);
20
  printf("\n\rEin Sinnloses Wort eingeben = ");
21
  gets(str);  <----------------------------------------
22
  printf("\n\rDeine Eingabe war = %s",str);
23
  
24
  
25
  
26
  _delay_ms(5000);}}

________________________________________________
Und hier Meine UART Routinen :
________________________________________________
1
//const int UBRR_VAL = 103;    //UBRR_VAL für 16Mhz und 9600baud beim ATMega32      
2
3
4
/*---------------------------------------------------------------------------
5
init_uart(): USART Initialisierung 
6
Parameter: UBRR_VAL                       
7
---------------------------------------------------------------------------*/
8
9
//für korrekten UBRR_VAL siehe Datenblatt des jeweiligen Microcontrollers
10
11
void init_uart (int UBRR_VAL)
12
{
13
  UCSRB |= (1<<TXEN) | (1<<RXEN);    // UART TX/RX einschalten
14
  UCSRC |= (1<<URSEL)|(3<<UCSZ0);    // Asynchron, 8Bit Framesize, Paritymode disabled, 1 Stopbit (8N1) 
15
16
  UBRRH = UBRR_VAL >> 8;
17
  UBRRL = UBRR_VAL & 0xFF; 
18
}
19
20
21
/*---------------------------------------------------------------------------
22
uart_putchar(): Senden einzelner Zeichen 
23
Parameter: c = Zeichen                       
24
---------------------------------------------------------------------------*/
25
void uart_putchar (unsigned char c)
26
{
27
  while (!(UCSRA & (1<<UDRE)))      // warten bis Senden moeglich
28
    {
29
    }
30
    UDR = c;
31
}
32
33
/*---------------------------------------------------------------------------
34
uart_getchar(): Empfangen einzelner Zeichen                       
35
---------------------------------------------------------------------------*/
36
unsigned char uart_getchar (void)
37
{
38
    while (!(UCSRA & (1<<RXC)))  // warten bis Zeichen verfuegbar
39
        {
40
    }
41
    return UDR;            // Zeichen aus UDR an Aufrufer zurueckgeben
42
}
43
44
/*---------------------------------------------------------------------------
45
uart_puts(): Senden einer Zeichenkette
46
Parameter: *s = Zeichenkette
47
---------------------------------------------------------------------------*/
48
void uart_puts (char *s)
49
{
50
  while (*s)          // so lange *s != '\0' also ungleich dem "String-Endezeichen"
51
  {
52
    uart_putchar(*s);
53
    s++;
54
  }
55
}

________________________________________________
Zusätzlich für die Verwendung von printf etc :
________________________________________________
1
FILE uart_output = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
2
FILE uart_input = FDEV_SETUP_STREAM(NULL, uart_getchar, _FDEV_SETUP_READ);

________________________________________________
Auf dem Terminal sieht es so aus :

Geben sie jetzt eine Zahl ein = 888
Deine Eingabe war 888
Ein Sinnloses Wort eingeben = jkjk

Der Courser liegt nach der Eingabe auf dem E von Eingabe.

von Karl H. (kbuchegg)


Lesenswert?

Der springende Punkt ist, dass du hier
1
  ... 888
nicht nur die Tasten 8, 8 und 8 gedrückt hast, sondern auch noch auf 
Return drüclken musstest.

EIn
1
  scanf("%u",&input);
holt sich zwar die 3 8-er, aber es belässt den Return im Eingabestrom. 
Die nächste Eingabeoperation muss sich darum kümmern. Genauer gesagt: 
Wenn ein scanf eine Zahl einlesen soll, dann hört er beim ersten Zeichen 
auf, das nicht zur Zahl gehört. Das kann irgendein Zeichen sein, zum 
Beispiel ein Leerzeichen, ein 'a' oder aber auch ein Return. Da dieses 
Zeichen nicht zur Zahl gehört kann es aber dieser scanf hier nicht 
einfach weglesen, sondern muss es im Eingabestrom belassen.

Ein
1
gets(str);
hingegen ist so definiert, dass er alle Zeichen einliest, bis er auf 
einen Return stösst.

Tja. Aber genau den Fall hast du. Von der Eingabe der 888 ist da noch 
ein Return im Eingabestrom. Und genau den sieht das gets als 
allererstes, holt ihn sich und erklärt damit die Eingabe des Strings für 
beendet.

Gewöhn dich daran, dass direkte Eingaben per printf und scanf auf einem 
µC eist keine so gute Idee sind. Das scanf, so wie du das betreibst, 
sowieso schon nicht. Denn auf einem µC ist der Normalfall der, dass der 
Prozessor nicht Däumchen drehen darf, nur weil er auf eine Eingabe vom 
Benutzer wartet. D.h. hier ist man im Endeffekt mit anderen Methoden 
viel besser bedient.

von Jakob (Gast)


Lesenswert?

Hallo, ich hab mal die Routine genommen die ich im TuT gefunden habe 
habe die main jetzt folgendermaßen geschrieben, der Effekt ist der 
selbe. Ich gebe das Wort ein drücke auf Enter und er springt zum Anfang 
des Satzes, zum E von Ein.

1
int main(void)
2
{
3
  
4
  
5
6
    init_uart(103);        //RS232 Initialisierung 
7
    stdout = &uart_output;    //
8
    stdin = &uart_input;    //
9
      unsigned int input;      //16bit    
10
    char Line[40];        // String mit maximal 39 zeichen
11
  
12
  
13
  while(1){  
14
  puts("\033[2J");  // clear screen
15
  puts("\033[0;0H");  // set cursor to 0,0
16
17
  uart_puts("\n\rEin Sinnloses Wort eingeben = ");  
18
  uart_gets( Line, sizeof( Line ) );
19
  uart_puts( "\r\n" );
20
  uart_puts( Line );  
21
  
22
  _delay_ms(5000);
23
  
24
  
25
  }//while(1)
_____________________
Routine =
_____________________
1
void uart_gets( char* Buffer, uint8_t MaxLen )
2
{
3
  uint8_t NextChar;
4
  uint8_t StringLen = 0;
5
  
6
  NextChar = uart_getchar();         // Warte auf und empfange das nächste Zeichen
7
  
8
  // Sammle solange Zeichen, bis:
9
  // * entweder das String Ende Zeichen kam
10
  // * oder das aufnehmende Array voll ist
11
  while( NextChar != '\n' && StringLen < MaxLen - 1 ) {
12
    *Buffer++ = NextChar;
13
    StringLen++;
14
    NextChar = uart_getchar();
15
  }
16
  
17
  // Noch ein '\0' anhängen um einen Standard
18
  // C-String daraus zu machen
19
  *Buffer = '\0';
20
}

von Jakob (Gast)


Lesenswert?

Jakob schrieb:
> uart_puts( "\r\n" );
> uart_puts( Line );


Dies wird ja dann so zu sagen gar nicht bearbeitet sondern bleibt im 
uart_gets stecken ?

von Karl H. (kbuchegg)


Lesenswert?

Jakob schrieb:

Mach dir hier mal eine zusätzliche Echo-Ausgabe rein

>
1
> void uart_gets( char* Buffer, uint8_t MaxLen )
2
> {
3
>   uint8_t NextChar;
4
>   uint8_t StringLen = 0;
5
> 
6
>   NextChar = uart_getchar();         // Warte auf und empfange das 
7
> nächste Zeichen
8
>
1
    uart_putchar( '*' );
2
    uart_putchar( NextChar );
3
    uart_putchar( '*' );

>
1
>  // Sammle solange Zeichen, bis:
2
>  // * entweder das String Ende Zeichen kam
3
>  // * oder das aufnehmende Array voll ist
4
>  while( NextChar != '\n' && StringLen < MaxLen - 1 ) {
5
>    *Buffer++ = NextChar;
6
>    StringLen++;
7
>    NextChar = uart_getchar();
8
>
1
     uart_putchar( '*' );
2
     uart_putchar( NextChar );
3
     uart_putchar( '*' );

>
1
>  }....
2
>
Dann fällt es dir leichter nachzuvollziehen, welche Zeichen (vor allen 
Dingen Steuerzeichen) in welcher Reihenfolge eintrudeln.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Jakob schrieb:
> Jakob schrieb:
>> uart_puts( "\r\n" );
>> uart_puts( Line );
>
>
> Dies wird ja dann so zu sagen gar nicht bearbeitet sondern bleibt im
> uart_gets stecken ?

Das kannst du ganz leicht rausfinden :-)
1
  uart_puts( "\r\n Ich hab verstanden: \"" );
2
  uart_puts( Line );
3
  uart_puts( "\"" );

Und schon hast du einen unzweideutigen Fixtext an der Ausgabe, der 
eindeutig belegt, ob es zu den puts überhaupt kommt.


Gewöhn dich an die Idee, dass du über die UART dir auch dir bekannte 
Fixtexte ausgeben kannst, die dir in der Entwicklungsphase des Programms 
helfen nachzuvollziehen, wie die Ablöufe im Programm sind. Es ist nicht 
verboten sich derartige Hilfen zunächst ins Programm einzubauen, die 
dann nach Ende der Entwicklung, bzw. wenn man ein Problem verstanden und 
behoben hat, wieder entfernt.

genau aus dem Grund ist es auch eine gute Idee, sich zb Texte mit 
Sonderzeichen einzurahmen, weil man dann auch Sonderzeichen, wie zum 
Beispiel Zeilenvorschübe im ausgegegbenen Text 'sehen' kann, indem man 
analysiert, ob diese Sonderzeichen dort in der Ausgabe auftauchen, wo 
sie auftauchen sollten. in einem
1
"Test.txt "
ist nun mal am Ende des eigentlichen Textes ein Leerzeichen, das man 
sonst nicht sehen würde. In
1
"Test.txt
2
"
hat sich ganz offensichtlich ein Carriage Return / Line Feed in den Text 
eingeschmuggelt. In
1
"
2
Test.txt"
steht der Carriage Return / Line Feed offenbar am Anfang des Textes.
Allen Fällen gemeinsam ist, dass man ohne die zusätzlichen 
Sonderzeichen, diese Spezialitäten nicht oder nur sehr schwer sehen 
würde.

: Bearbeitet durch User
von Jakob (Gast)


Lesenswert?

Hab es...das Programm hat so funktioniert musste aber die 40 Zeichen 
Eingeben und bei Enter hat er nur Unsinn gemacht, aber hab mir die 
Routine nochmal genauer angeschaut und hab gemeckert das ich statt denn 
\n ein \r schreiben muss damit er mit Enter beendet :S

1
while( NextChar != ---->  '\r' <----- && StringLen < MaxLen - 1 ) {
2
    *Buffer++ = NextChar;
3
    StringLen++;
4
    NextChar = uart_getchar();

Ist der Aufbau so besser, als mit Printf etc ?
1
  puts("\033[2J");  // clear screen
2
  puts("\033[0;0H");  // set cursor to 0,0
3
4
  uart_puts("\n\rEin Sinnloses Wort eingeben = ");  
5
  uart_gets( Line, sizeof( Line ) );
6
  uart_puts( "\r\nDeine Eingabe = " );
7
  uart_puts( Line );
8
  _delay_ms(10000);

von Jakob (Gast)


Lesenswert?

Ich danke dir für die Tipp's =)

von Karl H. (kbuchegg)


Lesenswert?

Jakob schrieb:
> Hab es...das Programm hat so funktioniert musste aber die 40 Zeichen
> Eingeben und bei Enter hat er nur Unsinn gemacht, aber hab mir die
> Routine nochmal genauer angeschaut und hab gemeckert das ich statt denn
> \n ein \r schreiben muss damit er mit Enter beendet :S

Ja, das kann sein.
Das hängt von der Einstellung deines Terminalprogramms ab, mit dem du 
arbeitest. Da gibt es alle Möglichkeiten, auch Kombinationen davon.

Und ja. das ist ein konstantes Ärgerniss.


> Ist der Aufbau so besser, als mit Printf etc ?

printf geht noch. Aber mit scanf wirst du nicht wirklich glücklich 
werden. Aus dem schon genannten Grund, dass in einem normalen 
µC-Programm die Heizungssteuerung ja nicht stehen bleiben darf, nur weil 
du eine Eingabe angefangen hast und zwischendurch mal aufs Klo musstest.

µC-Programmierung funktioniert am besten, wenn man diese sequentielle 
Vorgehensweise "erst mach dies, dann warte auf das" über Bord wirft und 
nach dem Muster vorgeht: Welches Ereignis ist eingetreten, wie muss ich 
darauf reagieren.
1
  lineCnt = 0;
2
3
  while( 1 ) {
4
5
    if( uart_char_available() ) {     // Ist ein Zeichen an der UART eingetroffen?
6
      nextChar = uart_getc();         // hole das Zeichen.
7
                                      // was soll damit geschehen?
8
9
      if( nextChar != '\r' )          // noch nicht das Ende einer Eingabezeile
10
        nextLine[lineCnt++] = nextChar;  // Zeichen zwischenspeichern
11
12
      else {                          // die Zeile ist vollständig
13
        nextLine[lineCnt] = '\0';
14
        lineCnt = 0;
15
16
                                      // auswerten
17
        if( strcmp( nextLine, "ein" ) == 0 )
18
          PORTB &= ~( 1 << PB0 );    // beispielsweise LED einschalten
19
20
        else if( strcmp( nextLine, "aus" ) == 0 )
21
          PORTB |= ( 1 << PB0 );     // beispielsweise LED ausschalten
22
      }
23
    }
24
25
    // mach was ganz wichtiges, was regelmässig erfolgen muss
26
    // zb. Überwachen ob ein motorgesteuerter Schlitten seine Endposition
27
    // erreicht hat.
28
  }

auf die Art läuft der Teil 'mach was ganz wichtiges, ...' auch dann 
weiter, während du an deinem Terminal sitzt und tippst und dir während 
des Tippens einen Kaffee holst. Trudelt das nächste getippte Zeichen 
beim AVR ein, dann wird es geholt, entschieden was damit passieren soll 
(in den meisten Fällen wird es einfach an die bisher empfangenen Zeichen 
hinten drann gehängt) und nur dann, aber auch wirklich nur dann, wenn du 
auf Return drückst, startet die Auswertung der übertragenen Zeile.

Auf etwas zu warten ist in der µC-Programmierung in 95% der Fälle eine 
ganz schlecht Design-Entscheidung.

Und genau deswegen ist scanf (nicht aber sscanf!) meist nicht wirklich 
brauchbar. Genausowenig wie ein _delay_ms ausser in ganz wenigen 
Ausnahmefällen etwas verloren hätte. Der Schlüssel zu einem Programm, 
das scheinbar mehrere Aufgaben gleichzeitig erledigen kann, besteht 
darin, dass auf nichts und niemanden gewartet wird. Ausser vielleicht 
auf die Antwort von einem DS1820 Temperatursensor. Aber da reden wir 
nicht von Millisekunden sondern von Mykrosekunden im Übertragungstakt 
:-)

: Bearbeitet durch User
von Jakob (Gast)


Lesenswert?

Ich versuche es umzusetzen =)
War erst mal nur die Vorbereitung auf ein Größeres Projekt

von Jörg E. (jackfritt)


Lesenswert?

Und auch bei Sensoren kann das Signal gestört sein und dein Mc wartet 
auf eine Flanke etc. Da ist es dann schön wenn auch dort eine Art 
timeout Variable vorhanden ist. Das ist mir bei wilden Steckbrett 
Aufbauten schon öfter passiert.

Und wieder Vielen Dank an Karl Heinz für die tollen Ausführungen.

von Stefan F. (Gast)


Lesenswert?

Ich habe mir angewöhnt, beim Empfang sowohl \r als auch \n zu 
akzeptieren. Außerdem noch die Sonderlocke dass ein \n ignoriert wird, 
wenn das vorherige Zeichen ein \r war (DOS Programme und daran 
angelehnte beenden Zeilen häufig mit \r\n).

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.