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
- 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).
- Convierte la lectura a voltios: V = lectura × 5 / 255.
- Separa el valor en dígitos y los convierte a código ASCII sumando el carácter '0'.
- Muestra el valor en la LCD y lo envía por el pin RC6 (TX) a 2400 baudios, con formato "d.ddd".
Receptor
- Espera caracteres por el pin RC7 (RX).
- 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_8en 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.cde 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.

Comentarios