www.mikrocontroller.net

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


Autor: Peter (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 <==

Autor: Peter (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Peter (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
So, falls der Code jemanden interessiert:
struct uartvalues
{
  unsigned long br;          // in: baudrate
  unsigned long pclk;        // in: clock frequency
  unsigned long fr;          // fraction (used internally)
  unsigned long dl;          // out: calculated dl value (that is 256*dlm + dll)
  unsigned char divaddval;  // out: calculated divaddval
  unsigned char mulval;     // out: calculated mulval
};

// does all the stuff
// input: pointer to uartvalues structure, pclk = UART clock, br = baud rate
// output: values in *uart: dl,divaddval, mulval
int calc_uart (struct uartvalues *uart, unsigned long pclk, unsigned long br)
{
  static unsigned long fr_est[] = {15000,14000,13000,12000,16000,17000,18000,19000,11000};
  int fr_est_idx = 0;
  unsigned char mulval;
  unsigned char divaddval;
  unsigned long diff = (unsigned long)~0;

  // init
  uart->pclk = pclk;
  uart->br = br;

  // first try. shifted into 'long' range (*10000)
  uart->dl = uart->pclk*50/uart->br*12 + uart->pclk*50/uart->br/2;
  
  // Do we have an integer? -> No further calculations needed
  if (uart->dl%10000 == 0)
  {
    uart->dl = uart->dl/10000;
    uart->divaddval = 0;
    uart->mulval = 1;
    return 0;
  }
  
try_next_fr:
  uart->fr = fr_est[fr_est_idx];
  
  // dl as integer * 1, but ...
  // 11000 <= uart->fr <= 19000 
  uart->dl = (uart->pclk*50/uart->br*12 + uart->pclk*50/uart->br/2)/uart->fr;

  // calculate fraction * 10000 to keep as most digits as possible
  uart->fr = (uart->pclk*50/uart->br*12 + uart->pclk*50/uart->br/2)/uart->dl;

  // fraction doesn't fit into range 1.1...1.9?
  if (uart->fr < 11000 || uart->fr > 19000)
  {
    fr_est_idx++;
    // leave with error if it's impossible to calculate 
    if (fr_est_idx == sizeof(fr_est)/sizeof(*fr_est))  
      return -1;
    goto try_next_fr;
  }
  
  // finally, seek the (hopefully) best mulval/divaddval combination 
  // must satisfy: 0<mulval<=15 ^ 0<=divaddval<15 ^ divaddval<mulval
  // always needs 224 iterations
  for (mulval = 1; mulval<=15; mulval++)
  {
    for (divaddval = 0; divaddval<15; divaddval++)
    {
      unsigned long res = 10000 + 10000*divaddval/mulval;
      unsigned long d;
      // calculate absolute difference
      if (res < uart->fr)
        d = uart->fr - res;
      else
        d = res - uart->fr;
      // record best match
      if (d < diff && divaddval < mulval)
      {
        diff = d;
        uart->mulval = mulval;
        uart->divaddval = divaddval;
      }
    }
  }

  return 0; // ok
}

Benutzen tue ich es so:
  /* Set baud rate divisors */
  struct uartvalues uart;
  calc_uart (&uart, F_PERIPHERALS, settings->baudrate);
  U1DLM = uart.dl >> 8;
  U1DLL = uart.dl & 0xff;
  if (U1DLL < 2)
    U1DLL = 2;
  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 <=

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hab auch sowas geschrieben...

Gruss Markus.
---------------------------------------------------------------
// Variablen werden in der Funktion Seriell_CalcBaudrate() beschreiben
float fSeriellAbw;
int iSeriellDL, iSeriellFDR;

// Diese Funktion ermittelt die mögliche Baudrate des Prozessors
// Rückgabe: Aktuelle Baudrate
int Seriell_CalcBaudrate(int iBaud) {
  int iBestDL, iBestDiv, iBestMul;
  int iDivV, iMulV, iDL;
  float fBestAbw, fAbw1, fAbw2, fDiv, fMul, fMulDiv, fBaudAbw;
  volatile int iRegister= PCLKSEL0;
  if (iBaud <= 0)
    return 0;
  fBestAbw = 1000;
  iBestDL = iBestDiv = iBestMul = 0;
  for (iDivV = 0; iDivV < 15; iDivV++) {
    fDiv = (float)iDivV;
    for (iMulV = iDivV + 1; iMulV < 16; iMulV++) {
      fMul = (float)iMulV;
      fMulDiv = (1 + (fDiv / fMul)) * 16.0;
      iDL = Fpclk / (int)((float)iBaud * fMulDiv); // Divisor DL ermitteln
      if (iDL >= 70000)
        break;
      if (iDL > 65535) // Begrenzung auch maximale Möglichkeit des Registers
        iDL = 65535;
      fBaudAbw = (((float)Fpclk / ((float)iDL * fMulDiv)));
      fAbw1 = (fBaudAbw * 100) / (float)iBaud - 100.0; // Gegenrechnung Abweichung in %
      if (fAbw1 < 0) // Abweichung nur Positiv
        fAbw1 *= -1;
      if (iDL < 65535) // Ein Count erhöhen und nochmal rechnen, wenn nicht schon max-Grenze
      {
        fBaudAbw = (((float)Fpclk / ((float)(iDL + 1) * fMulDiv)));
        fAbw2 = (fBaudAbw * 100) / (float)iBaud - 100.0; // Gegenrechnung Abweichung in %
        if (fAbw2 < 0)
          fAbw2 *= -1;
        if (fAbw2 < fAbw1) // Zweite Rechnung ist besser
        {
          fAbw1 = fAbw2;
          iDL++;
        }
      }
      if (fAbw1 < fBestAbw) // Neue Abweichung ist kleiner, Ergebnisse merken
      {
        fBestAbw = fAbw1;
        iBestDL = iDL;
        iBestDiv = iDivV;
        iBestMul = iMulV;
        if (fBestAbw == 0) // Optimales Ergebnis bereits gefunden
          break;
      }
      if (!iDivV) // Schleife bei iDivV = 0 nur ein mal berechnen 
        break;
    }
    if (fBestAbw == 0) // Optimales Ergebnis bereits gefunden
      break;
  }
  iSeriellDL = iBestDL;
  iSeriellFDR = (iBestMul << 4) + iBestDiv;
  fSeriellAbw = fBestAbw;
  iRegister++;
  if (fBestAbw == 0) // Keine Abweichung
    return iBaud;
  fDiv = (float)iBestDiv;
  fMul = (float)iBestMul;
  fMulDiv = 1 + (fDiv / fMul);
  // In den Variablen iSeriellDL und iSeriellFDR ist die Einstellung des Baudraten-Teilers
  return (int)((float)Fpclk / (16.0 * (float)iBestDL * fMulDiv));
}


Der Aufruf:
  (*(volatile unsigned int *)(UartBase + 0x03)) |= 0x80; // U0LCR |= 0x80;  // Baudrate errechnen
  Seriell_CalcBaudrate(iBaudrate[Port]);
  (*(volatile unsigned int *)(UartBase + 0x00)) = iSeriellDL & 0xFF; // U0DLL = iSeriellDL & 0xFF;
  (*(volatile unsigned int *)(UartBase + 0x01)) = iSeriellDL >> 8; // U0DLM = iSeriellDL >> 8;
  (*(volatile unsigned int *)(UartBase + 0x0A)) = iSeriellFDR; // U0FDR = iSeriellFDR;


Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
PS: Welche von beiden ist nun schneller?

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja, stimmt...

Autor: Markus (Gast)
Datum:

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

Autor: Peter (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
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:
if (uart->dl == 0)
  return -1;

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.