Forum: Mikrocontroller und Digitale Elektronik UART RXC Bit wird nach empfangenem Zeichen nicht gelöscht


von Philipp (Gast)


Lesenswert?

Hallo zusammen,

zur Zeit versuche ich erfolglos, ein kleines C-Programm auf dem ATMEGA 
328P zum laufen zu bringen. Dieses soll in einer Schleife nacheinander 
die Werte von 5 Variablen über den UART ausgeben. Anschließend soll es 
in den "Empfangsmodus" gehen.
Hier soll zuerst der Name der Variable und anschließend deren Wert 
empfangen werden. Danach ist diese Funktion beendet, und es soll wieder 
von vorne begonnen werden (mit der Ausgabe der 5 Variablen).

Am Serial Monitor empfange ich plausible Zeichenfolgen. Wenn ich nun 
aber einen Variablenname sende (z.B. a), wartet das Programm nicht, bis 
ich nachfolgend den Variablenwert sende, sondern springt gleich wieder 
aus der Funktion heraus in die Sendeschleife.

Um ein Zeichen zu empfangen, warte ich in einer While-Schleife auf das 
Setzen RXC0 Bits. Scheinbar wird dieses jedoch nicht mehr gelöscht, 
nachdem ich das UDR0 Register das erste Mal auslese (dies muss ja 
gelesen werden, um in den richtigen "Switch-Case" Zweig zu springen). 
Dadurch entsteht wohl das unerwartete Verhalten, im Case Zweig wartet er 
dann nicht mehr auf das RXC0 bit, da dieses ja scheinbar bereits gesetzt 
ist.

Hier ist mein Code:
1
#define BAUD 4800UL      // Baudrate
2
#define F_CPU  16000000L
3
4
#include <avr/io.h>
5
#include <avr/interrupt.h>
6
#include <stdlib.h>
7
#include <util/setbaud.h>
8
9
// Variablendefinition 
10
uint8_t a = '1';//
11
uint8_t b = '2';
12
uint8_t c = '3';
13
uint8_t d = '4';
14
uint8_t e = '5';
15
16
17
void SendeByte(uint8_t Byte){
18
  while (!(UCSR0A & (1<<UDRE0)))  // warten bis Senden moeglich                
19
  {
20
  }
21
  UDR0 = Byte;                    // schreibt das Zeichen x auf die Schnittstelle
22
}
23
24
void EmpfangeVariable(void){
25
    while (!(UCSR0A & (1<<RXC0))){   // warten bis Zeichen verfuegbar
26
    }
27
    switch(UDR0){          // Variablenname zuordnen
28
        case 'a':  while (!(UCSR0A & (1<<RXC0))){   // warten bis Zeichen verfuegbar
29
              }  
30
              a=UDR0;
31
              SendeByte('a');  
32
              SendeByte(a);  
33
              break;
34
        case 'b':  while (!(UCSR0A & (1<<RXC0))){   // warten bis Zeichen verfuegbar
35
              }
36
              b=UDR0;
37
              SendeByte('b');  
38
              SendeByte(b);  
39
              break;
40
        case 'c':  while (!(UCSR0A & (1<<RXC0))){   // warten bis Zeichen verfuegbar
41
              }
42
              c=UDR0;
43
              SendeByte('c');
44
              SendeByte(c);  
45
              break;
46
        case 'd':  while (!(UCSR0A & (1<<RXC0))){   // warten bis Zeichen verfuegbar
47
              }
48
              d=UDR0;
49
              SendeByte('d');
50
              SendeByte(d);  
51
              break;
52
        case 'e':  while (!(UCSR0A & (1<<RXC0))){   // warten bis Zeichen verfuegbar
53
              }
54
              e=UDR0;
55
              SendeByte('e');
56
              SendeByte(e);  
57
              break;
58
          }
59
                                  
60
  }
61
62
// Hauptschleife
63
int main(void)
64
{
65
  // UART Initialisieren  
66
  UBRR0H = UBRRH_VALUE;
67
  UBRR0L = UBRRL_VALUE;
68
  UCSR0C = (1 << UCSZ01)|(1 << UCSZ00); // Asynchron Modus 8N1
69
  
70
  while(1)
71
    {
72
    // Variablen der Reihe nach ausgeben
73
    UCSR0B &= ~(1<<RXEN0);    // Receiver Deaktivieren
74
    UCSR0B |= (1<<TXEN0);    // Transmitter Aktivieren
75
    SendeByte('*');        // Startzeichen
76
    SendeByte(a);
77
    SendeByte(b);
78
    SendeByte(c);
79
    SendeByte(d);
80
    SendeByte(e);
81
    SendeByte('#');      // Endzeichen
82
    
83
    // Variable empfangen
84
    UCSR0B &= ~(1<<TXEN0);    // Transmitter Deaktivieren
85
    UCSR0B |= (1<<RXEN0);    // Receiver Aktiviern
86
    EmpfangeVariable();      
87
  } 
88
    
89
}

Wäre super nett, wenn einer von euch eine Idee dazu hätte.

Danke schonmal für die Mühe,

Philipp

von STK500-Besitzer (Gast)


Lesenswert?

Das UDR enthält nach dem ersten Lesen schon das zweite Zeichen. Du muust 
es also in einer Variablen zwischenspeichern.

von STK500-Besitzer (Gast)


Lesenswert?

Vergiss, was ich geschrieben habe.
So, wie du es machst, ist es sehr ungewöhnlich.

von Karl H. (kbuchegg)


Lesenswert?

Philipp schrieb:

>     switch(UDR0){          // Variablenname zuordnen

der switch hier aus dem Register heraus ist keine gute Idee.
Du weisst nicht, was der COmpiler aus dem switch macht. Wir wissen aber 
aus dem Datenblatt, dass du auf UDR0 nur ein einziges mal zugreifen 
darfst.

Daher: mach dir eine Zwischenvariable, in der du dir erst mal das Byte 
aus der UART holst und erst damit arbeitest du dann weiter.

Noch besser:
Schreib dir eine Funktion, die auf 1 Byte wartet und es zurückliefert. 
Denn deine ständige Codewiederholung dieser simplen Funktionalität geht 
sowieso auf keine Kuhhaut.
1
uint8_t LeseByte()
2
{
3
  while (!(UCSR0A & (1<<RXC0))) {   // warten bis Zeichen verfuegbar
4
  }
5
6
  return UDR0;
7
}

und diese Funktion rufst du auf, wenn du in weiterer Folge auf 1 Zeichen 
warten willst.
1
void EmpfangeVariable(void){
2
3
    uint8_t c = LeseByte();
4
5
6
    switch( c ) {          // Variablenname zuordnen
7
        case 'a':
8
              a = LeseByte();
9
              SendeByte('a');  
10
              SendeByte(a);  
11
              break;
12
13
...

Hast du das Zeichen, dann ist es zb auch eine gute Möglichkeit sich zu 
Debug-Zwecken dieses empfangene Byte über die UART wieder zurückschicken 
zu lassen, um zu kontrollieren, ob der µC auch richtig verstanden hat.


>     // Variable empfangen
>     UCSR0B &= ~(1<<TXEN0);    // Transmitter Deaktivieren
>     UCSR0B |= (1<<RXEN0);    // Receiver Aktiviern

Eher unüblich.
Wenn der Transmitter eingeschaltet ist, dann bleibt der das auch. Wozu 
soll das gut sein, wenn du dauernd umschaltest.
Solange du nichts sendest, geht auch nichts aus dem AVR raus. und 
umgekehrt.

Ständiges Umkonfigurieren ist meistens (nicht immer) eher ein Zeichen 
für schlechtes Programmdesign.

von Philipp (Gast)


Lesenswert?

Kann dir nicht ganz folgen. Ich sende doch von der Seriellen Konsole am 
PC nur ein Zeichen, wie kann das UDR da schon ein zweites Zeichen 
enthalten?

von Philipp (Gast)


Lesenswert?

Alles klar, werde es mal mit dem Zwischenspeichern und extra Funktion 
probieren.

von Karl H. (kbuchegg)


Lesenswert?

Ausserdem ist es in so einem Fall eine extrem gute Idee, wenn du dem 
switch einen default Zweig verpasst, in dem du zb mit einer weiteren 
Ausgabe zum PC signalisiern kannst, dass da was schief gelaufen ist.
1
void EmpfangeVariable(void){
2
3
    uint8_t c = LeseByte();
4
5
6
    switch( c ) {          // Variablenname zuordnen
7
        case 'a':
8
              a = LeseByte();
9
              SendeByte('a');  
10
              SendeByte(a);  
11
              break;
12
13
....
14
      default:
15
              SendeByte( '!' );
16
              SendeByte( c );
17
              SendeByte( '!' );
18
    }
19
}

Du brauchst zb nur irrtümlich auf deiner Tastatur den Shift-Lock 
eingeschaltet haben und schon überträgst du kein 'a' sondern ein 'A'. 
Was natürlich nicht dasselbe ist.
Wenn du einen menschlichen Benutzer am Terminal sitzen hast, musst du 
IMMER damit rechnen, dass sich der vertippt oder einen sonstigen Fehler 
macht und enstrpechend reagieren bzw. eine entsprechende Rückmeldung 
liefern.

von Karl H. (kbuchegg)


Lesenswert?

Karl H. schrieb:

>>     // Variable empfangen
>>     UCSR0B &= ~(1<<TXEN0);    // Transmitter Deaktivieren
>>     UCSR0B |= (1<<RXEN0);    // Receiver Aktiviern
>
> Eher unüblich.
> Wenn der Transmitter eingeschaltet ist, dann bleibt der das auch. Wozu
> soll das gut sein, wenn du dauernd umschaltest.
> Solange du nichts sendest, geht auch nichts aus dem AVR raus. und
> umgekehrt.

Ganz abgesehen davon musst du mir jetzt mal erklären, wie eigentlich die 
Funktion EmpfangeVariable() etwas ausgeben soll, wenn der Transmitter 
abgeschaltet ist.
1
    // Variable empfangen
2
    UCSR0B &= ~(1<<TXEN0);    // Transmitter Deaktivieren
3
    UCSR0B |= (1<<RXEN0);    // Receiver Aktiviern
4
    EmpfangeVariable();

die wird sich da eher schwer tun.

-> Transmitter einschalten, Receiver einschalten gehören an den 
Programmanfng zu den Initialisierungen und danach lässt du die UART 
diesbezüglich in Ruhe.
Anstatt da zu künsteln schreibst du dir lieber eine Funktion, mit der du 
Strings ausgeben kannst. Von der hast du mehr, denn die brauchst du alle 
Nase lang.

von Philipp (Gast)


Lesenswert?

So, habe mal nachgebessert, sieht nun so aus:
1
#define USERINTERFACE DEBUG
2
#define BAUD 4800UL      // Baudrate
3
#define F_CPU  16000000L
4
5
#include <avr/io.h>
6
#include <avr/interrupt.h>
7
#include <stdlib.h>
8
#include <util/setbaud.h>
9
10
11
12
// Variablendefinition 
13
14
uint8_t a = '1';//
15
uint8_t b = '2';
16
uint8_t c = '3';
17
uint8_t d = '4';
18
uint8_t e = '5';
19
uint8_t UartBuffer;
20
21
22
void SendeByte(uint8_t Byte){
23
  while (!(UCSR0A & (1<<UDRE0)))  // warten bis Senden moeglich                
24
  {
25
  }
26
  UDR0 = Byte;                    // schreibt das Zeichen x auf die Schnittstelle
27
}
28
29
uint8_t LeseByte()
30
{
31
  while (!(UCSR0A & (1<<RXC0))) {   // warten bis Zeichen verfuegbar
32
  }
33
34
  return UDR0;
35
}
36
37
void EmpfangeVariable(void){
38
    UartBuffer=LeseByte();
39
    switch(UartBuffer){          // Variablenname zuordnen
40
        case 'a':  while (!(UCSR0A & (1<<RXC0))){   // warten bis Zeichen verfuegbar
41
              }  
42
              a=UDR0;
43
              SendeByte('a');  
44
              SendeByte(a);  
45
              break;
46
        case 'b':  while (!(UCSR0A & (1<<RXC0))){   // warten bis Zeichen verfuegbar
47
              }
48
              b=UDR0;
49
              SendeByte('b');  
50
              SendeByte(b);  
51
              break;
52
        case 'c':  while (!(UCSR0A & (1<<RXC0))){   // warten bis Zeichen verfuegbar
53
              }
54
              c=UDR0;
55
              SendeByte('c');
56
              SendeByte(c);  
57
              break;
58
        case 'd':  while (!(UCSR0A & (1<<RXC0))){   // warten bis Zeichen verfuegbar
59
              }
60
              d=UDR0;
61
              SendeByte('d');
62
              SendeByte(d);  
63
              break;
64
        case 'e':  while (!(UCSR0A & (1<<RXC0))){   // warten bis Zeichen verfuegbar
65
              }
66
              e=UDR0;
67
              SendeByte('e');
68
              SendeByte(e);  
69
              break;
70
        default:  SendeByte('!');
71
              SendeByte(UartBuffer);
72
              SendeByte('!');
73
        }
74
                                  
75
  }
76
77
// Hauptschleife
78
int main(void)
79
{
80
  // UART Initialisieren  
81
  UBRR0H = UBRRH_VALUE;
82
  UBRR0L = UBRRL_VALUE;
83
  UCSR0C = (1 << UCSZ01)|(1 << UCSZ00); // Asynchron Modus 8N1
84
  UCSR0B |= (1<<TXEN0)|(1<<RXEN0);    // Transmitter + Receiver  Aktivieren
85
  
86
  while(1)
87
    {
88
    // Variablen der Reihe nach ausgeben
89
    SendeByte('*');        // Startzeichen
90
    SendeByte(a);
91
    SendeByte(b);
92
    SendeByte(c);
93
    SendeByte(d);
94
    SendeByte(e);
95
    SendeByte('#');      // Endzeichen
96
    
97
    // Variable empfangen
98
    EmpfangeVariable();      
99
  } 
100
}

Funktioniert leider immer noch nicht. Das Programm wartet nachdem ich 
vom PC ein Zeichen gesendet habe einfach nicht, sondern gibt direkt 
irgendwas aus.
Habe mal ein W gesendet, dann sollte ja in den Default Zweig gesprungen 
werden. Darauf erhalte ich folgende Rückmeldung:
!w!*12345#!(Hier ist wohl ein Zeichen für Zeilenumbruch??)
!*12345#

Verstehe nicht, warum er das ganze zweimal Ausführt. Sieht echt so aus 
als ob das TXC Bit für zwei Durchläufte aktiv bleibt, warum auch 
immer???

von Karl H. (kbuchegg)


Lesenswert?

Philipp schrieb:

> Habe mal ein W gesendet,


wie hast du das gemacht?

Auf die Taste 'w' gedrückt und danach auf die Taste 'Return'?

Auch 'Return' ist ein Zeichen, dass übertragen wird.

von Karl H. (kbuchegg)


Lesenswert?

Philipp schrieb:
> So, habe mal nachgebessert,

aber leider hast du nur die Hälfte gemacht.

Wenn du eine Funktion LeseByte hast, dann wird die natürlich an ALLEN 
Stellen benutzt, an der du auf 1 Byte von der UART wartest.
Auch zb hier
1
         case 'a':  while (!(UCSR0A & (1<<RXC0))){   // warten bis  Zeichen verfuegbar
2
               }
3
               a=UDR0;
4
...

auch hier
1
         case 'b':  while (!(UCSR0A & (1<<RXC0))){   // warten bis  Zeichen verfuegbar
2
               }
3
               b=UDR0;
4
....

und all den anderen Stellen!
Die Funktion wurde ja nicht zur Volksbelustigung geschrieben sondern 
dazu, ein für alle mal den Themenkreis "warte auf 1 Zeichen von der 
UART" an einer einzigen Stelle abzuhandeln.

von Philipp (Gast)


Lesenswert?

Habe schon beides getestet, sowohl mit der Return Taste als auch mit 
Mausklick auf Senden. Beides Mal das gleiche Ergebnis.
Verwende übrigens die serielle Konsole von der Arduino IDE, falle die 
Info hilft.

von Karl H. (kbuchegg)


Lesenswert?

Philipp schrieb:
> Habe schon beides getestet, sowohl mit der Return Taste als auch mit
> Mausklick auf Senden. Beides Mal das gleiche Ergebnis.

Ja.
Das Ergebnis ist, dass dein Terminalprogramm 2 Zeichen wegschickt, von 
denen eines ein '\n' (also ein Zeilenumbruch oder auch "Return" genannt) 
ist. Die Rückmeldung vom AVR beweist das eindeutig.

Entweder machst du deinem Terminalprogramm klar, dass es das zu 
unterlassen hat
oder du benutzt ein anderes Terminalprogramm welches das nicht macht
oder du kümmerst dich im AVR Programm darum, dass derartige \n 
ausgefiltert werden und dir nicht die Programmlogik durcheinander 
bringen.

von Philipp (Gast)


Lesenswert?

Sorry, das ging wohl etwas schnell, natürlich gehört es auch an den 
anderen Stellen geändert.
Mit den Änderungen und anderem Terminal Programm nochmal versucht,jetzt 
funktioniert es tatsächlich.

Vielen Vielen Dank für die schnelle Hilfe!

von Karl H. (kbuchegg)


Lesenswert?

Philipp schrieb:
> Sorry, das ging wohl etwas schnell, natürlich gehört es auch an den
> anderen Stellen geändert.
> Mit den Änderungen und anderem Terminal Programm nochmal versucht,jetzt
> funktioniert es tatsächlich.

Da kannst du mal sehen, wie hilfreich es sein kann, wenn einem sein 
Programm bei der Fehlerdiagnose mittels Ausgaben weiterhilft :-)
Vorher tappte man im Dunkeln, ein paar Ausgaben später ist alles klar.

Unterschätze das daher nicht! Ein paar Ausgaben zwischendurch können ein 
mächtiges Werkzeug sein.
Wenn alles fertig ist, kann man sie immer noch wieder entfernen.

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.