Tutorial · Beginner · 28 min

8051 GPIO en poortinterfacing: trek aan, stuur niet

8051-pin: trekt tien milliampère en levert nauwelijks. Waarom dat je LED-bedrading omkeert, waarom je 1 schrijft voor het uitlezen en wat een poort aanstuurt.

Inleiding

Het eerste 8051-programma dat iedereen schrijft laat een LED knipperen, en een verbazend aantal ervan knippert niet. De bedrading klopt, de code klopt, en de pin meet een zelfverzekerde 5 V die niets nuttigs doet.

De oorzaak is bijna altijd dezelfde, en het is architectuur en geen fout: een 8051-pin is goed in omlaag trekken en slecht in omhoog trekken. Al het andere in deze tutorial volgt uit die ene asymmetrie — waarom LED’s precies andersom bedraad worden dan bij een Arduino, waarom het uitlezen van een schakelaar eerst een schrijfactie vereist, en waarom een poort geen acht van wat dan ook kan aansturen.

Een poort is gewoon een byte

Er is geen GPIO-API. De vier poorten zijn speciale functieregisters op vaste adressen, en er een beschrijven verandert de pinnen:

#include <reg52.h>

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

Losse bits krijgen een naam met sbit, en dat is de vorm die je in vrijwel al het cursusmateriaal tegenkomt:

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

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

Die ^ is geen exclusieve of. Het is Keils notatie voor “bit 0 van P1”. Onder SDCC komt het equivalent uit <8051.h> als P1_0, en de rest van de code blijft ongewijzigd.

Waaruit een pin bestaat

Elke poortpin heeft een zwakke interne pull-up naar de voeding en een sterke pull-downtransistor naar aarde, waarbij de poortlatch de gate van de transistor aanstuurt. De twee zijn geen van beide ook maar even sterk.

Interne structuur van een enkele 8051-poortpin. Binnen een stippellijn die de chip grenst verbindt een zwakke pull-up van ongeveer zestig microampère de vijfvoltsrail met de knoop van de pin, en een pull-down-veldeffecttransistor die tot tien milliampère trekt de knoop van de pin met aarde, waarvan de gate door de poortlatch wordt aangestuurd. De pin verlaat de chip en sluit aan op een externe LED waarvan de kathode naar de pin wijst en waarvan de anode via een weerstand van 330 ohm naar de vijfvoltsrail gaat. Twee regels eronder stellen dat 0 schrijven de transistor inschakelt zodat de pin naar aarde trekt en de LED oplicht, en dat 1 schrijven hem uitschakelt zodat er alleen zestig microampère overblijft en de LED donker blijft. Een laatste aantekening zegt dat poort 0 helemaal geen pull-up heeft en een externe weerstand van 10 kilohm nodig heeft.
De asymmetrie die 8051-bedrading bepaalt: een transistor die milliampère trekt tegen een pull-up die microampère levert. SVG downloaden

Schrijf 0 en de transistor gaat aan, klemt de pin vlak bij aarde en slokt gretig stroom van buiten op. Schrijf 1 en de transistor gaat uit, zodat alleen die zwakke pull-up — tientallen microampère — de lijn hoog houdt. Dat is genoeg om een logische 1 aan een andere CMOS-ingang aan te bieden, en lang niet genoeg om iets te laten oplichten.

Deze opstelling heet quasibidirectioneel: dezelfde pin is uitgang én ingang, zonder richtingsregister om te configureren, omdat een pin die 1 vasthoudt al voldoende is vrijgegeven om door iets anders te worden aangestuurd.

Dus LED’s gaan de andere kant op

Op een Arduino bedraad je een LED vanaf de pin via een weerstand naar aarde en stuur je de pin hoog. Doe dat op een 8051 en je krijgt op zijn best een zwakke gloed, want 60 µA is geen lichtbron.

De bedrading op de 8051 is omgekeerd: anode naar +5 V via de weerstand, kathode naar de pin. De pin trekt de stroom weg, en een 0 zet de LED aan.

sbit LED = P1^0;

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

Kies de weerstand voor de stroom die je wilt, en onthoud dat de pin zelf ongeveer een halve volt laat vallen terwijl hij trekt:

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 Ω

Het stroombudget van de poort dat niemand noemt

Limieten per pin zijn slechts de helft van het verhaal. De AT89S52-datasheet specificeert allebei, en de tweede is veel beperkter:

Limiet Waarde
Maximale trekstroom per pin 10 mA
Maximaal totaal voor alle pinnen van poort 1, 2 of 3 15 mA
Maximaal totaal voor alle pinnen van poort 0 26 mA
Maximaal totaal over de hele chip 71 mA

Lees die middelste rij nog eens. Een enkele pin mag 10 mA trekken, maar alle pinnen van poort 1 samen mogen samen maar 15 mA trekken. De klassieke oefening van acht LED’s aan een poort hangen en er een lichtkeverpatroon op draaien is prima — één LED tegelijk. Zet ze alle acht aan op 10 mA elk en je vraagt 80 mA van een poort die voor 15 mA is gespecificeerd.

Onderdelen die buiten de specificatie vallen ontploffen meestal niet; ze zakken in. Je logische niveaus drijven weg, de chip wordt warm en het gedrag wordt willekeurig op manieren die op softwarebugs lijken. Wil je acht LED’s helder en tegelijk schijnen, dan is de oplossing een driver: een ULN2803-Darlingtonschakeling trekt een halve ampère per kanaal, en de 8051 vertelt hem alleen wat hij moet doen.

Op de limiet van de poort zelf krijgen acht gelijktijdige LED’s ongeveer 1,9 mA elk. Een moderne hoogrendements-LED is daarbij prima zichtbaar, dus een lichtkever of een staafdiagram kan wel — reken alleen eerst voordat je helderheid aanneemt.

Een ingang uitlezen: schrijf eerst 1

Omdat dezelfde transistor beide richtingen bedient, kan een 8051-pin alleen als ingang worden uitgelezen als zijn latch een 1 vasthoudt. Met een 0 in de latch klemt de transistor de pin aan aarde en levert elke lezing 0 op, wat er ook aangesloten zit.

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

Die eerste regel overslaan is de meest voorkomende 8051-ingangsfout. Het is geen fout die je op een chip met een richtingsregister kunt maken, en juist daarom overkomt hij je als je komt uit de AVR- of PIC-wereld.

Schakelaars worden naar aarde bedraad, niet naar de voeding, om dezelfde reden als de LED’s werden omgekeerd: de zwakke pull-up is de toestand in rust, en de taak van de schakelaar is die te overwinnen door de pin kort naar aarde te sluiten.

Poort 0 is de uitzondering

Poort 0 heeft helemaal geen interne pull-up. Hij is open-drain, omdat hij tegelijk dienstdoet als de gemultiplexe adres- en databus voor extern geheugen. Gebruikt als algemeen I/O kan hij een pin omlaag trekken en verder alleen zweven.

Los dat op met een externe pull-up op elke P0-pin die je gebruikt — 10 kΩ naar +5 V, of een weerstandsnetwerk als je ze alle acht nodig hebt. Zonder deze bereiken P0-uitgangen nooit een hoog niveau en lezen P0-ingangen ruis. Poorten 1, 2 en 3 hebben niets nodig.

Demptijd, want een schakelaar is geen schakelaar

Een mechanisch contact kaatst enkele milliseconden heen en weer, en een 8051-poll lus is snel genoeg om elke kaats als een aparte indruk te zien. De goedkope oplossing is de lezing na een vertraging bevestigen:

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
}

Twintig milliseconden overleeft het kaatsen van een tactiele schakelaar ruim en blijft ver onder het punt waar een mens het zou opmerken. Een blokkerende vertraging is prima zolang je aan het leren bent; zodra er een timerinterrupt draait, neem je de schakelaar van daaruit monsters zodat de vertraging niet langer al het andere tegenhoudt.

Het gaat mis

  • De LED gloeit heel zwakjes. Hij is op Arduino-manier bedraad, van pin naar aarde. Draai hem om: anode naar +5 V via de weerstand, kathode naar de pin, en stuur de pin laag.
  • Een ingang leest altijd 0. De latch van de pin houdt 0 vast. Schrijf er een 1 naar voor je leest.
  • Poort 0 doet niets. Geen externe pull-ups. Zet 10 kΩ naar +5 V op elke P0-pin die in gebruik is.
  • Eén indruk telt als drie. Contactdendering. Bevestig de lezing na ~20 ms.
  • Alles werkt totdat er meerdere uitgangen tegelijk aan staan. Je overschrijdt het poortbudget van 15 mA. Verplaats de belasting naar een ULN2803 of beperk de stroom per LED.
  • Pinnen doen raar gedrag alleen wanneer de seriële poort draait. Je gebruikt P3.0 of P3.1, en dat zijn RXD en TXD. De alternatieve functies van poort 3 hebben voorrang op jouw plannen ermee.

Verken de graaf

Onderdeel van deze bouwprojecten

Projecten en leerroutes waarin deze tutorial voorkomt.

Verder lezen

Bronnen