domingo, 24 de marzo de 2013

Enviar datos a la PC mediante USART

En este post voy a utilizar el puerto USART de mi PIC16F88 para enviar datos a la pc cada 500 ms. Utilizando el proyecto que prende o apaga un led ahora además voy a enviar a la pc el valor de una variable que se incrementa cada cierta cantidad de tiempo.

Proyectos relacionados:



Lo primero que tenemos que hacer es configurar el puerto USART de nuestro pic para que envíe datos a una determinada velocidad. La misma va a depender de la velocidad del clock que estemos usando.

En mi caso voy a utilizar un cristal de 20Mhz. En la documentación de nuestro pic veremos que combinaciones podemos utilizar:

Primero tenemos que elegir el valor del parámetro BRGH ( "0" Baja velocidad  -  "1" Alta velocidad)

Tabla 1 - Datasheet PIC16F88

Dependiendo del valor que elijamos en BRGH iremos a la tabla que corresponda. Según la recomendación del manual, es preferible elegir el valor "1" ya que en la mayoría de los casos da un menor error en los cálculos:

Tabla 2 - Datasheet PIC16F88

Ahora lo que tenemos que hacer es para un determinado valor de KBauds seleccionar el valor SPBRG que le corresponda.

Por ejemplo, para 20Mhz y 19200 Baud el valor de SPBRG es 64 con un error de +0.16 para BRGH=1, (Si hubiese elegido BRGH=0 el error sería de +1.72)

La configuración en C es la siguiente:

// Habilita la conexión serial
// Habilitar uart

TXSTAbits.TX9 = 0;  //  Transmisión de 8-bit
TXSTAbits.SYNC = 0; // Modo asincrónico
TXSTAbits.BRGH = 1; // BRGH en alta velocidad

RCSTAbits.RX9 = 0;  // Recepción de 8-bit
RCSTAbits.SPEN = 1; // Puerto serial habilitado
// (configura RB2/SDO/RX/DT y RB5/SS/TX/CK pins como puerto serial)

SPBRG = 64;       // 19200 baud

TXSTAbits.TXEN = 1; // Habilita la transmisión de datos
RCSTAbits.CREN = 1; // Habilita la recepción de datos
RCIE = 1;           // Habilita las interrupciones por recibo de datos

__delay_ms(80); // Delay que permite que se estabilice la configuración y las interrupciones antes de comenzar a trabajar



Aparte de esto debemos agregar el siguiente código para que se limpie la interrupción de recepción de datos:

/*
* Vector de interrupciones
*/
static void interrupt isr(void) {
    // Limpia la interrupción de recepción de data
    if(RCIF && RCIE) {
        RCIF = 0;
    }
}

Si no hacemos esto, lo que sucede es que no recibiremos nada en la consola de la PC.

El siguiente código es necesario para poder utilizar las funciones como printf, getch, putch, etc para escribir y leer variables tal cómo se haría en una aplicación de consola.

/*
* Rutina necesaria para que funcione correctamente el printf.
* Escribe un caracter en el puerto serial.
*/
void putch(unsigned char data) {
  /* output one byte */
  while(!TXIF)     /* set when register is empty */
    continue;
  TXREG = data;
}

/**
* Obtiene un caracter desde el puerto serial.
* @return
*/
unsigned char getch() {
     /* retrieve one byte */
     while(!RCIF)     /* set when register is not empty */
          continue;
     return RCREG;
}

/**
* Obtiene un caracter desde el puerto serial y lo retransmite.
* @return
*/
unsigned char getche(void) {
     unsigned char c;
     putch(c = getch());
     return c;
}

Ahora lo que nos resta es llenar el cuerpo de la aplicación:

int contador = 0;

// Bucle principal
while(true) {

     LED = 1;    // Setea en estado alto (High) el LED

     __delay_ms(500); // Se queda esperando 500 milisegundos

     printf("contador: %d\r\n", contador++);
     LED = 0;    // Setea en estado bajo (Low) el LED

     __delay_ms(500);

     printf("contador: %d\r\n", contador++);

}

Lo que hace este código es bastante fácil de entender. 

1) Crea una variable de tipo integer llamada "contador"

2) Enciende el LED cómo se mostró en los anteriores posts y espera 500ms para apagarlo.

3) El método printf recibe una cadena de texto + parámetros. 
  • la expresión "%d" indica que debe recibir un parámetro de tipo entero (número) para formatearlo como cadena de caracteres. 
  • El "\r\n" hace un Enter en la consola. 
  • contador++ incrementa la variable

Una vez que compilamos el proyecto y lo pasamos con el AN1310, seleccionamos la opción "Run application firmware" y veremos que aparecerá la información en pantalla:


Imagen 1 - An1310 recibiendo datos cada 500ms
En mi caso estoy enviando un contador bastante simple pero haciéndo algunos cambios en el código podemos informar el valor de alguna entrada. Por ejemplo, podríamos informar que se presionó un botón o que recibimos información de algún sensor.

Hoy no hay video porque estoy afónico!

El código completo lo encontrarán en mi repositorio en GitHub: PIC16F88_06_USART.

Saludos!

Fuente:
Datasheet PIC16F88

5 comentarios:

  1. Hola, la verdad que tu post en muy interesante y claro. Pero no entiendo lo de que las funciones getch y putch son necesarias para el printf. En si, printf no viene ya en la libreria stdio?

    ResponderEliminar
  2. Hola Abel. Gracias por comentar!. Te cuento que printf viene definido en la librería stdio pero internamente utiliza las funciones getch y putch que para los pics no está implemententada.
    Esto lo hacen para poder implementar la salida directamente en nuestro programa. Por ejemplo, esto es muy útil si tenemos un pic con más de un puerto serial y queremos que la salida estandar sea sobre uno en específico.

    Esta info la podés encontrar en la página 58 de la documentación de XC8:
    http://ww1.microchip.com/downloads/en/DeviceDoc/52053B.pdf

    Saludos!

    ResponderEliminar
  3. Gracias. Estuve leyendo dicho odf y ahi dice que se puede usar un printf para que envie a varios destinos usando una varible global pero no especifica nada mas. Sabes como se podria implementar??
    Gracias.-

    ResponderEliminar
  4. Para enviar a distintos destinos usando una variable global podría ser utilizando un "int" que indique cual es el dispositivo a escribir. Por ej.:
    static volatile int _printDevice;
    . . .
    // Antes de llamar a printf:
    _printDevice = 1;

    printf("Hola");

    En la implementación haría algo así:

    void putch(unsigned char data) {
    switch(_printDevice)
    case 1:
    // código para enviar a USART
    break;
    case 2:
    // código para enviar a otro dispositivo
    break;
    }

    ResponderEliminar
  5. Muchas gracias, me has dado una mano con varias dudas :)
    Sobre todo por que recien estoy empezando a programar. La verdad... me quito el sombrero.-

    ResponderEliminar

Related Posts Plugin for WordPress, Blogger...