Forum: Mikrocontroller und Digitale Elektronik LPC23xx: UART möglichst genau einstellen


von Peter (Gast)


Lesenswert?

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 <==

von Peter (Gast)


Lesenswert?

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.

von Peter (Gast)


Lesenswert?

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 <=

von Markus (Gast)


Lesenswert?

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;

von Markus (Gast)


Lesenswert?

PS: Welche von beiden ist nun schneller?

von Falk B. (falk)


Lesenswert?

@  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

von Markus (Gast)


Lesenswert?

Ja, stimmt...

von Markus (Gast)


Lesenswert?

Ich hab die Berechnung aus der Excel Tabelle nachgebildet.
Welche von beiden nun genauer rechnet kann man ja mal testen.

von Peter (Gast)


Lesenswert?

> 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
Noch kein Account? Hier anmelden.