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.

Struttura interna di un singolo pin di una porta 8051. Dentro un confine del chip tratteggiato, un pull-up debole indicato come circa sessanta microampere collega la linea di alimentazione a cinque volt al nodo del pin; un transistor a effetto di campo di pull-down, indicato come in grado di assorbire fino a dieci milliampere, collega il nodo del pin a terra e il suo gate è pilotato dal latch della porta. Il pin esce dal chip e si collega a un LED esterno con il catodo rivolto verso il pin e l'anodo che passa attraverso una resistenza da 330 ohm verso la linea di alimentazione a cinque volt. Due righe sotto indicano che scrivere 0 accende il transistor, così il pin tira verso terra e il LED si accende, mentre scrivere 1 lo spegne e lascia solo sessanta microampere, così il LED resta spento. Una nota finale dice che la porta 0 non ha alcun pull-up e richiede una resistenza esterna da 10 kiloohm.
L'asimmetria che definisce il cablaggio dell'8051: un transistor che assorbe milliampere contro un pull-up che fornisce microampere. Scarica SVG

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

Fonti