Enlace de comunicación de 16 estados

Este es un enlace muy sencillo que permitirá realizar una comunicación simple entre dos microcontroladores mediante el la interfaz UART del microcontrolador.
En un microcontrolador tendremos 16 entradas y una salida UART, en el otro microcontrolador tendremos una entrada UART y 16 salidas. De esta manera podremos controlar 16 salidas a distancia. 
Si bien podríamos realizar el control de otra manera, en lugar de 16 entradas, podríamos usar una terminal serie, un terminal bluetooth desde el celular, botones, etc... Esta es la aplicación mas sencilla que servirá de base para otros proyectos, como podría ser el enlace vía bluetooth que es bastante tentador para encender y apagar cosas a distancia.
Tendremos que abordar dos programas, uno para el transmisor y otro para el receptor.
En este proyecto lo mostrare solamente en simulación ya que no tengo MCU con 16 salidas, por el momento..., pero el firmware esta realizado de forma simple para que se puedan emplear módulos inalámbricos UHF ASK de los que se venden en el mercado.

La Trama:
+----------+----------+----------+----------+
|  Header  |       Payload       | Checksum |
+----------+----------+----------+----------+
|  Header  |  Valor1  |  Valor2  | Checksum |
+----------+----------+----------+----------+
|  1 byte  |       2 byte        |  1 byte  |
+----------+----------+----------+----------+

El Transmisor:
Para este programa realizaremos primero la lectura del puerto A y del puerto B, quedando así las 16 entradas que mencionamos anteriormente, luego de leer estos dos puertos, moveremos sus valores en dos variables.
Para la trama de transmisión estaremos realizando una trama muy básica pero eficiente, esta trama consta de un preámbulo, una información útil y una verificación, también conocido como Header, Payload y Checksum. Este es el método mas sencillo de realizar una trama completamente insegura pero si no nos importa la seguridad no sera un problema.
Entonces, tendremos un Header que sera un numero que enviaremos siempre como un ID, en este caso utilice el numero 200, pero podría ser cualquier otro entre 0 y 255. Luego el payload sera nuestra información útil, donde pondremos dos variables de 8 bit, y por ultimo el checksum sera la suma de estas.

Por Ejemplo:
Header: 200
Valor1: 120
Valor2 34
Checksum: 120+34=154

+----------+----------+----------+----------+
|  Header  |       Payload       | Checksum |
+----------+----------+----------+----------+
|   200    |   120    |    34    |    154   |
+----------+----------+----------+----------+

De esta manera vamos a enviar los 4 bytes por puerto serie a 600bps (velocidad empleada para que la señal pueda ser enviada correctamente por los módulos UHF ASK (ver mas en teorema de Shanon Hartley, Nyquist, Relación Señal/Ruido, etc...).

Luego de crear nuestra trama de datos, esta podría ser enviada por puerto serie, pero no la enviaremos de forma deliberada, la enviaremos solo cuando exista algún cambio en alguno de los 16 estados de entrada, sino no enviaremos nada, lo cual se hace para ahorrar energía y para que no se este enviando todo el tiempo, claro que esto requiere de una comunicación con baja ruido, ya que si nosotros enviamos el dato y no llega bien en el receptor, nunca se realizara un re-intento, no tenemos confirmación en una transmisión simplex como esta. Lo ideal seria realizar algo de redundancia para asegurarnos de que el dato ha llegado correctamente, pero esto lo dejare para otra nota con el fin de no complicar mucho esta.

  1. //*********************************************************************************************************
  2. // Programa para enviar 16 estados por UART con CRC
  3. // El enlace se realiza mediante una comunicación ASK OOK UHF sobre UART Invertido a 600bps
  4. //*********************************************************************************************************
  5. #include <16F883.h>                                  //Biblioteca del microcontrolador
  6. #fuses NOMCLR, NOWDT, INTRC_IO                       //Configuración de fuses
  7. #use delay(int=4000000)                              //Configuración del clock interno a 4MHz
  8. #use rs232(baud=600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8) //Configuración de UART 600bps 8 N 1
  9. //*********************************************************************************************************
  10. // Función que realiza la lectura de puertos
  11. //*********************************************************************************************************
  12. int8 valor1=0, valor2=0, valor1Ant=0, valor2Ant=0;   //Declaracion de variables de 8bit
  13. int1 cambio=FALSE;                                   //Declaracion de variable boole
  14. int1 leePuertos(void){                               //Declaración de función para enviar datos
  15.    valor1=input_a();                                 //Lectura de puerto A
  16.    delay_ms(50);                                     //Delay de 50ms
  17.    valor2=input_b();                                 //Lectura de puerto B  
  18.    delay_ms(50);                                     //Delay de 50ms
  19.    if(valor1!=valor1Ant || valor2!=valor2Ant)        //Si hay cambio en entrada se actualiza
  20.       cambio=TRUE;                                   //Setea flag a TRUE
  21.    else                                              //Si no hay cambio en la entrada no actualiza
  22.       cambio=FALSE;                                  //Setea flag a FALSE
  23.    valor1Ant=valor1;                                 //Intercambio de valores
  24.    valor2Ant=valor2;                                 //Intercambio de valores
  25.    return(cambio);                                   //Retorno de funcion
  26. }
  27. //*********************************************************************************************************
  28. // Función que realiza el envio de datos por UART
  29. //*********************************************************************************************************
  30. #define HEADER 200                                   //Definición de valor Header para el payload
  31. static char trama[4];                                //Variable donde se aloja el trama
  32. void enivaRF(void){                                  //Declaración de función para enviar datos
  33.    trama[0]=HEADER;                                  //Carga el Header en byte 0 del trama
  34.    trama[3]=trama[1]+trama[2];                       //Realiza suma de los dos datos y lo carga en el byte 3 del trama
  35.    putc(trama[0]);                                   //Envía el byte 0 del trama por UART
  36.    delay_ms(50);                                     //Delay de espera entre bytes enviados por UART
  37.    putc(trama[1]);                                   //Envía el byte 1 del trama por UART
  38.    delay_ms(50);                                     //Delay de espera entre bytes enviados por UART
  39.    putc(trama[2]);                                   //Envía el byte 2 del trama por UART
  40.    delay_ms(50);                                     //Delay de espera entre bytes enviados por UART
  41.    putc(trama[3]);                                   //Envía el byte 3 del trama por UART
  42.    delay_ms(50);                                     //Delay de espera entre bytes enviados por UART
  43.    printf("\r");                                     //Envía caracter de retorno de linea como final de trama
  44. }
  45. //*********************************************************************************************************
  46. // Programa principal, Realiza el envio de datos por UART
  47. //*********************************************************************************************************
  48. void main(){                                         //Función principal
  49.    while(true){                                      //Loop principal repetitivo
  50.       if(leePuertos()){                              //Funcion que lee los puertos para enviar datos
  51.          trama[1]=valor1;                            //Carga el valor 1 en byte 1 del trama
  52.          trama[2]=valor2;                            //Carga el valor 2 en byte 2 del trama
  53.          enivaRF();                                  //Llamado a la función que envía datos
  54.       }
  55.    }
  56. }

El Receptor:
Este programa es similar al anterior, solo que en lugar de leer dos puertos, los escribiremos. 
Para realizar la lectura de datos utilizaremos la interrupción de puerto serie, es decir, cuando exista un dato presente en el puerto serie, se realizara una interrupción y se guardaran los datos para ser procesados mas adelante.
En este caso realizaremos un pequeño y simple buffer, que consiste en un vector de 4 posiciones, donde guardaremos la trama (header, payload y checksum) de esta manera tendremos los datos disponibles para su procesado.
Primero vamos a mover los byte recibidos al vector con un indice incremental, luego preguntaremos si este indice es mayor que 4 (buffer lleno) y si es así, procesaremos los datos y pondremos el indice en 0 nuevamente.
Mientras el indice sea menor que 4 (de 0 a 3) entonces los datos son útiles, para ello tendremos otra consulta anidada que preguntara si el Header es el que nosotros dijimos en el programa transmisor "200", si esto es verdadero entonces tendremos otra consulta preguntando si los datos de valor1 mas valor2, menos el checksum es igual a cero.

Por Ejemplo:
Checksum: 200
Valor1: 120
Valor2 34
Checksum: 154

+----------+----------+----------+----------+
|  Header  |       Payload       | Checksum |
+----------+----------+----------+----------+
|   200    |   120    |    34    |    154   |
+----------+----------+----------+----------+

Si (120+34)-154=0?, en este caso podremos ver que los datos han llegado bien porque la suma de sus variables es igual al valor del checksum, esta es nuestra verificación, recién en este punto si los datos son correcto procedemos a guardar los valores en variables que luego serán mostradas en los puertos A y B.

  1. //*********************************************************************************************************
  2. // El programa recibe 16bit por UART y los muestra por puertos
  3. // El enlace se realiza mediante una comunicación ASK OOK UHF sobre UART Invertido a 600bps
  4. //*********************************************************************************************************
  5. #include <16F883.h>                                //Biblioteca del microcontrolador
  6. #fuses NOMCLR, NOWDT, INTRC_IO                     //Configuración de fuses
  7. #use delay(int=4000000)                            //Configuración del clock interno a 4MHz
  8. #use rs232(baud=600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8) //Configuración de UART 600bps 8 N 1
  9. //*********************************************************************************************************
  10. // Variables
  11. //*********************************************************************************************************
  12. #define HEADER 200                                 //Definición de valor Header para el payload
  13. #define STATUS PIN_C0                              //Pin para analizar el status de la interrupt
  14. static int8 trama[4];                              //Variable donde se aloja el trama
  15. static int8 valor1=0, valor2=0;                    //Variable donde se alojan los datos externos
  16. //*********************************************************************************************************
  17. // Función Interrupción UART que realiza el parse de datos, validación de Header y Checksum
  18. // Si validación y Checksum son validos, carga el vector DATO para ser utilizado
  19. //*********************************************************************************************************
  20. int8 i=0;                                          //Variable para el contador de bytes de entrada
  21. #int_RDA                                           //Interrupción por dato en UART
  22. void intser(){                                     //Función de servicio de interrupción
  23.    trama[i++]=getc();                              //Guarda byte de entrada en trama. incrementa indice
  24.    if(i>4){                                        //Si el indice es mayor que 4 se asume que se completa el trama
  25.       if(trama[0]==HEADER){                        //Validación que el Header sea 200 (seteado en el transmisor)
  26.          if(trama[1]+trama[2]-trama[3]==0){        //Validación de checksum, si datos leídos son igual a checksum
  27.             valor1=trama[1];                       //Cargamos los datos en el vector
  28.             valor2=trama[2];                       //Cargamos los datos en el vector
  29.             output_high(STATUS);                   //Ponemos a 1 el LED de estado de dato presente
  30.             delay_ms(100);                         //Delay de LED encendido
  31.             output_low(STATUS);                    //Ponemos a 0 el LED
  32.          }
  33.       }
  34.       i=0;                                         //Una vez que se completan los 5 bytes, se reinicia el contador
  35.    }
  36. }
  37. //*********************************************************************************************************
  38. // Programa principal, recibe los datos por UART y los muestra en los puertos
  39. //*********************************************************************************************************
  40. void main(){                                       //Función principal
  41.    enable_interrupts(global);                      //Habilita interrupciones globales
  42.    enable_interrupts(int_rda);                     //Habilita interrupción de dato en UART
  43.    while(true){                                    //Loop principal infinito
  44.       output_a(valor1);                            //Cargamos puerto A
  45.       delay_us(100);                               //Delay de refresco
  46.       output_b(valor2);                            //Cargamos puerto B
  47.       delay_us(100);                               //Delay de refresco
  48.    }
  49. }