Tutorial · Principiante · 28 min
GPIO dell'8051: assorbire, non alimentare
Un pin dell'8051 assorbe 10 mA e non eroga corrente: vedi perché i LED vanno al contrario, quando scrivere 1 prima di leggere e cosa può pilotare una porta.
Introduzione
Il primo programma 8051 che tutti scrivono fa lampeggiare un LED e un numero sorprendente di programmi non lo fa lampeggiare. Il cablaggio sembra giusto, il codice sembra giusto e il pin misura un convinto 5 V che non serve a nulla.
La causa è quasi sempre la stessa ed è architetturale, non un errore: un pin dell’8051 è bravo a tirare verso il basso e incapace di tirare verso l’alto. Tutto il resto di questo tutorial segue da quell’unica asimmetria—perché i LED sono cablati al contrario rispetto a un Arduino, perché la lettura di un interruttore richiede prima una scrittura e perché una porta non può pilotare otto elementi qualsiasi.
Una porta è soltanto un byte
Non esiste un’API GPIO. Le quattro porte sono registri a funzione speciale a indirizzi fissi e scrivere in una di esse cambia lo stato dei pin:
#include <reg52.h>
void main(void) {
P1 = 0x00; // all eight P1 pins low
P1 = 0xFF; // all eight high
P1 = 0b10101010; // alternating
}
I singoli bit ricevono nomi con sbit, la forma che vedrai in quasi tutto il materiale didattico:
sbit LED = P1^0; // Keil C51 syntax
sbit BUTTON = P1^1;
LED = 0; // one instruction — the 8051 has real bit operations
Quel ^ non è un OR esclusivo. È la notazione di Keil per “bit 0 di P1”. Con SDCC l’equivalente proviene da <8051.h> ed è P1_0; il resto del codice rimane invariato.
Di cosa è fatto un pin
Ogni pin di una porta ha un pull-up interno debole verso l’alimentazione e un transistor di pull-down robusto verso terra, con il latch della porta che pilota il gate del transistor. I due non hanno assolutamente la stessa forza.
Scrivi 0 e il transistor si accende, vincola il pin vicino a terra e assorbe volentieri corrente dall’esterno. Scrivi 1 e il transistor si spegne, lasciando solo quel debole pull-up—decine di microampere—per mantenere alta la linea. Bastano a presentare un livello logico 1 a un altro ingresso CMOS, ma non sono assolutamente sufficienti per illuminare qualcosa.
Questa disposizione si chiama quasi-bidirezionale: lo stesso pin è un’uscita e un ingresso, senza un registro di direzione da configurare, perché un pin che mantiene 1 è già abbastanza rilasciato da poter essere pilotato da qualcos’altro.
Quindi i LED vanno collegati al contrario
Su un Arduino colleghi un LED dal pin, attraverso una resistenza, a terra e piloti il pin alto. Fallo su un 8051 e ottieni al massimo una luce fioca, perché 60 µA non sono una sorgente luminosa.
Il cablaggio dell’8051 è invertito: anodo a +5 V attraverso la resistenza, catodo al pin. Il pin assorbe la corrente e uno 0 accende il LED.
sbit LED = P1^0;
LED = 0; // ON — the pin sinks current from the supply
LED = 1; // OFF — the pin releases the line
Dimensiona la resistenza per la corrente desiderata, ricordando che il pin stesso perde circa mezzo volt mentre assorbe corrente:
R = (Vsupply − Vf(LED) − Vpin) / I
red LED at 5 mA : (5 − 2.0 − 0.5) / 0.005 = 500 Ω → use 470 Ω
red LED at 10 mA: (5 − 2.0 − 0.5) / 0.010 = 250 Ω → use 270 Ω
Il bilancio di corrente della porta di cui nessuno parla
I limiti per pin sono soltanto metà della storia. La scheda tecnica dell’AT89S52 specifica entrambi e il secondo è molto più restrittivo:
| Limite | Valore |
|---|---|
| Corrente massima assorbita per pin | 10 mA |
| Corrente massima totale per tutti i pin della porta 1, 2 o 3 | 15 mA |
| Corrente massima totale per tutti i pin della porta 0 | 26 mA |
| Corrente massima totale sull’intero chip | 71 mA |
Rileggi la riga centrale. Un singolo pin può assorbire 10 mA, ma l’intera porta 1 può assorbire solo 15 mA tra tutti e otto i pin. Il classico esercizio che collega otto LED a una porta ed esegue un effetto di inseguitura è accettabile—un LED alla volta. Se accendi tutti e otto a 10 mA ciascuno, stai chiedendo 80 mA a una porta progettata per 15 mA.
I componenti fuori specifica di solito non esplodono; cedono. I livelli logici si abbassano, il chip si scalda e il comportamento diventa intermittente in modi che sembrano bug del software. Se vuoi otto LED luminosi e simultanei, la soluzione è un driver: una matrice Darlington ULN2803 assorbe mezzo ampere per canale e l’8051 deve solo dirle cosa fare.
Al limite della porta, otto LED simultanei ricevono circa 1.9 mA ciascuno. Un LED moderno ad alta efficienza è perfettamente visibile a tale corrente, quindi un effetto di inseguitura o un display a barre funziona—solo fai i conti prima di dare per scontata la luminosità.
Lettura di un ingresso: prima scrivi 1
Poiché lo stesso transistor serve a entrambe le direzioni, un pin dell’8051 può essere letto come ingresso solo se il suo latch mantiene 1. Con uno 0 memorizzato, il transistor blocca il pin a terra e ogni lettura restituisce 0, indipendentemente da ciò che è collegato.
sbit BUTTON = P1^1;
void main(void) {
BUTTON = 1; // release the pin — REQUIRED before reading
while (1) {
if (BUTTON == 0) { // switch wired to ground: pressed reads 0
// handle press
}
}
}
Saltare quella prima riga è il bug di ingresso dell’8051 più comune. Non è un errore che puoi commettere su un chip dotato di un registro di direzione, ed è esattamente il motivo per cui colpisce chi arriva dall’AVR o dal PIC.
Gli interruttori sono collegati a terra, non all’alimentazione, per lo stesso motivo per cui i LED sono stati invertiti: il pull-up debole è lo stato inattivo e il compito dell’interruttore è sovrascriverlo cortocircuitando il pin verso terra.
La porta 0 è l’eccezione
La porta 0 non ha alcun pull-up interno. È open-drain, perché svolge anche la funzione di bus multiplexato di indirizzi e dati per la memoria esterna. Usata come I/O generico può tirare un pin verso il basso e, per il resto, può solo restare galleggiante.
Correggila con un pull-up esterno su ogni pin P0 che utilizzi—10 kΩ verso +5 V, oppure una rete di resistenze se ti servono tutti e otto. Senza questi, le uscite P0 non raggiungono mai un livello alto e gli ingressi P0 leggono rumore. Le porte 1, 2 e 3 non richiedono nulla.
Eliminazione del rimbalzo, perché un interruttore non è un interruttore
Un contatto meccanico rimbalza per alcuni millisecondi e un ciclo di polling dell’8051 è abbastanza veloce da vedere ogni rimbalzo come una pressione separata. La soluzione economica è confermare la lettura dopo un ritardo:
sbit BUTTON = P1^1;
bit pressed(void) {
if (BUTTON != 0) return 0; // not pressed
delay_ms(20); // let the contact settle
return (BUTTON == 0); // still down? real press
}
Venti millisecondi superano comodamente il rimbalzo di un interruttore tattile e restano ben sotto il punto in cui una persona lo percepirebbe. Una pausa bloccante va bene mentre impari; quando è in funzione un interrupt del timer, campiona l’interruttore da lì, così il ritardo non blocca più tutto il resto.
Quando qualcosa va storto
- Il LED emette una luce molto debole. È cablato allo stile Arduino, dal pin a terra. Invertilo: anodo a +5 V attraverso la resistenza, catodo al pin, e pilota il pin verso il basso.
- Un ingresso legge sempre 0. Il latch del pin mantiene 0. Scrivi 1 prima di leggere.
- La porta 0 non fa nulla. Mancano i pull-up esterni. Aggiungi 10 kΩ verso +5 V su ogni pin P0 utilizzato.
- Una pressione viene registrata come tre. Rimbalzo del contatto. Conferma la lettura dopo circa 20 ms.
- Tutto funziona finché più uscite non sono attive contemporaneamente. Hai superato il bilancio di 15 mA della porta. Sposta il carico su un ULN2803 o riduci la corrente per ogni LED.
- I pin si comportano in modo strano solo quando la porta seriale è attiva. Stai usando P3.0 o P3.1, cioè RXD e TXD. Le funzioni alternative della porta 3 hanno la precedenza sui tuoi programmi per quei pin.
Esplora il grafo
Fa parte di queste costruzioni
Progetti e percorsi di apprendimento che includono questo tutorial.
Per approfondire