www.mikrocontroller.net

Forum: GCC UART Baudrate clever runden


Autor: UART (Gast)
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
\mathrm{UBRR} = \frac{\mathrm{F_{CPU}}}{\mathrm{Baudrate} \cdot 16} - 1

Wo kommt also das "+BAUD*8" her?
Was ist daran clever gerundet?
Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
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.
Autor: Uwe ... (uwegw)
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.
Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
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?
Autor: Lord Ziu (lordziu)
Datum:

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

Daumen hoch!
Autor: remote1 (Gast)
Datum:

Ich habs mal eben sinngemäß in den UART Artikel mit übernommen.
Autor: Peter Dannegger (peda)
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
Autor: Jörg G. (joergderxte)
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
Autor: Peter Dannegger (peda)
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
Autor: Ulrich Basting (richie)
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
Autor: Johann L. (gjlayde) Benutzerseite
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
Autor: Peter Dannegger (peda)
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
Autor: Johann L. (gjlayde) Benutzerseite
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.
Autor: Falk Brunner (falk)
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
Autor: Justus Skorps (jussa)
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...
Autor: Johann L. (gjlayde) Benutzerseite
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.
Autor: Peter Dannegger (peda)
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
Autor: Simon K. (simon) Benutzerseite
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).
Autor: Steiny (Gast)
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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel




Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder GIF-Format hochladen.
Siehe Bildformate
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken erkennst du die Nutzungsbedingungen an.

webmaster@mikrocontroller.netImpressumNutzungsbedingungenWerbung auf Mikrocontroller.net