Transmisor y receptor serial con PIC16F877A (código)

En este proyecto, dos microcontroladores PIC16F877A se comunican por puerto serie (UART / RS-232 a nivel TTL): el transmisor lee una tensión analógica con su conversor A/D, la muestra en una pantalla LCD y la envía como texto; el receptor recibe los caracteres y los muestra en su propia LCD. El código está escrito en CCS C (PCW). En este post explico cómo funciona, corrijo algunos detalles del código original y agrego una sincronización simple para que el receptor no se desordene.

La simulación

La imagen muestra la simulación con los dos PIC, cada uno con su pantalla LCD, y un terminal virtual que permite ver los datos que viajan por la línea serie.

Cómo funciona

Transmisor

  1. Lee la tensión de la entrada analógica AN0 (pin RA0) con el conversor A/D en modo de 8 bits (valores de 0 a 255).
  2. Convierte la lectura a voltios: V = lectura × 5 / 255.
  3. Separa el valor en dígitos y los convierte a código ASCII sumando el carácter '0'.
  4. Muestra el valor en la LCD y lo envía por el pin RC6 (TX) a 2400 baudios, con formato "d.ddd".

Receptor

  1. Espera caracteres por el pin RC7 (RX).
  2. Cuando llegan, los guarda y los muestra en su LCD.

Conexión: el pin TX (RC6) del transmisor va al RX (RC7) del receptor, y las masas de ambos circuitos deben estar unidas. Ambos usan un cristal de 4 MHz.

Detalles corregidos respecto al código original

  • Resolución del A/D: el transmisor usa #device ADC=8 (8 bits), aunque el comentario decía 10 bits. Se mantiene en 8 bits porque la conversión divide entre 255.
  • Asignación de carácter: Dig1 = " " asignaba una cadena a una variable de un byte; lo correcto es ' ' (comillas simples). En la versión corregida se eliminó, porque para tensiones menores de 1 V dejaba un espacio en lugar del 0.
  • Reloj del A/D: con cristal de 4 MHz se recomienda ADC_CLOCK_DIV_8 en lugar del oscilador interno RC.
  • Sincronización: el receptor original leía cinco caracteres seguidos sin saber dónde empieza cada dato; si se perdía uno, todo quedaba corrido. Ahora el transmisor envía un carácter de inicio # antes de cada dato.
  • Se eliminaron variables y lecturas que no se usaban, y en el receptor no hace falta configurar el A/D.

Código del transmisor (corregido)

// Transmisor - Luis Portilla (versión revisada)
#include <16F877A.h>
#device ADC=8                 // conversor A/D en 8 bits (0-255)
#fuses XT,NOWDT,NOPROTECT,NOLVP,PUT,BROWNOUT
#use delay(clock=4000000)
#use rs232(baud=2400, xmit=PIN_C6, rcv=PIN_C7)
#include <lcd.c>

int8 Dig1, Dig2, Dig3, Dig4;

void IniConversorAD(){
   setup_adc(ADC_CLOCK_DIV_8);
   setup_adc_ports(AN0);
   set_adc_channel(0);
}

float LeerVoltios(){
   delay_us(20);                       // tiempo de adquisición
   return (read_adc() * 5.0) / 255.0;
}

void DescomponerEnDigitos(float v){
   int16 mv = (int16)(v * 1000.0);     // 0..5000 mV
   Dig1 = mv / 1000 + '0';
   Dig2 = (mv % 1000) / 100 + '0';
   Dig3 = (mv % 100) / 10 + '0';
   Dig4 = mv % 10 + '0';
}

void main(){
   IniConversorAD();
   lcd_init();
   printf(lcd_putc, "\f");
   while(TRUE){
      DescomponerEnDigitos(LeerVoltios());
      lcd_gotoxy(1,1);
      printf(lcd_putc, "%c.%c%c%c V", Dig1, Dig2, Dig3, Dig4);
      putc('#');                       // inicio de trama
      putc(Dig1); putc('.'); putc(Dig2); putc(Dig3); putc(Dig4);
      delay_ms(200);
   }
}

Código del receptor (corregido)

// Receptor - Luis Portilla (versión revisada)
#include <16F877A.h>
#fuses XT,NOWDT,NOPROTECT,NOLVP,PUT,BROWNOUT
#use delay(clock=4000000)
#use rs232(baud=2400, xmit=PIN_C6, rcv=PIN_C7)
#include <lcd.c>

char trama[6];

void main(){
   int8 i;
   lcd_init();
   printf(lcd_putc, "\fEsperando...");
   while(TRUE){
      if(kbhit()){
         if(getc() == '#'){            // espera el inicio de trama
            for(i = 0; i < 5; i++)
               trama[i] = getc();      // d.ddd
            trama[5] = 0;              // fin de cadena
            lcd_gotoxy(1,1);
            printf(lcd_putc, "%s V    ", trama);
         }
      }
   }
}

¿Qué es la UART?

La UART envía los datos bit a bit por un solo cable: un bit de inicio, 8 bits de datos y un bit de parada. A 2400 baudios, cada carácter tarda unos 4,2 ms, así que una trama de 6 caracteres tarda unos 25 ms. Ambos extremos deben estar configurados a la misma velocidad; por eso los dos PIC usan el mismo cristal y la misma directiva #use rs232.

Notas sobre el hardware

  • La librería lcd.c de CCS usa por defecto un puerto determinado para la pantalla (normalmente el puerto D en el 16F877A); revisa su configuración en la cabecera de la librería.
  • La tensión en AN0 no debe superar 5 V ni ser negativa. Para medir tensiones mayores usa un divisor resistivo.
  • Si los dos circuitos están a más de unos metros, usa un transceptor como el MAX232 (RS-232) o un par RS-485.
  • Coloca un condensador de 100 nF entre VDD y VSS de cada PIC y la resistencia de 10 kΩ en MCLR.

Solución de problemas

  • El receptor muestra caracteres raros: velocidad o cristal diferentes entre los dos PIC.
  • No llega nada: TX y RX cruzados al revés o masas sin unir.
  • La lectura no cambia: revisa el potenciómetro en AN0 y la configuración del puerto A.
  • La LCD no muestra nada: ajusta el contraste (pin V0) y verifica el puerto configurado en lcd.c.

Preguntas frecuentes

¿Puedo usar 10 bits?

Sí: usa #device ADC=10, una variable int16 y divide entre 1023.

¿Funciona con Arduino como receptor?

Sí, a 2400 baudios y con masas unidas; mira proyectos Arduino para iniciarse.

¿Puedo guardar el último valor recibido?

Sí, en la EEPROM interna del PIC con write_eeprom(). La idea es similar a la de guardar variables en Arduino.

Conclusión

Dos PIC16F877A pueden intercambiar datos por UART con muy poco código. Con un carácter de inicio de trama, la asignación correcta de caracteres y el reloj del A/D adecuado, el sistema transmite y muestra tensiones de forma confiable.

No hay comentarios