UART Baudrate clever runden

Gast #1631916
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?
#1631932
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.
#1631940
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?
#1633195
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
#1765703
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
Persönliche Seite #1766359
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.
#1766401
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
Persönliche Seite #1766510
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.
Gast #1979652
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
Angehängte Dateien:

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren