Tutorial · Principiante · 28 min
GPIO del 8051 e interfaz de puertos: hunde, no alimentes
Un pin del 8051 hunde diez miliamperios y casi no alimenta. Por qué eso invierte el cableado del LED, por qué escribes 1 antes de leer y qué maneja un puerto.
Introducción
El primer programa en 8051 que todo el mundo escribe parpadea un LED, y un número sorprendente de ellos no parpadea. El cableado parece correcto, el código parece correcto y el pin mide unos seguros 5 V sin hacer nada útil.
La causa es casi siempre la misma, y es arquitectónica más que un error: un pin del 8051 es bueno hundiendo corriente y malo alimentando. Todo lo demás en este tutorial se deduce de esa única asimetría: por qué los LED se cablean al revés que en un Arduino, por qué leer un interruptor necesita antes una escritura y por qué un puerto no puede manejar ocho de nada.
Un puerto es solo un byte
No hay API de GPIO. Los cuatro puertos son registros de función especial en direcciones fijas, y escribir uno cambia los pines:
#include <reg52.h>
void main(void) {
P1 = 0x00; // los ocho pines de P1 en bajo
P1 = 0xFF; // los ocho en alto
P1 = 0b10101010; // alternados
}
Los bits individuales reciben nombres con sbit, que es la forma que verás en casi todo el material de los cursos:
sbit LED = P1^0; // sintaxis Keil C51
sbit BUTTON = P1^1;
LED = 0; // una instrucción — el 8051 tiene operaciones reales de bit
Ese ^ no es un o-exclusivo. Es la notación de Keil para «bit 0 de P1». Con SDCC el equivalente viene de <8051.h> como P1_0, y el resto del código no cambia.
De qué está hecho un pin
Cada pin del puerto tiene una resistencia pull-up interna débil hacia la alimentación y un transistor pull-down fuerte hacia tierra, con el latch del puerto manejando la puerta del transistor. No se parecen en nada en fuerza.
Escribe 0 y el transistor se enciende, fijando el pin cerca de tierra y absorbiendo con gusto la corriente del exterior. Escribe 1 y el transistor se apaga, dejando solo ese débil pull-up —decenas de microamperios— para mantener la línea en alto. Basta para presentar un 1 lógico a otra entrada CMOS, y no se acerca a encender nada.
Esta disposición se llama cuasi bidireccional: el mismo pin es salida y entrada, sin registro de dirección que configurar, porque un pin que mantiene un 1 ya está lo bastante liberado para que otra cosa lo maneje.
Así que los LED van al revés
En un Arduino cableas un LED del pin a través de una resistencia a tierra y manejas el pin en alto. Si haces eso en un 8051 obtienes como mucho un brillo tenue, porque 60 µA no es una fuente de luz.
El cableado del 8051 está invertido: ánodo a +5 V a través de la resistencia, cátodo al pin. El pin hunde la corriente, y un 0 enciende el LED.
sbit LED = P1^0;
LED = 0; // ENCENDIDO — el pin hunde corriente de la alimentación
LED = 1; // APAGADO — el pin libera la línea
Dimensiona la resistencia para la corriente que quieras, recordando que el propio pin cae aproximadamente medio voltio mientras hunde:
R = (Vsupply − Vf(LED) − Vpin) / I
LED rojo a 5 mA : (5 − 2,0 − 0,5) / 0,005 = 500 Ω → usa 470 Ω
LED rojo a 10 mA: (5 − 2,0 − 0,5) / 0,010 = 250 Ω → usa 270 Ω
El presupuesto de corriente del puerto que nadie menciona
Los límites por pin son solo la mitad de la historia. La hoja de datos del AT89S52 especifica ambos, y el segundo es mucho más restrictivo:
| Límite | Valor |
|---|---|
| Corriente máxima hundida por pin | 10 mA |
| Máximo total para todo el puerto 1, 2 o 3 | 15 mA |
| Máximo total para todo el puerto 0 | 26 mA |
| Máximo total en todo el chip | 71 mA |
Lee de nuevo esa fila del medio. Un solo pin puede hundir 10 mA, pero todo el puerto 1 solo puede hundir 15 mA entre sus ocho pines. El ejercicio clásico de cablear ocho LED a un puerto y ejecutar un patrón de persecución está bien —un LED cada vez. Enciende los ocho a 10 mA cada uno y estarás pidiendo 80 mA a un puerto limitado a 15.
Fuera de especificación, las piezas no suelen explotar; se hunden. Tus niveles lógicos derivan, el chip se calienta y el comportamiento se vuelve intermitente de formas que parecen errores de software. Si quieres ocho LED brillantes y simultáneos, la solución es un manejador: un array Darlington ULN2803 hunde medio amperio por canal, y el 8051 solo le dice qué hacer.
En el propio límite del puerto, ocho LED simultáneos reciben unos 1,9 mA cada uno. Un LED moderno de alta eficiencia se ve perfectamente a eso, así que una persecución o un gráfico de barras está bien —solo haz la aritmética antes de dar por sentado el brillo.
Leer una entrada: escribe 1 primero
Como el mismo transistor sirve a ambas direcciones, un pin del 8051 solo puede leerse como entrada si su latch contiene un 1. Con un 0 almacenado, el transistor está fijando el pin a tierra y cada lectura devuelve 0 sin importar lo que esté conectado.
sbit BUTTON = P1^1;
void main(void) {
BUTTON = 1; // libera el pin — OBLIGATORIO antes de leer
while (1) {
if (BUTTON == 0) { // interruptor cableado a tierra: pulsado lee 0
// maneja la pulsación
}
}
}
Saltarse esa primera línea es el error de entrada más común en el 8051. No es un error que puedas cometer en un chip con registro de dirección, que es justo por qué atrapa a quienes llegan de AVR o PIC.
Los interruptores se cablean a tierra, no a la alimentación, por la misma razón por la que los LED estaban invertidos: el pull-up débil es el estado de reposo, y el trabajo del interruptor es vencerlo cortocircuitando el pin hacia abajo.
El puerto 0 es la excepción
El puerto 0 no tiene ningún pull-up interno. Es de drenador abierto, porque además es el bus multiplexado de direcciones y datos para la memoria externa. Usado como E/S de propósito general puede bajar un pin a tierra y por lo demás solo puede flotar.
Arréglalo con un pull-up externo en cada pin de P0 que uses —10 kΩ a +5 V, o una red de resistencias si necesitas los ocho. Sin ellos, las salidas de P0 nunca alcanzan un alto y las entradas de P0 leen ruido. Los puertos 1, 2 y 3 no necesitan nada.
Antirrebotes, porque un interruptor no es un interruptor
Un contacto mecánico rebota durante unos milisegundos, y un bucle de sondeo del 8051 es lo bastante rápido para ver cada rebote como una pulsación separada. La solución barata es confirmar la lectura tras una espera:
sbit BUTTON = P1^1;
bit pressed(void) {
if (BUTTON != 0) return 0; // no pulsado
delay_ms(20); // deja que el contacto se asiente
return (BUTTON == 0); // ¿sigue abajo? pulsación real
}
Veinte milisegundos superan con holgura el rebote de un interruptor táctil y quedan muy por debajo de lo que un humano notaría. Una espera bloqueante está bien mientras aprendes; una vez que una interrupción de temporizador esté en marcha, muestrea el interruptor desde ella para que la espera deje de detener todo lo demás.
Cuando algo sale mal
- El LED brilla muy tenue. Está cableado al estilo Arduino, del pin a tierra. Dale la vuelta: ánodo a +5 V a través de la resistencia, cátodo al pin, y maneja el pin en bajo.
- Una entrada siempre lee 0. El latch del pin mantiene un 0. Escribe un 1 en él antes de leer.
- El puerto 0 no hace nada. Sin pull-ups externos. Añade 10 kΩ a +5 V en cada pin de P0 en uso.
- Una pulsación se registra como tres. Rebote del contacto. Confirma la lectura tras ~20 ms.
- Todo funciona hasta que varias salidas están encendidas a la vez. Estás por encima del presupuesto de 15 mA del puerto. Mueve la carga a un ULN2803 o reduce la corriente por LED.
- Los pines se comportan raro solo cuando el puerto serie está en marcha. Estás usando P3.0 o P3.1, que son RXD y TXD. Las funciones alternativas del puerto 3 tienen prioridad sobre tus planes para ellos.
Explora el grafo
Forma parte de estos montajes
Proyectos y rutas de aprendizaje que incluyen este tutorial.
Lecturas adicionales