LPC23xx: UART möglichst genau einstellen

Gast #1277841
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 <==
Gast #1278559
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.
Gast #1279955
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 <=
Gast #1283415
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;
Gast #1283734
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;

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