Forum: Mikrocontroller und Digitale Elektronik AVR / RS 485: TXC vor JEDEM Byte löschen?


von Claude M. (stoner)


Lesenswert?

Guten Morgen

Ich habe zwei ATMEGA8, die mit je einem MAX485 über eine Datenleitung 
mit 18'200 Baud kommunizieren. Die Leitungen sind an beiden Enden mit 
120R terminiert und mit 2 x 390R fail save gemacht. Inzwischen 
funktioniert das zwar alles perfekt, ich kann mir aber nicht so recht 
erklären, weshalb es mit dem ursprünglichen Ansatz nicht funktioniert 
hat. Ev. hilft die Diskussion dazu ja auch anderen...

Anfänglich hatte ich das sehr verbreitete Problem, dass das letzte 
gesendete Byte nach dem Abschalten des TX des MAX485 verloren gegangen 
ist, obschon ich vorher auf das TXC Flag geprüft hatte und dieses auch 
jeweils zu Beginn des Sendevorgangs gelöscht hatte (nicht vor jedem 
Byte, sondern vor jeder Bytefolge) - so wie das auch in sehr vielen 
Threads (z.B. Thread 270268) beschrieben ist. Wenn ich einen genügend 
langen Delay vor dem Abschalten eingefügt hatte, hat es zwar 
funktioniert, aber die Lösung schien mir nicht sauber und auch nicht 
effizient.

So habe ich nach längerem Analysieren festgestellt, dass sich das 
Problem beheben lässt, wenn vor Senden JEDES Bytes das TXC Flag gelöscht 
wird. Das würde mir zwar einleuchten, wenn das Füllen des Buffers nicht 
so schnell wäre wie das Übertragen der Daten. Das ist ja aber kaum der 
Fall. Im übrigen scheint es ja auch Leute zu geben, bei denen es 
funktioniert, wenn man nur zu Beginn des Sendens das TXC Flag löscht.

Hat jemand von euch eine Erklärung?

Hier der Code wie er heute aussieht (im alten Code wurde das TXC Flag 
nicht am Anfang von SendByte gelöscht sondern am Anfang von SendString).
1
void usart_Init(void) {
2
    UBRRH = (BAUD_PRESCALE >> 8);   // Set baud rate (high byte)
3
    UBRRL = (BAUD_PRESCALE & 0xFF); // Set baud rate (low byte)
4
    UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); // Set Frame format: 8N1
5
    UCSRB |= (1<<RXEN)|(1<<TXEN);   // Enable receiver and transmitter
6
}
7
8
char usart_RecieveByte() {
9
    while(!(UCSRA & (1<<RXC)));  // wait for data to be recieved
10
    char c = UDR;                // get data from recieve buffer
11
    UCSRA |= (1<<RXC);           // clear recieve complete flag
12
    return c;
13
}
14
15
void usart_SendByte(uint8_t c) {
16
    UCSRA |= (1<<TXC);            // clear transmit complete flag
17
    while (!(UCSRA & (1<<UDRE))); // wait for empty transmit buffer
18
    UDR = c;                      // put data into transmit buffer
19
}
20
21
void usart_SendString(uint8_t *s) {
22
    PORTD |= (1<<PD2);           // set MAX 485 to transmit mode
23
    while (*s) {                 // send byte by byte
24
        usart_SendByte(*s);
25
        s++;
26
    }
27
    while (!(UCSRA & (1<<TXC))); // wait for transmit complete flag (all data sent)
28
    PORTD &= ~(1<<PD2);          // set MAX 485 to recieve mode
29
}


Viele Grüsse
Claude

von Falk B. (falk)


Lesenswert?

@ Claude Marksteiner (stoner)

>mit 18'200 Baud kommunizieren. Die Leitungen sind an beiden Enden mit

Komische Baudrate. Oder Tippfehler? 19k2?

>Anfänglich hatte ich das sehr verbreitete Problem, dass das letzte
>gesendete Byte nach dem Abschalten des TX des MAX485 verloren gegangen
>ist, obschon ich vorher auf das TXC Flag geprüft hatte und dieses auch
>jeweils zu Beginn des Sendevorgangs gelöscht hatte (nicht vor jedem
>Byte, sondern vor jeder Bytefolge) - so wie das auch in sehr vielen
>Threads (z.B. Thread 270268) beschrieben ist. Wenn ich einen genügend
>langen Delay vor dem Abschalten eingefügt hatte, hat es zwar
>funktioniert, aber die Lösung schien mir nicht sauber und auch nicht
>effizient.

eben.

>So habe ich nach längerem Analysieren festgestellt, dass sich das
>Problem beheben lässt, wenn vor Senden JEDES Bytes das TXC Flag gelöscht
>wird.

Braucht man nicht, wenn man die Bytes schnell genug in den UART 
schreiben kann.

>Hier der Code wie er heute aussieht (im alten Code wurde das TXC Flag
>nicht am Anfang von SendByte gelöscht sondern am Anfang von SendString).

Dein Code ist OK, das Löschen vor jedem Byte ist nicht nötig.
Der Fehler liegt woanders.

von Purzel H. (hacky)


Lesenswert?

>while(!(UCSRA & (1<<RXC)));  // wait for data to be recieved


Aaaaahhhhhhh !!!!!

Nein, so nicht. Mach ne Zustandsmaschine & Interrupts. Warten ist nun 
absolut gar nichts. Als anleitung siehe :
http://www.ibrtses.com/embedded/avruart.html

von Falk B. (falk)


Lesenswert?

@ Siebzehn mal Fuenfzehn (hacky)

>>while(!(UCSRA & (1<<RXC)));  // wait for data to be recieved

>Aaaaahhhhhhh !!!!!

>Nein, so nicht. Mach ne Zustandsmaschine & Interrupts. Warten ist nun
>absolut gar nichts. Als anleitung siehe :

Das hat mit dem eigentlichen Problem rein gar nixhts zu tun. Und man 
kann schon UART-Daten mit busy waiting empfangen, wenn man mit den 
Einschränkungen leben kann.

von Purzel H. (hacky)


Lesenswert?

Es ist bekannt, dass das TxComplete kommt, wenn das letzte Byte draussen 
ist. Zu diesem Zeitpunkt ist das Stopbit des letzten Bytes aber noch 
nicht draussen. Das kann ein Problem sein, oder auch nicht. Denn das 
Stopbit kommt so nicht an. Ausser der abgeschaltete Treiber fuehrt zu 
einem impliziten Stopbit.

Sonst ja.
Bei DataregisterEmpty nachfuellen, das loescht auch gleich das 
DataregisterEmpty.
Am Ende des Vorganges (Blockes) das DataregisterEmpty haendisch
loeschen und das TxComplete abwarten, dann die Direction Leitung
umschalten.

von spess53 (Gast)


Lesenswert?

Hi

>Es ist bekannt, dass das TxComplete kommt, wenn das letzte Byte draussen
>ist. Zu diesem Zeitpunkt ist das Stopbit des letzten Bytes aber noch
>nicht draussen.

Wo ist das bekannt?

Datenblatt:

Bit 6 – TXCn: USART Transmit Complete

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).

Ein Frame umfasst Startbit, Datenbits und Stopbit.

MfG Spess

von Michael (Gast)


Lesenswert?

spess53 schrieb:
> This flag bit is set when the entire frame in the Transmit Shift
> Register has been shifted out ...
Rausgeschoben ist es am Anfang des Stop-Bits. Ob das auch so zu 
verstehen ist, verrät ein Oszi und eine Zeitmarke auf einem I/O-Pin bei 
TXCn.

von Falk B. (falk)


Lesenswert?

@ Michael (Gast)

>> This flag bit is set when the entire frame in the Transmit Shift
>> Register has been shifted out ...
>Rausgeschoben ist es am Anfang des Stop-Bits. Ob das auch so zu
>verstehen ist, verrät ein Oszi und eine Zeitmarke auf einem I/O-Pin bei
>TXCn.

Been there, done that. Der TXC Interrupt kommt NACH dem Senden des 
Stop-Bits.

von Claude M. (stoner)


Lesenswert?

Falk Brunner schrieb:

> Komische Baudrate. Oder Tippfehler? 19k2?

Ja, müsste natürlich 19k2 sein :-)

> Braucht man nicht, wenn man die Bytes schnell genug in den UART
> schreiben kann.

Sehe ich auch so.

> Dein Code ist OK, das Löschen vor jedem Byte ist nicht nötig.
> Der Fehler liegt woanders.

Hmmm...

Danke schon mal für dein Feedback!

von Claude M. (stoner)


Lesenswert?

Siebzehn mal Fuenfzehn schrieb:

>>while(!(UCSRA & (1<<RXC)));  // wait for data to be recieved

> Aaaaahhhhhhh !!!!!
>
> Nein, so nicht. Mach ne Zustandsmaschine & Interrupts. Warten ist nun
> absolut gar nichts.

Danke für das Feedback. Ja, ist auch so vorgesehen. Um die 
Grundfunktionalität zu entwickeln / testen schien es mir aber sinnvoller 
mich auf das Nötigste zu beschränken um andere Fehler / Nebeneffekte 
möglich auszuschliessen.

Wie Falk schon gesagt hat, dürfte das Problem / die Fragestellung aber 
unabhänig davon sein ob mit Interrupt oder Polling gearbeitet wird.

Siebzehn mal Fuenfzehn schrieb:
> Es ist bekannt, dass das TxComplete kommt, wenn das letzte Byte draussen
> ist. Zu diesem Zeitpunkt ist das Stopbit des letzten Bytes aber noch
> nicht draussen. Das kann ein Problem sein, oder auch nicht. Denn das
> Stopbit kommt so nicht an. Ausser der abgeschaltete Treiber fuehrt zu
> einem impliziten Stopbit.

Abgesehen davon, dass dem meiner Meinung nach nicht so ist, so dürfte es 
- wenn das das Problem wäre  - dann aber auch nicht funktionieren wenn 
ich vor JEDEM Byte das TXC Flag lösche, oder?

von Peter D. (peda)


Lesenswert?

Claude Marksteiner schrieb:
> UCSRA |= (1<<TXC);            // clear transmit complete flag
>     while (!(UCSRA & (1<<UDRE))); // wait for empty transmit buffer

Das geht so nicht.
Dann sind ja 2 Byte im Puffer, aber das TXC kommt schon nach dem 1. 
Byte, also 1 Byte zu früh.

Du mußt es so machen:
1
  UCSRA = 1<<TXC;
2
  UDR = val;
3
  while((UCSRA & 1<<TXC) == 0 );

von Georg G. (df2au)


Lesenswert?

Falk Brunner schrieb:
> Braucht man nicht, wenn man die Bytes schnell genug in den UART
> schreiben kann.

Genau da würde ich suchen. Es genügt, dass während der Sendung des 
Blockes eine Verzögerung von 1..2 Byte Länge auftritt und schon ist das 
Flag gesetzt und am realen Ende des Blockes wird der Treiber zu früh 
abgeschaltet.

Timer oder anderer Interrupt, der zu lange braucht?

von Falk B. (falk)


Lesenswert?

@ Peter Dannegger (peda)

>> UCSRA |= (1<<TXC);            // clear transmit complete flag
>>     while (!(UCSRA & (1<<UDRE))); // wait for empty transmit buffer

>Das geht so nicht.

Doch.

>Dann sind ja 2 Byte im Puffer, aber das TXC kommt schon nach dem 1.
>Byte, also 1 Byte zu früh.

Das kommt nur, wenn das aktuelle Byte VOLLSTÄNDIG übertragen wurde UND 
der FIFO leer ist.

>Du mußt es so machen:

>  UCSRA = 1<<TXC;
>  UDR = val;
>  while((UCSRA & 1<<TXC) == 0 );

Aber nicht in einer Funktion, die einzelene Bytes senden soll. Da sollte 
man VORHER prüfen, ob der FIFO leer ist und nicht hinterher sinnlos Zeit 
verplempern, damit der FIFO garantiert leer läuft. Er will ja TXC gerade 
NICHT während der Übertragung sehen sonsern nur einmal am Ende.

von Claude M. (stoner)


Lesenswert?

Georg G. schrieb:

> Falk Brunner schrieb:
>> Braucht man nicht, wenn man die Bytes schnell genug in den UART
>> schreiben kann.
>
> Genau da würde ich suchen. Es genügt, dass während der Sendung des
> Blockes eine Verzögerung von 1..2 Byte Länge auftritt und schon ist das
> Flag gesetzt und am realen Ende des Blockes wird der Treiber zu früh
> abgeschaltet.
>
> Timer oder anderer Interrupt, der zu lange braucht?

Ja, das ist die einzige Erklärung, die mit im Moment dazu einfällt. Aber 
um das auszuschliessen habe ich genau deshalb für den Moment auf Timer, 
Interrupts und dergleichen verzichtet. Ich kann mir deshalb nicht 
verstellen, dass irgendwas das Programm während des Schreibens 
verzögert. Ich werde heute Abend mal den Zustand des TXC Flags zwischen 
den dem Senden der einzelnen Bytes protokollieren...

von Claude M. (stoner)


Lesenswert?

Peter Dannegger schrieb:

> Du mußt es so machen:
1
>   UCSRA = 1<<TXC;
2
>   UDR = val;
3
>   while((UCSRA & 1<<TXC) == 0 );

Ja, das funktioniert aber nur für ein einzelnes Byte. Ich möchte aber 
eine ganze Bytefolge senden und erst am Schluss warten bis alles 
gesendet ist. Dazwischen reichte es ja zu prüfen ob der Buffer leer ist. 
Wenn ich auf TXC prüfen würde, so würde ich einiges an Zeit 
verschwenden.

von Falk B. (falk)


Lesenswert?

Poste einen VOLLSTÄNDIGEN Sourcecode deines Testprogramms als Anhang.

von Peter D. (peda)


Lesenswert?

Falk Brunner schrieb:
> Doch.

Nein.
Nur meine Erklärung war falsch.

Falk Brunner schrieb:
> Das kommt nur, wenn das aktuelle Byte VOLLSTÄNDIG übertragen wurde UND
> der FIFO leer ist.

Stimmt.
Daher kann es zu einer Race-Condition kommen, wenn Interrupts auftreten 
und die Baudrate hoch ist:
1
    UCSRA |= (1<<TXC);            // clear transmit complete flag
2
// hier können zufällig alle vorherigen Bytes fertig sein und TXC setzen
3
    while (!(UCSRA & (1<<UDRE))); // wait for empty transmit buffer
4
// hier können auch zufällig alle vorherigen Bytes fertig sein und TXC setzen
5
    UDR = c;                      // put data into transmit buffer

Der kritische Bereich ist zwar nur wenige Zyklen lang, aber er ist da.
Es müssen nur zufällig Interrupts auftreten, die das letzte Byte senden 
um knapp 2 Bytezeiten verzögern.

Nur die Codeabfolge von mir ist dagegen geschützt.
Das TXC kann man leider nicht vor dem Senden prüfen, da es nach einem 
Reset gelöscht ist.

Hier noch eine Lösung:
1
    while (!(UCSRA & (1<<UDRE))); // wait for empty transmit buffer
2
    cli();
3
    UDR = c;                      // put data into transmit buffer
4
    UCSRA = (1<<TXC);             // clear transmit complete flag
5
    sei();

von Claude M. (stoner)


Angehängte Dateien:

Lesenswert?

Falk Brunner schrieb:
> Poste einen VOLLSTÄNDIGEN Sourcecode deines Testprogramms als Anhang.

Ok, hier der vollständige Code.

Soviel aber schon mal vorweg. Ich weiss, dass es Stellen hat, die dirty 
sind und noch korrigiert werden müssen. War aber auch nur als Prtotyp 
und auch nicht für euch gedacht ;-) Zerreisst mich jetzt also deswegen 
nicht in der Luft.

Den oben geposteten Auszug hatte ich für euch noch etwas optisch 
aufgepeppt und einen überflüssigen Parameter SendBlock entfernt.

von Falk B. (falk)


Lesenswert?

@ Peter Dannegger (peda)

>Daher kann es zu einer Race-Condition kommen, wenn Interrupts auftreten
>und die Baudrate hoch ist:

Jetzt wird es noch exotischer!

>    UCSRA |= (1<<TXC);            // clear transmit complete flag

das sit nur ein Würg around, weil der OP sich ncht erklären kann, wieso 
das Bit mehrfach gesetzt wird.


>Der kritische Bereich ist zwar nur wenige Zyklen lang, aber er ist da.
>Es müssen nur zufällig Interrupts auftreten, die das letzte Byte senden
>um knapp 2 Bytezeiten verzögern.

Es gibt angeblich keine Interrupts im Programm des OP.

>Nur die Codeabfolge von mir ist dagegen geschützt.
>Das TXC kann man leider nicht vor dem Senden prüfen,

Wozu auch?

>    while (!(UCSRA & (1<<UDRE))); // wait for empty transmit buffer
>    cli();
>    UDR = c;                      // put data into transmit buffer
>    UCSRA = (1<<TXC);             // clear transmit complete flag
>    sei();

Was soll das?

von Falk B. (falk)


Lesenswert?

@Claude M. (stoner)

>Ok, hier der vollständige Code.

>Soviel aber schon mal vorweg. Ich weiss, dass es Stellen hat, die dirty
>sind und noch korrigiert werden müssen. War aber auch nur als Prtotyp
>und auch nicht für euch gedacht ;-)

Großer Irrtum, mit sowas holt man sich viele Probleme an den Hals.

"Die schnellste und preiswerteste Art etwas zu tun, ist es gleich 
richtig zu tun."

>Den oben geposteten Auszug hatte ich für euch noch etwas optisch
>aufgepeppt und einen überflüssigen Parameter SendBlock entfernt.

?? Genau dort sehe ich ein Problem! Du hast eine Stringfunktion 
missbraucht, um generische Binärdaten zu senden! Was denn nun? Wird das 
Ende SICHER per \0 Terminator erkannt oder immer n Zeichen gesendet?

OK, deine Funktion sendDatagram übergibt einen STRING, der auch \0 
terminiert ist. Somit sollte es laufen
1
void usart_SendBlock(uint8_t *s) {
2
  PORTD |= (1<<PD2);           // RS485-Tranciever vom µC auf Senden schalten
3
  UCSRA = (1<<TXC);    // clear TXC flag
4
  while (*s) {
5
    usart_SendByte(*s);
6
    s++;
7
  }
8
    while (!(UCSRA & (1<<TXC))); // Warte bis TXComplete
9
    PORTD &= ~(1<<PD2);          // RS485-Tranciever vom µC wieder auf Empfang schalten
10
}

Es reicht vollkommen, einmalig vor dem Senden des Datenblocks TXC zu 
löschen.

von Claude M. (stoner)


Lesenswert?

Peter Dannegger schrieb:

> Daher kann es zu einer Race-Condition kommen, wenn Interrupts auftreten
> und die Baudrate hoch ist:

> Der kritische Bereich ist zwar nur wenige Zyklen lang, aber er ist da.
> Es müssen nur zufällig Interrupts auftreten, die das letzte Byte senden
> um knapp 2 Bytezeiten verzögern.

Ok, da hast du theoretisch recht. Aber:

1.) gibt es zur Zeit keine Interrupts (ich bin aber trotzdem dankbar um 
den Hinweis, da es die definitiv mal geben wird)

2.) müsste die Verzögerung bei einer Taktfrequenz von 8 MHz (sorry, das 
hatte ich noch nicht erwähnt) dann ja um die 9000 Takte lang sein. Das 
dürfte aber ein Vielfaches dessen sein, das ein vernünftig geschriebener 
Interrupt Handler in Anspruch nimmt.

von Claude M. (stoner)


Lesenswert?

Falk Brunner schrieb:

>>Den oben geposteten Auszug hatte ich für euch noch etwas optisch
>>aufgepeppt und einen überflüssigen Parameter SendBlock entfernt.

> ?? Genau dort sehe ich ein Problem! Du hast eine Stringfunktion
> missbraucht, um generische Binärdaten zu senden! Was denn nun? Wird das
> Ende SICHER per \0 Terminator erkannt oder immer n Zeichen gesendet?

Ja, ich hatte den Bug heute morgen während des Postens des Codes bemerkt 
(deshalb habe ich das auch geändert). Da das Phänomen aber schon bestand 
als ich noch mit einem fixen String gearbeitet hatte und ich auch sicher 
war, dass der Datenblock auf Grund der Initialisierung und des Codes 
auch jetzt noch 0-terminiert ist, hatte ich mich entschlossen doch schon 
zu posten. Ich werde das aber gleich als erstes korrigieren.

> Es reicht vollkommen, einmalig vor dem Senden des Datenblocks TXC zu
> löschen.

Sehe ich auch so. Aber dann funktioniert es eben nicht mehr ohne einen 
zusätzlichen Delay.

von Falk B. (falk)


Lesenswert?

>> Es reicht vollkommen, einmalig vor dem Senden des Datenblocks TXC zu
>> löschen.

>Sehe ich auch so. Aber dann funktioniert es eben nicht mehr ohne einen
>zusätzlichen Delay.

Glaub ich nicht. Probier es noch einmal. Möglicherweise war das ein 
Nebeneffekt aus deinem anderen Code. Der COde hier ist sehr einfach und 
MUSS funktionieren.

von Claude M. (stoner)


Lesenswert?

Falk Brunner schrieb:

>>Sehe ich auch so. Aber dann funktioniert es eben nicht mehr ohne einen
>>zusätzlichen Delay.
>
> Glaub ich nicht. Probier es noch einmal. Möglicherweise war das ein
> Nebeneffekt aus deinem anderen Code. Der COde hier ist sehr einfach und
> MUSS funktionieren.

Ja, dachte ich auch. Zumal das Ganze sich schon so verhalten hat als der 
Code noch einfacher war (mit fixen String, ohne generischen Binärdaten 
mit Header und Message).

Aber gut, ich werde heute Abend meinen Code noch etwas bereinigen und es 
nochmals probieren...

Vielen Dank schon mal allen für die Hilfe!!!

von Purzel H. (hacky)


Lesenswert?

Ja. String Operationen sollte man eh vermeiden. Auch wenn man mit fest 
allozierten Strings arbeitet. Eine Laengen-variable ist da besser. Denn 
sie vermeidet das Zaehlen. Und laesst die Nullen durch.

von Claude M. (stoner)


Lesenswert?

Falk Brunner schrieb:

> Glaub ich nicht. Probier es noch einmal. Möglicherweise war das ein
> Nebeneffekt aus deinem anderen Code. Der COde hier ist sehr einfach und
> MUSS funktionieren.

Falk hatte wie immer recht!

Ich habe UCSRA = (1<<TXC) wieder an den Beginn von SendBlock() 
verschoben und siehe da, es funktioniert noch immer!

Ich werde wohl meine Backups durchforsten müssen um zu sehen, was ich in 
meinen früheren Versuchen anders gemachst hatte (ich bin ja der Meinung, 
dass ich das so schon mal genauso hatte)...

Es ist mir nun schon fast ein wenig peinlich, dass ich euch dafür 
belästigt habe. Naja, vieleicht helfen die in diesem Thread geführten 
Diskussionen ja wieder mal jemandem.

Auf jeden Fall ein grosses Dankeschön an alle, die sich zu Wort gemeldet 
haben. Insbesondere an Falk Brunner, Peter Dannegger, Spess und Hacky, 
die sich ja seit Jahren unermüdlich in diesem Forum engagieren!!!

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.