Saltar al contenido
Apuntes GITT 4º

Escribe al menos dos letras.

    SEDAUARTPEI1 · 19:00 en 20 días

    Comunicación serie

    UART

    Trama asíncrona, velocidad con DLM, DLL y el divisor fraccionario, e interrupciones de Tx y Rx

    Idea clave. La UART manda bytes de uno en uno por un solo hilo (TxD) y los recibe por otro (RxD), sin reloj compartido. Por eso los dos extremos tienen que acordar de antemano el formato y la velocidad. El LPC1768 tiene cuatro (UART0–UART3). En la Mini-DK2, la UART0 (P0.2 y P0.3) va a un conversor USB-serie: es la que se usa para hablar con el PC y la que usa el bootloader para grabar la flash.

    La trama

    En reposo la línea está a 1. Cada byte va así:

    El formato más usado es 8N1 (8 datos, sin paridad, 1 stop): 10 bits por byte. Cada bit dura tbit=1/Vt_{bit} = 1/V, y la velocidad VV se da en bit/s (baudios): 9600, 19 200, 115 200…

    Para enlazar dos equipos, el TxD de uno va al RxD del otro (cruzados) y las masas unidas. Así se conectan un GPS, un módulo GSM o Bluetooth, un LiDAR…

    La configuración

    1. Pines con PINSEL: P0.2 → TXD0 y P0.3 → RXD0 (función 01).
    2. Alimentación con PCONP (UART0 y UART1 ya están encendidas tras el reset; UART2 y UART3 no).
    3. Formato en LCR.
    4. Velocidad con DLL, DLM y FDR (con DLAB = 1).
    5. Interrupciones en IER y en el NVIC.
    U0LCR · formato de la trama
    BitsCampoQué hace
    1:0Word Length Select00 = 5 bits … 11 = 8 bits
    2Stop Bit Select0 = 1 bit de stop; 1 = 2 bits
    3Parity Enable1 = con paridad
    5:4Parity Select00 = impar, 01 = par, 10 = forzada a 1, 11 = forzada a 0
    7DLAB1 = acceso a DLL y DLM; 0 = acceso a RBR, THR e IER

    La velocidad

    El reloj de la UART se divide entre 16 (cada bit se muestrea 16 veces) y entre el divisor DL=256⋅DLM+DLLDL = 256\cdot DLM + DLL (16 bits). El divisor fraccionario (FDR) afina aún más:

    V=FPCLK16⋅(256⋅DLM+DLL)⋅(1+DivAddValMulVal)V = \frac{F_{PCLK}}{16\cdot(256\cdot DLM + DLL)\cdot\left(1 + \frac{DivAddVal}{MulVal}\right)}

    En la mayoría de los casos basta con DL (tras el reset, DivAddVal = 0 y el factor vale 1):

    DL=FPCLK16⋅V redondeadoDLM=DL/256DLL=DL % 256DL = \frac{F_{PCLK}}{16\cdot V} \ \text{redondeado} \qquad DLM = DL / 256 \qquad DLL = DL \,\%\, 256

    Por ejemplo, con FPCLK=25F_{PCLK} = 25 MHz y 19 200 bit/s: DL=81,38→81DL = 81{,}38 \to 81 y la velocidad real es 19 290 bit/s (0,47 % de error, sin problema). Con 115 200 bit/s el redondeo da DL=14DL = 14 y un error del 3,1 %, ya demasiado; ahí entra el fraccionario.

    U0FDR · divisor fraccionario
    BitsCampoQué hace
    3:0DIVADDVAL0 a 14. Con 0, el divisor fraccionario no actúa (valor tras el reset)
    7:4MULVAL1 a 15, y mayor que DIVADDVAL (vale 1 tras el reset)

    La calculadora prueba todas las combinaciones válidas de MulVal y DivAddVal y se queda con la de menor error:

    Para resolver

    Calculadora de baudios de la UART

    Desarrollo

      Con V = 115200 bit/s y FPCLK = 25 MHz: DLM = 0, DLL = 10, DivAddVal = 5, MulVal = 14 (error -0,059 %).

      Desarrollo

      1. Divisor sin fraccionario (DivAddVal = 0):

      2. Redondear DL_est da demasiado error, así que se usa el fraccionario. Con DivAddVal = 5 y MulVal = 14:

      3. Se reparte en dos registros de 8 bits:

      4. Velocidad real y error (-0,059 %):

      Transmitir y recibir

      Hay dos registros de datos de 8 bits en la misma dirección: THR (lo que se escribe se envía) y RBR (lo que se lee es lo recibido).

      U0IER · qué interrupciones se habilitan
      BitsCampoQué hace
      0RBR Interrupt Enable1 = interrumpir al recibir un dato (RDA)
      1THRE Interrupt Enable1 = interrumpir cuando THR queda vacío
      2RX Line Status1 = interrumpir con errores de recepción
      U0IIR · de dónde viene la interrupción
      BitsCampoQué hace
      0IntStatus0 = hay una interrupción pendiente (activo a nivel bajo)
      3:1IntId011 = errores de línea, 010 = RDA, 110 = time-out de la FIFO, 001 = THRE
      void uart0_init(void) {
        LPC_PINCON->PINSEL0 |= (1 << 4) | (1 << 6);  // P0.2 = TXD0, P0.3 = RXD0
        LPC_UART0->LCR = (1 << 7) | 0x03;            // DLAB = 1; 8 bits, sin paridad, 1 stop
        LPC_UART0->DLL = 81;                         // 19 200 bit/s con F_PCLK = 25 MHz
        LPC_UART0->DLM = 0;
        LPC_UART0->LCR = 0x03;                       // DLAB = 0
        LPC_UART0->IER = (1 << 0) | (1 << 1);        // RDA y THRE
        NVIC_EnableIRQ(UART0_IRQn);
      }

      Mandar una cadena con interrupciones

      El primer carácter se escribe fuera de la ISR; cada interrupción THRE manda el siguiente, hasta el '\0'. Mientras tanto, el programa principal sigue con lo suyo.

      char *ptr_tx;                 // siguiente carácter que hay que mandar
      char tx_completa;
      char buffer[32];              // línea recibida (RDA); 32 es un tamaño de ejemplo
      char *ptr_rx = buffer;        // dónde se guarda el siguiente byte recibido
      volatile char rx_completa;    // la ISR lo pone a 1 cuando llega '\r'
      
      void tx_cadena_UART0(char *cadena) {
        ptr_tx = cadena;
        tx_completa = 0;
        LPC_UART0->THR = *ptr_tx++;              // el primero, a mano
      }
      
      void UART0_IRQHandler(void) {
        switch ((LPC_UART0->IIR >> 1) & 0x07) {  // leer IIR borra la THRE
          case 2:                                // RDA: ha llegado un byte
            *ptr_rx = LPC_UART0->RBR;            // leer RBR borra la RDA
            if (*ptr_rx == '\r') {               // fin de línea: cadena completa
              *ptr_rx = '\0';
              rx_completa = 1;
              ptr_rx = buffer;
            } else {
              ptr_rx++;
            }
            break;
          case 1:                                // THRE: cabe el siguiente
            if (*ptr_tx) LPC_UART0->THR = *ptr_tx++;
            else tx_completa = 1;
            break;
        }
      }

      La cadena tiene que seguir existiendo hasta que acabe de enviarse (una variable global, no un array local de una función que ya ha terminado). Y en la recepción conviene comprobar que ptr_rx no se sale de buffer.

      Práctica 4 (UART: monitorización remota) · conviene dominarlo

      Cuando lo que llega no son líneas de texto sino tramas con cabecera, longitud y checksum, la ISR de recepción se convierte en una máquina de estados: cada byte hace avanzar un estado. Se ve en el tema de máquinas de estados.

      Práctica 5 (UART: lectura del LiDAR) · conviene dominarlo

      Preguntas de examen

      Configura la UART0 a 9600 bit/s, 8N1, con F_PCLK = 25 MHz y sin divisor fraccionario. ¿Qué error tiene?

      DL=25⋅106/(16⋅9600)=162,76→163DL = 25\cdot 10^6/(16\cdot 9600) = 162{,}76 \to 163: DLM = 0, DLL = 163. V=25⋅106/(16⋅163)≈9586V = 25\cdot 10^6/(16\cdot 163) \approx 9586 bit/s, un −0,15 %. LCR = 0x83, DLL, DLM, LCR = 0x03. (Respuesta redactada a partir de los apuntes.)

      ¿Por qué se manda el primer carácter fuera de la ISR?

      Porque la interrupción THRE salta cuando THR pasa a estar vacío. La interrupción THRE solo se produce después de que THR haya recibido algún dato y lo haya pasado al registro de desplazamiento: mientras no se escriba en THR, no hay nada que se vacíe y no salta ninguna. Por eso hay que escribir el primer carácter a mano. Una vez hecho, cada THRE posterior encadena el siguiente. (Respuesta redactada a partir de los apuntes.)

      ¿Qué error máximo de velocidad aguanta una UART con 8N1?

      El receptor se sincroniza en el flanco del start y muestrea cada bit hacia su centro. Hasta el bit de stop pasan unos 9,5 bits, y el error acumulado no puede llegar a medio bit: en total, menos de un 5 % entre los dos extremos (en la práctica se busca menos del 2 %). (Respuesta redactada: es la regla general de cualquier UART.)

      Material del curso usado: Apuntes de Miguel · Resumen de interrupciones del LPC1768 (diapositivas de clase) · UM10360 (manual del LPC17xx), cap. 14.