Forum: Compiler & IDEs UART Baudrate clever runden


von UART (Gast)


Lesenswert?

Kann mir mal bitte jemand die folgenden defines erklären
1
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1)   // clever runden
2
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))     // Reale Baudrate
3
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD)

Im Datenblatt steht doch immer

Wo kommt also das "+BAUD*8" her?
Was ist daran clever gerundet?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

BAUD*8 ist genau die Hälfte von BAUD*16.

Zum Ausgleichen von Rundungsungenauigkeiten kann man 0.5 addieren - 
genau das macht der Code.

von Uwe .. (uwegw)


Lesenswert?

Der Präprozessor würde bei der Division gar nicht runden, sondern 
einfach die Nachkommastellen abschneiden. Nun gibt es den Trick, zum 
Zähler die Hälfte des Nenners zu addieren.
Beispiel: 29/10 würde ohne Runden 2 ergeben. Rechnet man aber (29+5)/10, 
kommt man auf 3, was einer Rundung entspricht.

von Karl H. (kbuchegg)


Lesenswert?

UART schrieb:

> Was ist daran clever gerundet?


Es geht um den Fehler, den man macht, wenn man ein, dem Prinzip nach, 
Floating Point Ergebnis einem Integer zuweist. Da werden die 
Kommastellen einfach abgeschnitten.

 int i

 i = 1.0;     // i hat den Wert 1
 i = 1.1;     // i hat den Wert 1
 i = 1.4;     // i hat den Wert 1
 i = 1.7;     // i hat den Wert 1
 i = 1.8;     // i hat den Wert 1
 i = 1.9;     // i hat den Wert 1
 i = 1.99;    // i hat den Wert 1
 i = 2.0;     // i hat den Wert 2


Das ist aber blöd, denn 1.99 liegt viel näher an 2 als an 1.
Gerundet wäre zb so

 i = 1.0;     // i hat den Wert 1
 i = 1.1;     // i hat den Wert 1
 i = 1.4;     // i hat den Wert 1
 i = 1.7;     // i hat den Wert 2
 i = 1.8;     // i hat den Wert 2
 i = 1.9;     // i hat den Wert 2
 i = 1.99;    // i hat den Wert 2
 i = 2.0;     // i hat den Wert 2

Wie kann man das machen?
Indem man zur Gleitkommazahl noch 0.5 dazuzählt, ehe die Kommastellen 
abgeschnitten werden

 i = 1.0 + 0.5 = 1.5;     // i hat den Wert 1
 i = 1.1 + 0.5 = 1.6;     // i hat den Wert 1
 i = 1.4 + 0.5 = 1.9;     // i hat den Wert 1
 i = 1.7 + 0.5 = 2.2;     // i hat den Wert 2
 i = 1.8 + 0.5 = 2.3;     // i hat den Wert 2
 i = 1.9 + 0.5 = 2.4;     // i hat den Wert 2
 i = 1.99 + 0.5 = 2.49;   // i hat den Wert 2
 i = 2.0 + 0.5 = 2.5;     // i hat den Wert 2

perfektes Ergebnis.

Nun ist es aber so, dass bei Integer Divisionen kein Kommaergebnis 
entsteht, sondern gleich die ganze Zahl. d.h aber auch, dass man nicht 
im Nachhinein 0.5 addieren kann

  i = 5 / 3 + 0.5;

5 / 3  ergibt 1 (und nicht 1.6666), 0.5 dazu ergibt 1.5, davon die 
Kommastellen abgeschnitten macht immer noch 1.

bringts also nicht. -> Ausweg: Die Rundungskorrektur von 0.5 muss vor 
die Division gezogen werden

  i = ( 5 + 0.5 * 3) / 3;
oder eben (da wir in den ganzen Zahlen bleiben wollen)
  i = ( 5 + 3 / 2 ) / 3

3/2 ergibt 1, 5 dazu macht 6, und erst dann durch 3 ergibt 2

mit dem Taschenrechner gerechnet: 5/3 ergibt 1.6666 und gerundet ist das 
nun mal 2 und nicht 1

Allgemeiner formuliert haben wir also:

  a/b   gerundet gerechnet, macht man so:
  i = ( a + b/2 ) / b;

Und jetzt vergleich mal mit

  ((F_CPU+BAUD*8)/(BAUD*16)

Na. Klingelts?

von Lord Z. (lordziu)


Lesenswert?

Könnt ihr diese Erklärung als Artikel einstellen? Finde ich super und 
sollte jeder wissen wie das geht.

Daumen hoch!

von remote1 (Gast)


Lesenswert?

Ich habs mal eben sinngemäß in den UART Artikel mit übernommen.

von Peter D. (peda)


Lesenswert?

Dieses Macro stammt wohl ursprünglich aus einem Assemblerprogramm.

Unter C kann man eine Konstantenrechnung ruhig in float machen und nach 
int casten:
1
#define UBRR_VAL (uint16_t)(F_CPU / 16.0 / BAUD - 0.5)


Peter

von Jörg G. (joergderxte)


Lesenswert?

> Dieses Macro stammt wohl ursprünglich aus einem Assemblerprogramm.

Nein, das sieht so aus, weil der Präprozessor nur in int rechnet, und 
man so mit #if den Baudratenfehler ausrechnen lassen kann, um dann auf 
den 2X-Mode umzustellen oder mit #error abzubrechen etc.

hth, Jörg

von Peter D. (peda)


Lesenswert?

Jörg G. schrieb:
> Nein, das sieht so aus, weil der Präprozessor nur in int rechnet, und
> man so mit #if den Baudratenfehler ausrechnen lassen kann, um dann auf
> den 2X-Mode umzustellen oder mit #error abzubrechen etc.

Stimmt, das hat mich auch schon oft geärgert, daß der Präprozessor kein 
float kann.
Ehe ich aber ständig auf float-Konstanten verzichte, verzichte ich 
lieber auf das "#if".


Peter

von Ulrich B. (richie)


Lesenswert?

Manno - dachte schon ich hätte irgendwo Aussetzer.
Hab seit 30 Jahren keinen Assembler mehr bemüht und wollte die 
Abweichung für einen 12Mhz Quarz berechnen.
Hatte die Anweisungen mal in Excel umgesetzt und geriet über die 
Ergebnisse ins Staunen. Selbst mit einem "Baudratenquarz" hatte ich da 
bei Baudraten über 19200 Abweichungen von über 10 Promille^^.
Mit
GANZZAHL(B1/(B2*16)-1)
erhält man den korrekten Wert für UBRVAL.
Lässt man das "GANZZAHL" (INT) weg, sieht man sofort die Abweichung.
Bei einem Baudratenquarz hat man dann auch keine Nachkommastellen in 
UBRVAL.

Naja, mein Algebra ist auch schon fast 40 Jahre alt und ich habe mehrere 
Stunden mit grübeln verbracht, bevor ich auf die Beiträge hier gestossen 
bin.
Vielen Dank für die ausführlichen Erklärungen.

Bis die Tage

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Dieses Runden führt leider nicht immer zum kleinsten Fehler.

Hatte mal nen kleinen JavaScript-Rechner genau dafür geschrieben:

http://www.gjlay.de/helferlein/avr-uart-rechner.html

von Peter D. (peda)


Lesenswert?

Johann L. schrieb:
> Dieses Runden führt leider nicht immer zum kleinsten Fehler.

Warum nicht?

Dann zeig mal ein Beispiel (Quarz, Baudrate), wo es nicht funktionieren 
soll.


Peter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:
> Johann L. schrieb:
>> Dieses Runden führt leider nicht immer zum kleinsten Fehler.
>
> Warum nicht?

Der Zusammenhang zwischen UBRR und der Baudrate ist nicht linear. Indem 
man 0.5 zur Fehlerminimierung aufaddiert macht man eine (implizite) 
Linearisierung der Beziehung zwischen UBRR und der Baudrate auf einem 
klenen Teilstück.

Grund ist also die Krümmung der Funktion Baudrate(UBRR)

In der Praxis dürfte das -- wenn überhaupt -- nur eine untergeordnete 
Rolle spielen.

Interessanter als die Rolle die es in der Praxis spielt finde ich aber 
sich über den Effekt klar zu werden.

von Falk B. (falk)


Lesenswert?

@  Johann L. (gjlayde) Benutzerseite

>Indem man 0.5 zur Fehlerminimierung aufaddiert macht man eine (implizite)
>Linearisierung der Beziehung zwischen UBRR und der Baudrate auf einem
>klenen Teilstück.

Keine Sekunde. Das ist schlicht ein Trick, um mathematisch exakt runden 
zu können, weil der C-Präprozessor sowie der AVR-Assembler im AVR-Studio 
nur abschneiden können, nicht runden.

>In der Praxis dürfte das -- wenn überhaupt -- nur eine untergeordnete
>Rolle spielen.

Im Fall der Fälle ist es entscheidend. Vor allem bei kleinen Werten für 
UBRR. Wenn der Präprozessor 7,9 auf 7 abschneidet, sind das 12% Fehler. 
Durch mathematisch/cleveres Runden auf 8 nur 1,2%. Und clevererweise 
berechnen die Makros in den Tutorials gleich noch den Fehler und warnen 
wenn nötig.

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Der_UART#UART_initialisieren

http://www.mikrocontroller.net/articles/AVR-Tutorial:_UART#UART_konfigurieren

MFG
Falk

von Justus S. (jussa)


Lesenswert?

Falk Brunner schrieb:
>>In der Praxis dürfte das -- wenn überhaupt -- nur eine untergeordnete
>>Rolle spielen.
>
> Im Fall der Fälle ist es entscheidend.

ich nehme an, er meint das in Bezug auf die Nichtlinearität, dass man 
diese vernachlässigen kann...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ja, ich bezog das auf die Nichtlinearität bzw. Krümmung.

Ein Rechenbeispiel:

f = 9.8304 MHz und baud = 250 kBaud

ubrr = f/16/baud - 1

Das gibt einen UBRR-Wert von 1.4576 der auf 1 gerundet wird. Die 
Baudraten sind nun:

UBRR=1 -> 307.2 kBaud
UBRR=2 -> 204.8 kBaud

d.h. die zweite Baudrate liegt dichter am Zielwert als die erste.

Der Grund ist, daß UBRR zum nächsten hin gerundet wird. Das ist nicht 
unbedingt das gleiche als die Baudrate zur nächstmöglichen zu runden. 
Was man haben will ist letzteres, tut in der Rechnung aber ersteres und 
sollte sich also überlegen, warum und wann man die Ersetzung machen 
kann/darf.

In der Praxis der Baudratenberechnung spielt der Effekt wie gesagt keine 
Rolle (Begründung ist Hausaufgabe ;-)), ich wollte aber trotzdem darauf 
hinweisen. Spart vielleicht bei ähnlichen Aufgaben "seltsame" Effekte.

von Peter D. (peda)


Lesenswert?

Johann L. schrieb:
> UBRR=1 -> 307.2 kBaud
> UBRR=2 -> 204.8 kBaud
>
> d.h. die zweite Baudrate liegt dichter am Zielwert als die erste.

Das ist egal, da beide völlig unbrauchbar sind (~20% Fehler).

Man möchte nur Fehler bis max 1..2% und dann ist die Funktion schon 
ausreichend linear.


Peter

von Simon K. (simon) Benutzerseite


Lesenswert?

Das könnte man sogar mit Präprozessor-#ifs hinkriegen. Bei der 
Rundungsproblematik gibt es ja prinzipiell nur zwei Werte, die man 
einfach beide gegen die eingestellte Baudrate checkt (in Sachen 
Abweichung).

von Steiny (Gast)


Angehängte Dateien:

Lesenswert?

@simon
Hab das jetzt mal als Präprozessormakros alles eingerichtet. Es werden 
der abgerundete und aufgerundete Wert verwendet, zu beiden der Fehler 
berechnet und der bessere Wert ausgewählt und in UBRR_VAL gespeichert.

Die Makros sind in einem kleinen Testprogramm eingebettet mit dem man 
die Funktion des Ganzen überprüfen kann.

Viele Grüße,
Steiny

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.