Mikrocontroller und Digitale Elektronik
›
Thread
LPC23xx: UART möglichst genau einstellen
P
Peter
Gast
27.05.2009 17:59
#1277841
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 <==
P
Peter
Gast
28.05.2009 11:20
#1278559
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.
P
Peter
Gast
29.05.2009 12:05
#1279955
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 <=
M
Markus
Gast
01.06.2009 23:58
#1283415
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;
M
Markus
Gast
01.06.2009 23:59
#1283417
PS: Welche von beiden ist nun schneller?
FB
Falk B.
(falk)
02.06.2009 09:34
#1283546
@ 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
M
Markus
Gast
02.06.2009 10:11
#1283579
M
Markus
Gast
02.06.2009 10:13
#1283581
Ich hab die Berechnung aus der Excel Tabelle nachgebildet.
Welche von beiden nun genauer rechnet kann man ja mal testen.
P
Peter
Gast
02.06.2009 11:44
#1283734
> 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 ;
↑ Nach oben
← Zurück zu Mikrocontroller und Digitale Elektronik
Antwort schreiben