Tutorial · Einsteiger · 28 Min.

8051-GPIO und Ports: Senken statt treiben

Ein 8051-Pin senkt zehn Milliampere und treibt kaum. Warum das deine LED-Verdrahtung umkehrt, warum du vor dem Lesen eine 1 schreibst und was ein Port treibt.

Einführung

Das erste 8051-Programm, das jeder schreibt, lässt eine LED blinken – und überraschend viele davon blinken nicht. Die Verdrahtung sieht richtig aus, der Code sieht richtig aus, und der Pin misst selbstbewusste 5 V, die nichts Nützliches tun.

Die Ursache ist fast immer dieselbe und architektonisch bedingt statt ein Fehler: Ein 8051-Pin zieht gut nach unten und schlecht nach oben. Alles andere in dieser Anleitung folgt aus dieser einen Asymmetrie – warum LEDs genau andersherum verdrahtet sind als beim Arduino, warum das Lesen eines Schalters erst einen Schreibzugriff braucht und warum ein Port nicht acht beliebige Lasten treiben kann.

Ein Port ist nur ein Byte

Es gibt keine GPIO-API. Die vier Ports sind Spezialfunktionsregister an festen Adressen, und ein Schreibzugriff ändert die Pins:

#include <reg52.h>

void main(void) {
    P1 = 0x00;        // all eight P1 pins low
    P1 = 0xFF;        // all eight high
    P1 = 0b10101010;  // alternating
}

Einzelne Bits erhalten Namen mit sbit, die Form, die du in fast allem Kursmaterial siehst:

sbit LED    = P1^0;   // Keil C51 syntax
sbit BUTTON = P1^1;

LED = 0;              // one instruction — the 8051 has real bit operations

Dieses ^ ist kein Exklusiv-Oder. Es ist Keils Notation für „Bit 0 von P1“. Unter SDCC kommt das Äquivalent aus <8051.h> als P1_0, und der Rest des Codes bleibt unverändert.

Woraus ein Pin besteht

Jeder Port-Pin hat einen schwachen internen Pull-up zur Versorgung und einen starken Pull-down-Transistor nach Masse, wobei das Port-Latch das Gate des Transistors treibt. Beide sind bei Weitem nicht gleich stark.

Innenaufbau eines einzelnen 8051-Port-Pins. Innerhalb einer gestrichelten Chipgrenze verbindet ein schwacher Pull-up von etwa sechzig Mikroampere die 5-Volt-Schiene mit dem Pin-Knoten, und ein Pull-down-Feldeffekttransistor für bis zu zehn Milliampere verbindet den Pin-Knoten mit Masse, sein Gate vom Port-Latch getrieben. Der Pin verlässt den Chip und ist mit einer externen LED verbunden, deren Kathode zum Pin zeigt und deren Anode über einen 330-Ohm-Widerstand an die 5-Volt-Schiene geht. Zwei Zeilen darunter erklären, dass Schreiben von 0 den Transistor einschaltet, sodass der Pin nach Masse zieht und die LED leuchtet, und Schreiben von 1 ihn ausschaltet, sodass nur sechzig Mikroampere bleiben und die LED dunkel bleibt. Ein Hinweis besagt, dass Port 0 gar keinen Pull-up hat und einen externen 10-Kiloohm-Widerstand braucht.
Die Asymmetrie, die 8051-Verdrahtung definiert: ein Transistor, der Milliampere senkt, gegen einen Pull-up, der Mikroampere liefert. SVG herunterladen

Schreibe 0, und der Transistor schaltet ein, klemmt den Pin nahe Masse und schluckt gern Strom von außen. Schreibe 1, und der Transistor schaltet aus, sodass nur der schwache Pull-up – Dutzende Mikroampere – die Leitung oben hält. Das reicht, um einem anderen CMOS-Eingang eine logische 1 zu zeigen, und reicht bei Weitem nicht, um etwas zum Leuchten zu bringen.

Diese Anordnung heißt quasi-bidirektional: Derselbe Pin ist Ausgang und Eingang, ohne Richtungsregister zur Konfiguration, weil ein Pin mit gehaltener 1 bereits so weit freigegeben ist, dass er von etwas anderem getrieben werden kann.

Also kommen LEDs andersherum hinein

Beim Arduino verdrahtest du eine LED vom Pin über einen Widerstand nach Masse und treibst den Pin auf High. So am 8051 bekommst du bestenfalls ein mattes Glimmen, weil 60 µA keine Lichtquelle sind.

Die 8051-Verdrahtung ist invertiert: Anode über den Widerstand an +5 V, Kathode an den Pin. Der Pin senkt den Strom, und eine 0 schaltet die LED ein.

sbit LED = P1^0;

LED = 0;    // ON  — the pin sinks current from the supply
LED = 1;    // OFF — the pin releases the line

Dimensioniere den Widerstand für den gewünschten Strom und denke daran, dass der Pin selbst beim Senken etwa ein halbes Volt abfallen lässt:

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 Ω

Das Port-Strombudget, das niemand erwähnt

Grenzwerte pro Pin sind nur die halbe Geschichte. Das AT89S52-Datenblatt nennt beide, und der zweite ist weit restriktiver:

Grenzwert Wert
Maximaler Senkstrom pro Pin 10 mA
Maximum gesamt für Port 1, 2 oder 3 15 mA
Maximum gesamt für Port 0 26 mA
Maximum gesamt über den ganzen Chip 71 mA

Lies die mittlere Zeile erneut. Ein einzelner Pin darf 10 mA senken, aber ganz Port 1 darf zwischen allen acht Pins nur 15 mA senken. Die klassische Übung, acht LEDs an einen Port zu hängen und ein Lauflicht laufen zu lassen, ist in Ordnung – immer eine LED gleichzeitig. Schalte alle acht mit je 10 mA ein, forderst du 80 mA von einem Port mit 15 mA Nennwert.

Überforderte Bausteine explodieren meist nicht; sie brechen ein. Deine Logikpegel driften, der Chip wird warm, und das Verhalten wird intermittierend auf Arten, die wie Softwarefehler aussehen. Willst du acht LEDs hell und gleichzeitig, ist die Lösung ein Treiber: Ein ULN2803-Darlington-Array senkt ein halbes Ampere pro Kanal, und der 8051 sagt ihm nur, was zu tun ist.

An der Portgrenze bekommen acht gleichzeitige LEDs je etwa 1,9 mA. Eine moderne hocheffiziente LED ist dabei gut sichtbar, also sind Lauflicht oder Balkenanzeige in Ordnung – rechne nur vorher, statt Helligkeit anzunehmen.

Einen Eingang lesen: erst 1 schreiben

Weil derselbe Transistor beide Richtungen bedient, kann ein 8051-Pin nur als Eingang gelesen werden, wenn sein Latch eine 1 hält. Mit gehaltener 0 klemmt der Transistor den Pin auf Masse, und jeder Lesezugriff liefert 0, egal was angeschlossen ist.

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
        }
    }
}

Diese erste Zeile zu überspringen ist der häufigste 8051-Eingabefehler. Es ist kein Fehler, den man auf einem Chip mit Richtungsregister machen kann – genau darum erwischt er Leute, die von AVR oder PIC kommen.

Schalter werden aus demselben Grund nach Masse verdrahtet, aus dem LEDs invertiert wurden: Der schwache Pull-up ist der Ruhezustand, und die Aufgabe des Schalters ist, ihn zu überstimmen, indem er den Pin nach unten kurzschließt.

Port 0 ist die Ausnahme

Port 0 hat gar keinen internen Pull-up. Er ist Open-Drain, weil er zugleich als gemultiplexter Adress- und Datenbus für externen Speicher dient. Als Allzweck-I/O genutzt kann er einen Pin nach unten ziehen und sonst nur schweben.

Behebe das mit einem externen Pull-up an jedem genutzten P0-Pin – 10 kΩ nach +5 V oder ein Widerstandsnetzwerk, wenn du alle acht brauchst. Ohne sie erreichen P0-Ausgänge nie High, und P0-Eingänge lesen Rauschen. Ports 1, 2 und 3 brauchen nichts.

Entprellen, weil ein Schalter kein Schalter ist

Ein mechanischer Kontakt prellt einige Millisekunden, und eine 8051-Abfrageschleife ist schnell genug, um jedes Prellen als eigenes Drücken zu sehen. Die billige Lösung bestätigt den Messwert nach einer Verzögerung:

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
}

Zwanzig Millisekunden überdauern das Prellen eines Tasters bequem und bleiben weit unter der Wahrnehmbarkeitsschwelle. Eine blockierende Verzögerung ist zum Lernen in Ordnung; sobald ein Timer-Interrupt läuft, taste den Schalter stattdessen von dort ab, damit die Verzögerung nichts anderes mehr aufhält.

Wenn es schiefgeht

  • Die LED glimmt nur sehr schwach. Sie ist Arduino-like verdrahtet, vom Pin nach Masse. Drehe sie um: Anode über den Widerstand an +5 V, Kathode an den Pin, und treibe den Pin auf Low.
  • Ein Eingang liest immer 0. Das Latch des Pins hält 0. Schreibe vor dem Lesen eine 1 hinein.
  • Port 0 tut nichts. Keine externen Pull-ups. Füge 10 kΩ nach +5 V an jedem genutzten P0-Pin hinzu.
  • Ein Tastendruck zählt als drei. Kontaktprellen. Bestätige den Messwert nach ca. 20 ms.
  • Alles funktioniert, bis mehrere Ausgänge gleichzeitig an sind. Du bist über dem 15-mA-Port-Budget. Verlagere die Last auf einen ULN2803 oder senke den Strom pro LED.
  • Pins verhalten sich nur seltsam, wenn die Serienschnittstelle läuft. Du nutzt P3.0 oder P3.1, also RXD und TXD. Die Alternativfunktionen von Port 3 haben Vorrang vor deinen Plänen.

Den Graphen erkunden

Teil dieser Bauprojekte

Projekte und Lernpfade, die dieses Tutorial enthalten.

Weiterführende Literatur

Quellen