Forum: Mikrocontroller und Digitale Elektronik Uart sendet falsch wenn er gestresst wird


von Hermann E. (hermann_e)


Angehängte Dateien:

Lesenswert?

Guten Abend an alle Forums Mitglieder,

seit ein paar Tagen schon schleppe ich mich mit einem Uart Problem 
herum, welches ich irgendwie nicht gebacken bekomme.
In meinem Programm soll nach Erledigen des eigentlichen Jobs die CPU für 
4s zum Schlafen gelegt werden, nach dem Aufwachen den nächsten Job 
erledigen und dann wieder Heia machen usw. Dies ist zwar mein 
Hauptproblem aber auf dem Weg dahin kommen mir einige Dinge in die 
Quere.
Um den Programmfluß zu überprüfen, lass ich den Uart in den 
verschiedenen Bereichen des Programmes einen Buchstaben senden nach dem 
Motto: 'Hier bin ich gerade'. Da aber z.B. die Uart Bibliothek von Peter 
Fleury Interrupt gesteuert ist (was ich übersetze mit: 'Ich sende das 
nächste Byte wenn gerade mal wieder Platz im UDR Register frei wird und 
in der Zwischenzeit tu' ich was anderes') habe ich dies durch meine 
eigene Funktion ersetzt die wirklich warten soll bis das Byte raus ist. 
Dies soll durch Übergabe des zu sendenden Bytes an 'UDR' und 
anschließendem Warten bis (wirklich) das TXC0 bzw. UDRE0 Bit gesetzt 
wird, erreicht werden.
Das Dumme ist nun dass es zwar grundsätzlich funktioniert aber wenn ein 
Programmsprung ansteht(?) Buchstaben verloren gehen bzw. falsch gesendet 
werden.
So sollte im angefügten Code in der Endlosschleife die Buchstabenfolge: 
'BCDXYZE' enstehen, was ich hingegen wirklich erhalte ist alle 4s 
'BC4ZE', d.h anstatt der Teilsequenz 'DXY' erhalte ich schlicht '4', 
selbst wenn ich noch zusätzlich explizit überprüfe dass UDR0 wieder 0 
ist (durch while(UDR0 != 0) {})
Der Uart ist mit 2 Stop bits konfiguriert, noch schlimmer wird's wenn 
ich nur ein Stop-Bit setze was ich auch nicht recht zu interpretieren 
weiss.
Die anderen Werte sind: CPU Atmega644A, BaudenQuarz: 11.0592MHz, Baud 
rate: 9600, FTDI Chip zur Verständigung mit dem Compi, AVR-GCC Compiler, 
die Fuse Bits sind auf maximale Aufwachzeit gesetzt: Low: FF, High: D1, 
Extended FF,
so dass die CPU nicht schlaftrunken irgendwo hinstolpert.

Hätte jemand 'ne Idee, i.B. dazu wie ich die CPU dazu zwingen kann so 
lange nichts anderes zu tun (um sicherzustellen) bis/dass jeder 
Buchstabe wirklich (korrekt) gesendet wird ?

Danke:  Hermann

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


Lesenswert?

Bei 9600 Baud passiert sowas eigentlich nicht. Was ist denn da auf der 
anderen Seite des UART? Etwa so ein UART-USB Wandler unserer asiatischen 
Freunde?
Die Vorgehensweise mit den TXC und UDR Flags ist jedenfalls die 
richtige.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Hermann E. schrieb:

> Motto: 'Hier bin ich gerade'. Da aber z.B. die Uart Bibliothek von Peter
> Fleury Interrupt gesteuert ist (was ich übersetze mit: 'Ich sende das
> nächste Byte wenn gerade mal wieder Platz im UDR Register frei wird und
> in der Zwischenzeit tu' ich was anderes')

was mal eigentlich sehr sinnvoll und komfortabel ist!

> habe ich dies durch meine
> eigene Funktion ersetzt die wirklich warten soll bis das Byte raus ist.
> Dies soll durch Übergabe des zu sendenden Bytes an 'UDR' und
> anschließendem Warten bis (wirklich) das TXC0 bzw. UDRE0 Bit gesetzt
> wird, erreicht werden.

Kann man machen, waber warum? Man kann genausogut warten, bis der FIFO 
der Lib leer ist und das letzte Zeichen gesendet wurde.
Aber wenn man das schon selber machen will, dann richtig!

So nicht!
1
void SendByte1(char byte)
2
{
3
    UDR0 = byte ;
4
   while (( UCSR0A & (1<<UDRE0))  == 0){};  
5
 //  while(UDR0 != 0) {}
6
}

Denn hier wird ja nur gewartet, bis UDRE wieder leer ist! Das ist aber 
NICHT der Zeitpunkt, an dem das Byte WIRKLICH übertragen wurde! Der UART 
hat einen Sendepuffer, eben UDR. Der wird nach dem Schreiben sofort ins 
Schieberegister des Senders kopiert und UDR ist dann wieder frei, 
sprich, UDRE geht auf 1. Du hast es doch selber gesagt, du willst auf 
TXC warten. Also muss man TXC VORHER löchen und dann auf das Setzen 
warten!

Eher so
1
void SendByte1(char byte)
2
{  
3
  UCSR0A = (1<<TXC0);  // TXC0 loeschen
4
  UDR0 = byte ;
5
  while (!( UCSR0A & (1<<TXC0)));  // warte auf Ende der Datenübertragung  
6
}

von Falk B. (falk)


Lesenswert?

Matthias S. schrieb:
> Die Vorgehensweise mit den TXC und UDR Flags ist jedenfalls die
> richtige.

Aber nur in der Theorie. Dazu müßte man TXC in der Praxis auch mal 
auswerten . . .und vorher mal löschen . . .

von Nick M. (Gast)


Lesenswert?

Das geht so nicht. Im SendByte2 schreibst du einfach was ins 
Senderegister ohne nachzusehen ob das schon gesendet wurde. Die Watchdog 
ISR nimmt keine Rücksicht darauf, ob das Sendbyte1 fertig ist oder 
nicht.

Wobei ich mit den ganzen Registern die du da prozessorspezifisch 
verwendest keine Ahnung hab was was ist.

von oszi40 (Gast)


Lesenswert?

Hermann E. schrieb:
> Das Dumme ist nun dass es zwar grundsätzlich funktioniert aber wenn ein
> Programmsprung ansteht(?) Buchstaben verloren gehen bzw. falsch gesendet
> werden.

Dann sende doch mal zum Test eine paaaaaaaar Buchstaben mehr und prüfe 
ob immer nur diese letzten beiden verloren gehen? Evtl. ist es auch ein 
Unterprogramm höherer Priorität, das Dir den Ast absägt?

von Nick M. (Gast)


Lesenswert?

Ich würde beim Senden zuerst schauen ob das Senderegister leer ist und 
dann das nächste Zeichen senden. Das geht dann auch schneller als 
andersrum. Denn so wird das Zeichen dann im Hintergrund verschickt, wozu 
auch die ganze Hardware ist.

von Hermann E. (hermann_e)


Lesenswert?

Erst einmal vielen dank für eure Mühe/Rückmeldung !

Punkt für Punkt:
@Matthias: Der Wandler ist zwar 'made in China', der Chipsatz aber ein 
FTDI232, ich arbeite ausschließlich mit diesem...

//-----------------------------------------------------

B. Falk:
"Dazu müßte man TXC in der Praxis auch mal  auswerten . . .und vorher 
mal löschen . . ."
Aber genau das tue ich doch in der Fkt "SendByte2(char byte)" (?). Das 
Ergebnis ist genau das Gleiche (ausprobiert !)

//-----------------------------------------------------

Nick Müller:
"Im SendByte2 schreibst du einfach was ins
Senderegister ohne nachzusehen ob das schon gesendet wurde. "
Das verstehe ich nicht: Die zwei Terme: "( UCSR0A & (1<<UDRE0)" bzw. 
"((UCSR0A)|(1<<TXC0)))" dienen doch genau dazu zu überwachen ob UDR 
gesendet hat.

Das Datenblatt meint doch:
TXCn:  This flag bit is set when the entire frame in the Transmit Shift 
Register has been shifted out and there are no new data currently 
present in the transmit buffer (UDRn).
bzw:
The UDREn Flag indicates if the transmit buffer (UDRn) is ready to 
receive new data. If UDREn is one, the buffer is empty, and therefore 
ready to be written.

Selbst wenn ich noch explizit den "Füllstand" von UDE überprüfe (siehe 
auskommentierte Zeile: "while(UDR0 != 0) {}" in Send1 und Send2 ist das 
Ergebnis immer noch unverändert.

von (prx) A. K. (prx)


Lesenswert?

> UCSR0A &=  ~(1<<TXC0);

"The TXCn Flag bit is automatically cleared when a transmit complete 
interrupt is executed, or it can be cleared by writing a one to its 
bit location."

> //  while(UDR0 != 0) {}

Es gibt eigentlich zwei UDR Register. Eines für die Senderichtung, das 
nur geschrieben werden kann, und eines davon völlig getrenntes für die 
Empfangsrichtung. In der Sendefunktion das Empfangsregister zu testen 
ist recht wahrscheinlich nicht das, was du vorhast.

: Bearbeitet durch User
von oszi40 (Gast)


Lesenswert?

Nick M. schrieb:
> schauen ob das Senderegister leer ist

Ich wüßte jetzt nicht so genau wie die Gegenstelle reagiert. Evtl. 
besteht da auch ein zeitliches Problem am anderen Ende? Testweise 
Umleitung von Ausgang auf Eingang wäre evtl. der nächste Schritt?

von Nick M. (Gast)


Lesenswert?

Hermann E. schrieb:
> "Im SendByte2 schreibst du einfach was ins
> Senderegister ohne nachzusehen ob das schon gesendet wurde. "
> Das verstehe ich nicht: Die zwei Terme: "( UCSR0A &

SendByte2 wird von einer ISR aufgerufen! Die kann jederzeit aufgerufen 
werden, auch wenn gerade SendByte1 in der Warte-Schleife ist und das 
Senderegister noch nicht (ganz) leer ist.
Du musst zuerst nachsehen ob das Senderegister leer ist und dann 
senden. Und das sinnvollerweise in beiden SendByte.

So wie du es jetzt machst ist es nochdazu ein blocking send, was nicht 
sehr sinnvoll ist.

von Falk B. (falk)


Lesenswert?

Hermann E. schrieb:
> B. Falk:
> "Dazu müßte man TXC in der Praxis auch mal  auswerten . . .und vorher
> mal löschen . . ."
> Aber genau das tue ich doch in der Fkt "SendByte2(char byte)" (?).

VORHER löschen!

>Das
> Ergebnis ist genau das Gleiche (ausprobiert !)

Quark.

von Hermann E. (hermann_e)


Lesenswert?

@ Ak
Die Aussage: [This flag bit is SET when the entire frame in the Transmit 
Shift Register has been shifted out ] "or it can be cleared by writing a 
one to its
bit location." ist natürlich richtig, habe ich glatt versemmelt, Danke 
für den Hinweis.
Leider hat aber auch die Umkehrung von:

while (( UCSR0A & (1<<UDRE0) && ((UCSR0A)|(1<<TXC0)))  == 0){};

UCSR0A &=  ~(1<<TXC0);

in

while (( UCSR0A & (1<<UDRE0) == 0) && ((UCSR0A)|(1<<TXC0)  != 0) ){};

UCSR0A |=  (1<<TXC0);

das Problem nicht gelöst.

//--------------------------------------------------------

Nun zu "Es gibt eigentlich zwei UDR Register"
Das war mir bislang so nicht klar, danke für den Hinweis! Vielleicht 
liegt ja sogar hier der Hund begraben weil ich im entsprechenden 
Abschnitt folgendes sehe:

" When data is written to the transmit buffer [UDR], and the Transmitter 
is enabled, the Transmitter will load the data into the Transmit Shift 
Register when the Shift Register is empty. Then the data will be 
serially transmitted on the TxDn pin.

D.h. wenn ich das so richtig verstehe wird der Inhalt vom "transmit 
buffer" in das "Transmit Shift Register" geschrieben (also zwei Buffer 
!) und dann gesendet. Dies könnte vielleicht zumindest ein Timing 
Problem erzeugen da ja mit Übergabe des Bytes von einem zum anderen 
Register das Byte noch nicht 'raus' ist und immer noch gesendet werden 
muß, also verspätet wird.

Ps: hab inzwischen den RS232 Wandler gewechselt, das Problem ist aber 
immer noch da....

Muß jetzt selbst in die Heia, morgen (Abend) geht's weiter...

Nochmals Danke:  Hermann

von oszi40 (Gast)


Lesenswert?


von Falk B. (falk)


Lesenswert?

Hermann E. schrieb:
> Leider hat aber auch die Umkehrung von:
>
> while (( UCSR0A & (1<<UDRE0) && ((UCSR0A)|(1<<TXC0)))  == 0){};
>
> UCSR0A &=  ~(1<<TXC0);
>
> in
>
> while (( UCSR0A & (1<<UDRE0) == 0) && ((UCSR0A)|(1<<TXC0)  != 0) ){};
>
> UCSR0A |=  (1<<TXC0);
>
> das Problem nicht gelöst.

Bis du merkbefreit? Oder kennst du die Bedeutung des Worts VORHER nicht?

Beitrag "Re: Uart sendet falsch wenn er gestresst wird"

Und nimm nur EINE Sendefunktion, nämlich SenByte1().
Der Rest deines Programms ist auch rein konzeptionell als auch vom 
Aufbau her eher fragwürdig. Egal, man schafft es trotzdem, die Daten 
komplett zu senden, bevor man in den Sleep Mode geht.

von huhu (Gast)


Lesenswert?

ja, und den Ausdruck in der Bedingung der While-Schleife so klammern, 
wie man ihn ausgeführt haben möchte. Sonst wird es keine 
"Warteschleife".

von Peter D. (peda)


Lesenswert?

Die UART ist nicht reentrant, wenn 2 Tasks (Main, Interrupt) darauf 
zugreifen, muß es schief gehen.
Lösung 1:
Die Interrupttask schreibt in einen Puffer, die Maintask sendet ihn, 
wenn die UART frei ist.
Lösung2:
Der UART Interrupt verwaltet 2 Puffer, je einen für jede Task und sendet 
sie abwechselnd.

von Hermann E. (hermann_e)


Lesenswert?

@Nick: "So wie du es jetzt machst ist es noch dazu ein blocking send, 
was nicht sehr sinnvoll ist"

Das war ja gerade die Vorgabe, ich wollte ja den Prozessablauf solange 
stoppen bis das Byte gesendet wurde !

//---------------------------------------------------------------

@ B. Falk: Danke, das war i.d.T. die Lösung, also:

void SendByte(char byte)
{
    UCSR0A |=  (1<<TXC0);
    UDR0 = byte ;
   while (!( UCSR0A & (1<<TXC0)));
}

blockiert, wie erhofft, solange den Programmablauf bis das Byte 
[wirklich] raus ist.
Deine wirklich hilfreiche und kompetente Antwort ist zwar etwas 
"brummelig" bei mir angekommen, aber die Kompetenz ist unleugbar 
vorhanden.
Unsere Posts haben sich vorgestern Abend gekreuzt so dass ich deinen 
zweiten Kommentar noch nicht gelesen hatte.

Nochmals Daumen hoch an alle Beteiligten, das hat mir echt weiter 
geholfen.

Grüße aus dem warmen Südfrankreich:  Hermann

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.