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_ta='1';//
11
uint8_tb='2';
12
uint8_tc='3';
13
uint8_td='4';
14
uint8_te='5';
15
16
17
voidSendeByte(uint8_tByte){
18
while(!(UCSR0A&(1<<UDRE0)))// warten bis Senden moeglich
19
{
20
}
21
UDR0=Byte;// schreibt das Zeichen x auf die Schnittstelle
22
}
23
24
voidEmpfangeVariable(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
intmain(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
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_tLeseByte()
2
{
3
while(!(UCSR0A&(1<<RXC0))){// warten bis Zeichen verfuegbar
4
}
5
6
returnUDR0;
7
}
und diese Funktion rufst du auf, wenn du in weiterer Folge auf 1 Zeichen
warten willst.
1
voidEmpfangeVariable(void){
2
3
uint8_tc=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.
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
voidEmpfangeVariable(void){
2
3
uint8_tc=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.
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.
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???
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.
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.
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.
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.
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!
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.