Datum:
Kann mir mal bitte jemand die folgenden defines erklären
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1) // clever runden #define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1))) // Reale Baudrate #define BAUD_ERROR ((BAUD_REAL*1000)/BAUD) |
Im Datenblatt steht doch immer

Wo kommt also das "+BAUD*8" her? Was ist daran clever gerundet?
Datum:
BAUD*8 ist genau die Hälfte von BAUD*16. Zum Ausgleichen von Rundungsungenauigkeiten kann man 0.5 addieren - genau das macht der Code.
Datum:
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.
Datum:
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?
Datum:
Könnt ihr diese Erklärung als Artikel einstellen? Finde ich super und sollte jeder wissen wie das geht. Daumen hoch!
Datum:
Ich habs mal eben sinngemäß in den UART Artikel mit übernommen.
Datum:
Dieses Macro stammt wohl ursprünglich aus einem Assemblerprogramm. Unter C kann man eine Konstantenrechnung ruhig in float machen und nach int casten:
#define UBRR_VAL (uint16_t)(F_CPU / 16.0 / BAUD - 0.5) |
Peter
Datum:
> 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
Datum:
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
Datum:
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
Datum:
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
Datum:
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
Datum:
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.
Datum:
@ 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-Tu... http://www.mikrocontroller.net/articles/AVR-Tutori... MFG Falk
Datum:
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...
Datum:
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.
Datum:
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
Datum:
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).
Datum:
Angehängte Dateien:@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