Hallo! Ich möchte die UART-Teiler UDL, DivAddVal und Mulval so einstellen, daß die Baudrate eine möglichste geringe Abweichung hat. Und zwar so, daß man nur die Baudrate angeben muss. (Die Baudrate soll variabel sein). Meine Taktfrequenz ist 72 MHz. Hat vielleicht jemand eine Näherungsformel parat? Danke im voraus, ==> Peter <==
Ok, habe mir schon selbst was gebaut. Der Algorithmus sucht mit 221 Schleifendurchläufen nach den optimalen Einstellungen. Im Schnitt ist der relative Fehler mit diesen gefundenen Settings nur etwa 0.1%. Leider arbeitet der Code mit Fließkommazahlen. Wenn ich ihn "Mikrocontroller-tauglich" gemacht habe, stelle ich ihn mal hier rein.
So, falls der Code jemanden interessiert:
1 | struct uartvalues |
2 | {
|
3 | unsigned long br; // in: baudrate |
4 | unsigned long pclk; // in: clock frequency |
5 | unsigned long fr; // fraction (used internally) |
6 | unsigned long dl; // out: calculated dl value (that is 256*dlm + dll) |
7 | unsigned char divaddval; // out: calculated divaddval |
8 | unsigned char mulval; // out: calculated mulval |
9 | };
|
10 | |
11 | // does all the stuff
|
12 | // input: pointer to uartvalues structure, pclk = UART clock, br = baud rate
|
13 | // output: values in *uart: dl,divaddval, mulval
|
14 | int calc_uart (struct uartvalues *uart, unsigned long pclk, unsigned long br) |
15 | {
|
16 | static unsigned long fr_est[] = {15000,14000,13000,12000,16000,17000,18000,19000,11000}; |
17 | int fr_est_idx = 0; |
18 | unsigned char mulval; |
19 | unsigned char divaddval; |
20 | unsigned long diff = (unsigned long)~0; |
21 | |
22 | // init
|
23 | uart->pclk = pclk; |
24 | uart->br = br; |
25 | |
26 | // first try. shifted into 'long' range (*10000)
|
27 | uart->dl = uart->pclk*50/uart->br*12 + uart->pclk*50/uart->br/2; |
28 | |
29 | // Do we have an integer? -> No further calculations needed
|
30 | if (uart->dl%10000 == 0) |
31 | {
|
32 | uart->dl = uart->dl/10000; |
33 | uart->divaddval = 0; |
34 | uart->mulval = 1; |
35 | return 0; |
36 | }
|
37 | |
38 | try_next_fr:
|
39 | uart->fr = fr_est[fr_est_idx]; |
40 | |
41 | // dl as integer * 1, but ...
|
42 | // 11000 <= uart->fr <= 19000
|
43 | uart->dl = (uart->pclk*50/uart->br*12 + uart->pclk*50/uart->br/2)/uart->fr; |
44 | |
45 | // calculate fraction * 10000 to keep as most digits as possible
|
46 | uart->fr = (uart->pclk*50/uart->br*12 + uart->pclk*50/uart->br/2)/uart->dl; |
47 | |
48 | // fraction doesn't fit into range 1.1...1.9?
|
49 | if (uart->fr < 11000 || uart->fr > 19000) |
50 | {
|
51 | fr_est_idx++; |
52 | // leave with error if it's impossible to calculate
|
53 | if (fr_est_idx == sizeof(fr_est)/sizeof(*fr_est)) |
54 | return -1; |
55 | goto try_next_fr; |
56 | }
|
57 | |
58 | // finally, seek the (hopefully) best mulval/divaddval combination
|
59 | // must satisfy: 0<mulval<=15 ^ 0<=divaddval<15 ^ divaddval<mulval
|
60 | // always needs 224 iterations
|
61 | for (mulval = 1; mulval<=15; mulval++) |
62 | {
|
63 | for (divaddval = 0; divaddval<15; divaddval++) |
64 | {
|
65 | unsigned long res = 10000 + 10000*divaddval/mulval; |
66 | unsigned long d; |
67 | // calculate absolute difference
|
68 | if (res < uart->fr) |
69 | d = uart->fr - res; |
70 | else
|
71 | d = res - uart->fr; |
72 | // record best match
|
73 | if (d < diff && divaddval < mulval) |
74 | {
|
75 | diff = d; |
76 | uart->mulval = mulval; |
77 | uart->divaddval = divaddval; |
78 | }
|
79 | }
|
80 | }
|
81 | |
82 | return 0; // ok |
83 | }
|
Benutzen tue ich es so:
1 | /* Set baud rate divisors */
|
2 | struct uartvalues uart; |
3 | calc_uart (&uart, F_PERIPHERALS, settings->baudrate); |
4 | U1DLM = uart.dl >> 8; |
5 | U1DLL = uart.dl & 0xff; |
6 | if (U1DLL < 2) |
7 | U1DLL = 2; |
8 | U1FDR = uart.mulval<<4 | uart.divaddval; |
Scheint ganz gut zu klappen. Der berechnete Fehler bei allen getesteten Baudraten von 300 baud ... 921600 baud ist kleiner als 1%. Wenn ihr Bugs findet, bitte schreibt es hier... => Peter <=
Hab auch sowas geschrieben... Gruss Markus. ---------------------------------------------------------------
1 | // Variablen werden in der Funktion Seriell_CalcBaudrate() beschreiben
|
2 | float fSeriellAbw; |
3 | int iSeriellDL, iSeriellFDR; |
4 | |
5 | // Diese Funktion ermittelt die mögliche Baudrate des Prozessors
|
6 | // Rückgabe: Aktuelle Baudrate
|
7 | int Seriell_CalcBaudrate(int iBaud) { |
8 | int iBestDL, iBestDiv, iBestMul; |
9 | int iDivV, iMulV, iDL; |
10 | float fBestAbw, fAbw1, fAbw2, fDiv, fMul, fMulDiv, fBaudAbw; |
11 | volatile int iRegister= PCLKSEL0; |
12 | if (iBaud <= 0) |
13 | return 0; |
14 | fBestAbw = 1000; |
15 | iBestDL = iBestDiv = iBestMul = 0; |
16 | for (iDivV = 0; iDivV < 15; iDivV++) { |
17 | fDiv = (float)iDivV; |
18 | for (iMulV = iDivV + 1; iMulV < 16; iMulV++) { |
19 | fMul = (float)iMulV; |
20 | fMulDiv = (1 + (fDiv / fMul)) * 16.0; |
21 | iDL = Fpclk / (int)((float)iBaud * fMulDiv); // Divisor DL ermitteln |
22 | if (iDL >= 70000) |
23 | break; |
24 | if (iDL > 65535) // Begrenzung auch maximale Möglichkeit des Registers |
25 | iDL = 65535; |
26 | fBaudAbw = (((float)Fpclk / ((float)iDL * fMulDiv))); |
27 | fAbw1 = (fBaudAbw * 100) / (float)iBaud - 100.0; // Gegenrechnung Abweichung in % |
28 | if (fAbw1 < 0) // Abweichung nur Positiv |
29 | fAbw1 *= -1; |
30 | if (iDL < 65535) // Ein Count erhöhen und nochmal rechnen, wenn nicht schon max-Grenze |
31 | {
|
32 | fBaudAbw = (((float)Fpclk / ((float)(iDL + 1) * fMulDiv))); |
33 | fAbw2 = (fBaudAbw * 100) / (float)iBaud - 100.0; // Gegenrechnung Abweichung in % |
34 | if (fAbw2 < 0) |
35 | fAbw2 *= -1; |
36 | if (fAbw2 < fAbw1) // Zweite Rechnung ist besser |
37 | {
|
38 | fAbw1 = fAbw2; |
39 | iDL++; |
40 | }
|
41 | }
|
42 | if (fAbw1 < fBestAbw) // Neue Abweichung ist kleiner, Ergebnisse merken |
43 | {
|
44 | fBestAbw = fAbw1; |
45 | iBestDL = iDL; |
46 | iBestDiv = iDivV; |
47 | iBestMul = iMulV; |
48 | if (fBestAbw == 0) // Optimales Ergebnis bereits gefunden |
49 | break; |
50 | }
|
51 | if (!iDivV) // Schleife bei iDivV = 0 nur ein mal berechnen |
52 | break; |
53 | }
|
54 | if (fBestAbw == 0) // Optimales Ergebnis bereits gefunden |
55 | break; |
56 | }
|
57 | iSeriellDL = iBestDL; |
58 | iSeriellFDR = (iBestMul << 4) + iBestDiv; |
59 | fSeriellAbw = fBestAbw; |
60 | iRegister++; |
61 | if (fBestAbw == 0) // Keine Abweichung |
62 | return iBaud; |
63 | fDiv = (float)iBestDiv; |
64 | fMul = (float)iBestMul; |
65 | fMulDiv = 1 + (fDiv / fMul); |
66 | // In den Variablen iSeriellDL und iSeriellFDR ist die Einstellung des Baudraten-Teilers
|
67 | return (int)((float)Fpclk / (16.0 * (float)iBestDL * fMulDiv)); |
68 | }
|
69 | |
70 | |
71 | Der Aufruf: |
72 | (*(volatile unsigned int *)(UartBase + 0x03)) |= 0x80; // U0LCR |= 0x80; // Baudrate errechnen |
73 | Seriell_CalcBaudrate(iBaudrate[Port]); |
74 | (*(volatile unsigned int *)(UartBase + 0x00)) = iSeriellDL & 0xFF; // U0DLL = iSeriellDL & 0xFF; |
75 | (*(volatile unsigned int *)(UartBase + 0x01)) = iSeriellDL >> 8; // U0DLM = iSeriellDL >> 8; |
76 | (*(volatile unsigned int *)(UartBase + 0x0A)) = iSeriellFDR; // U0FDR = iSeriellFDR; |
@ Markus (Gast) >PS: Welche von beiden ist nun schneller? Ich behaupte mal kess, dass eine Berechnung mit Floatvariablen a)langsamer und b) unnötig ist. Festkommaarithmetik tuts locker, wenn man weiss was man tut. MfG Falk
Ich hab die Berechnung aus der Excel Tabelle nachgebildet. Welche von beiden nun genauer rechnet kann man ja mal testen.
> PS: Welche von beiden ist nun schneller?
Geschwindigkeit ist doch egal, weil die Berechnung nur einmal beim
Initialisieren nötig ist. Ich habe bewusst auf Fließkomma-Arithmetik
verzichtet, damit die Float-Library nicht dazugelinkt werden muß.
Übrigens ist noch kleiner Fehler drin. Der Ausdruck:
1 | uart->dl = (uart->pclk*50/uart->br*12 + uart->pclk*50/uart->br/2)/uart->fr; |
kann 0 werden. Für den Fall gibt es keine Lösung. Daher muß noch folgendes dahinter:
1 | if (uart->dl == 0) |
2 | return -1; |
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.