mikrocontroller.net

Forum: Compiler & IDEs UART Baudrate clever runden


Autor: UART (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Uwe ... (uwegw)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Lord Ziu (lordziu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:

Bewertung
0 lesenswert
nicht lesenswert
Ich habs mal eben sinngemäß in den UART Artikel mit übernommen.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:

Bewertung
0 lesenswert
nicht 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

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Ulrich Basting (richie)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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-Tu...

http://www.mikrocontroller.net/articles/AVR-Tutori...

MFG
Falk

Autor: Justus Skorps (jussa)
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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).

Autor: Steiny (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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

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
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
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 bestätigst du, die Nutzungsbedingungen anzuerkennen.