Forum: Mikrocontroller und Digitale Elektronik RS422 USART letzte 2 Bytes im String werden nicht gesendet


von V. B. (dr-robotnik)


Lesenswert?

Hallo zusammen!
Ich möchte mit einem ATtiny1634 was über einen RS422-Bus senden. dafür 
nutze ich den USART1 mit 9600 8N1 und einen MAX489 Treiber.

Zum Testen schicke ich einen String raus mit:
1
void RS422_PutStr(unsigned char *s) {
2
  /* enable output*/
3
  PORTB |= (1 << PORTB3);
4
  
5
  USART1_PutStr(s);
6
  
7
  /* disable output */
8
  PORTB &= ~(1 << PORTB3);
9
}
PORTB3 geht hierbei auf den Datenrichtungs-Pin auf dem MAX489.


Selsamerweise werden dabei immer die letzten beiden Zeichen im 
beliebigen String nicht gesendet.
Meine Vermutung war dass der Datenrichtungs-Pin zu schnell wieder auf 
low gezogen wird sobald der ATtiny aus der USART1_PutStr(s) Funktion 
zurückkommt und die letzten beiden Bytes zu dem Zeitpunkt noch in 
irgendwelchen Buffer-Registern hängen und noch nicht rausgeschiftet 
wurden.

Tatsächlich funktioniert das Ganze ohne Probleme wenn ich ein kurzes 
delay einbaue:
1
void RS422_PutStr(unsigned char *s) {
2
  /* enable output*/
3
  PORTB |= (1 << PORTB3);
4
  
5
  USART1_PutStr(s);
6
  
7
  _delay_ms(20);
8
  
9
  /* disable output */
10
  PORTB &= ~(1 << PORTB3);
11
}
Jetzt möchte ich das aber etwas eleganter lösen und einfach auf den 
TXC-Flag warten, denn im Datenblatt heißt es:

"This flag is set when the entire frame in the transmit shift register 
has been shifted out and there is no new data
currently present in the transmit buffer (UDRn)." Also probiere ich das 
mit:
1
void RS422_PutStr(unsigned char *s) {
2
  /* enable output*/
3
  PORTB |= (1 << PORTB3);
4
  
5
  USART1_PutStr(s);
6
  
7
  while (!(UCSR1A & (1 << TXC1)))
8
    ;
9
  
10
  /* disable output */
11
  PORTB &= ~(1 << PORTB3);
12
}

Überraschenderweise werden auch hier wieder die letzten beiden Zeichen 
nicht gesendet! Hat jemand eine Idee was ich falsch mache?

Vielen Dank und viele Grüße!

von Karl H. (kbuchegg)


Lesenswert?

Und wie sieht deine USART1_PutStr Funktion aus?

Und wenn die wieder weitere Funktionen aufruft, wie sehen diese aus?

von V. B. (dr-robotnik)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Und wie sieht deine USART1_PutStr Funktion aus?
>
> Und wenn die wieder weitere Funktionen aufruft, wie sehen diese aus?

Die sind ganz unspektakulär:
1
void USART1_PutChar(unsigned char c) {
2
  while (!(UCSR1A & (1 << UDRE1)))
3
    ;
4
  UDR1 = c;
5
}
6
7
void USART1_PutStr(unsigned char *s) {
8
  while (*s) {
9
    USART1_PutChar(*s);
10
    s++;
11
  }
12
}

Interrupts habe ich auch keine.

von Georg G. (df2au)


Lesenswert?

Das TXC Flag muss händisch gelöscht werden (oder der Interrupt muss 
ausgeführt werden). Das wird deine putstr Funktion vermutlich nicht 
machen. Also bleibt das Bit aktiv mit dem ersten Zeichen.

Wenn du ohne Interrupt sendest und eh deine Zeit mit warten auf 
"nächstes Zeichen kann raus" vertrödelst, warte doch einfach in putstr, 
das alles raus ist.

von Uwe (de0508)


Lesenswert?

Hallo,

so wie ich das aus dem DB lese, muss man das BIT auch wieder löschen, 
wenn es gesetzt wurde und ohne den zugehörigen Interrupt zu verwenden !
1
* Bit 6 - TXCn: USART Transmit Complete
2
This flag is set when the entire frame in the transmit shift register has been shifted out and there is no new data
3
currently present in the transmit buffer (UDRn). The TXCn flag bit is automatically cleared when a transmit complete interrupt is executed,
4
or it can be cleared by writing a one to its bit location.
5
The flag can generate a Transmit Complete interrupt (see TXCIEn bit).

von V. B. (dr-robotnik)


Lesenswert?

D.h. wenn ich auf interrupts verzichte, muss ich die Abfrage für jedes 
Zeichen innerhalb der USART1_PutChar() Funktion machen und danach das 
Flag durch schreiben einer 1 zurücksetzen.

Also etwa so?
1
void USART1_PutChar(unsigned char c) {
2
  while (!(UCSR1A & (1 << UDRE1)))
3
    ;
4
  UDR1 = c;
5
  
6
  /* wait for Tx compete */
7
  while (!(UCSR1A & (1 << TXC1)))
8
    ;
9
  
10
  /* clear TXC flag */
11
  UCSR1A |= (1 << TXC1);
12
  
13
}

von STK500-Besitzer (Gast)


Lesenswert?

Besser wäre es, das Flag vor dem Senden zu löschen.

von V. B. (dr-robotnik)


Lesenswert?

STK500-Besitzer schrieb:
> Besser wäre es, das Flag vor dem Senden zu löschen.

Macht Sinn. :-)


Vielen Dank Leute!

von Stefan E. (sternst)


Lesenswert?

V. Baumann schrieb:
> D.h. wenn ich auf interrupts verzichte, muss ich die Abfrage für jedes
> Zeichen innerhalb der USART1_PutChar() Funktion machen und danach das
> Flag durch schreiben einer 1 zurücksetzen.

Die (gängigere) Alternative wäre, das Flag in PutChar zu löschen (ohne 
Warten), und dann in einer übergeordneten Funktion (bei dir PutStr) zu 
Warten.

Egal welche Variante du benutzt, beim Flag-Löschen gibt es zwei Dinge zu 
beachten:

1) Löschen nach dem Schreiben nach UDR (nicht direkt davor, wie man es 
relativ häufig sieht).

2) Beide Aktionen müssen atomar gekapselt sein.

von Stefan E. (sternst)


Lesenswert?

V. Baumann schrieb:
> STK500-Besitzer schrieb:
>> Besser wäre es, das Flag vor dem Senden zu löschen.
>
> Macht Sinn. :-)

Nein, eben nicht. Das öffnet ein kleines Fenster für Race Conditions.

von Karl H. (kbuchegg)


Lesenswert?

Stefan Ernst schrieb:

> Nein, eben nicht. Das öffnet ein kleines Fenster für Race Conditions.

Ich bin heut geistig noch nicht ganz da.
Kannst du das bitte näher ausführen? Ich seh die Race Condition noch 
nicht (zumindest springt sie mir nicht ins Gesicht)

: Wiederhergestellt durch User
von Stefan E. (sternst)


Lesenswert?

Karl Heinz Buchegger schrieb:
>> Nein, eben nicht. Das öffnet ein kleines Fenster für Race Conditions.
>
> Ich bin heut geistig noch nicht ganz da.
> Kannst du das bitte näher ausführen? Ich seh die Race Condition noch
> nicht (zumindest springt sie mir nicht ins Gesicht)

Wenn der Ablauf länger unterbrochen wird (Interrupts), kann der UART 
leer laufen und das Flag wird mitten drin schon gesetzt. Dieses "mitten 
drin" könnte zwischen das Flag-Löschen und UDR-Schreiben fallen, und 
schon funktioniert das Warten auf das gerade nach UDR geschriebene Byte 
nicht mehr. Wenn das nun gerade das letzte Byte der aktuellen 
Übertragung war, wird diese zu früh "abgewürgt".

Zugegeben, ein sehr sehr kleines Fenster, aber eben vorhanden.

von Karl H. (kbuchegg)


Lesenswert?

Ja natürlich - Interrupts.
Danke.

(Ich brauch jetzt erst mal einen Eimer Kaffee)

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Das Warten auf "tx complete" muss eigentlich nur einmal erfolgen -- 
nämlich unmittelbar vor dem Deaktivieren des RS422-Treibers, also nach 
dem Senden des letzten Zeichens des Strings.

Wird nach jedem Zeichen gewartet, wird damit die effektive 
Übertragungsrate halbiert.

von Purzel H. (hacky)


Lesenswert?

Wie dass so ein Interrupt fuer Blockuebertragungen funktioniert sieht 
man hier :
http://www.ibrtses.com/embedded/avruart.html

von Stefan E. (sternst)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Das Warten auf "tx complete" muss eigentlich nur einmal erfolgen --
> nämlich unmittelbar vor dem Deaktivieren des RS422-Treibers, also nach
> dem Senden des letzten Zeichens des Strings.

Was ich ja auch als "gängigere" Variante vorgeschlagen habe.

Rufus Τ. Firefly schrieb:
> Wird nach jedem Zeichen gewartet, wird damit die effektive
> Übertragungsrate halbiert.

Also das ja nun nicht gerade. Es entsteht lediglich eine kleine Lücke 
zwischen den Zeichen. Selbst bei maximaler Baudrate (UBRR = 0, U2X = 1) 
käme man kaum auf eine Halbierung.

von Georg G. (df2au)


Lesenswert?

Stefan Ernst schrieb:
>> Das Warten auf "tx complete" muss eigentlich nur einmal erfolgen --
>> nämlich unmittelbar vor dem Deaktivieren des RS422-Treibers, also nach
>> dem Senden des letzten Zeichens des Strings.
>
> Was ich ja auch als "gängigere" Variante vorgeschlagen habe.

Was aber zwingend voraussetzt, dass man das Flag während der ersten 
Zeichen "passend" zurücksetzt.

In einer Anwendung ohne Interrupts ist es meist egal,ob ich am Anfang 
der Übertragung eines Zeichens auf das Freiwerden des Datenregisters 
warte oder am Ende auf "Schieberegister leer".

von Stefan E. (sternst)


Lesenswert?

Georg G. schrieb:
> Was aber zwingend voraussetzt, dass man das Flag während der ersten
> Zeichen "passend" zurücksetzt.

Was meinst du mit "passend"? Man muss doch nur dem Flag stumpf bei jedem 
Schreiben nach UDR "eins über braten".

Georg G. schrieb:
> In einer Anwendung ohne Interrupts ist es meist egal,ob ich am Anfang
> der Übertragung eines Zeichens auf das Freiwerden des Datenregisters
> warte oder am Ende auf "Schieberegister leer".

Es geht hier aber nicht um das "am Anfang der Übertragung eines 
Zeichens", sondern um das "nach der Übertragung aller Zeichen". Und da 
kommst du eben mit "auf das Freiwerden des Datenregisters warten" nicht 
weit.

von Georg G. (df2au)


Lesenswert?

Stefan Ernst schrieb:
> Was meinst du mit "passend"? Man muss doch nur dem Flag stumpf bei jedem
> Schreiben nach UDR "eins über braten".

Der UART ist auch sendeseitig doppelt gepuffert. Mit etwas Pech schiebst 
du das zweite Byte rein während das erste noch in Arbeit ist. Dann wird 
das Flag vom Byte 1 gesetzt und Nummer 2 kommt noch - unerwartet und 
geht verloren, weil die Senderichtung schon umgeschaltet wurde.

von Stefan E. (sternst)


Lesenswert?

Georg G. schrieb:
> Der UART ist auch sendeseitig doppelt gepuffert. Mit etwas Pech schiebst
> du das zweite Byte rein während das erste noch in Arbeit ist. Dann wird
> das Flag vom Byte 1 gesetzt

Nein, wird es eben nicht.

1
This flag bit is set when the entire frame in the Transmit Shift
2
Register has been shifted out and there are no new data currently
3
present in the transmit buffer (UDRn).

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.