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
voidusart_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
charusart_RecieveByte(){
9
while(!(UCSRA&(1<<RXC)));// wait for data to be recieved
10
charc=UDR;// get data from recieve buffer
11
UCSRA|=(1<<RXC);// clear recieve complete flag
12
returnc;
13
}
14
15
voidusart_SendByte(uint8_tc){
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
voidusart_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)
@ 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.
>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
@ 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.
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.
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
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.
@ 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.
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!
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?
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:
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?
@ 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.
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...
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.
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
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.
@ 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?
@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
voidusart_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.
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.
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.
>> 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.
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!!!
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.
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!!!