Metodické materiály k předmětu MIMP

Jednočipový mikropočítač Intel 8051

© 1997

Ing. Petr Weissar

ZČU, FEL-KAE

Jednočipový mikropočítač Intel 8051 je základem celé rodiny procesorů kompatibilních jak po stránce instrukční sady, tak po stránce vývodové a signálové. Lepší a výkonnější typy obsahují lépe vybavené či přidané periférie, větší i rychlejší paměť apod., ale programování a práce s obvodem zůstává zachována. Proto stačí se naučit pracovat se základním modelem a znát pak další rozšíření kompatibilních typů.

Zde jsou uvedeny informace potřebné ke psaní programů pro 8051 s nezbytným minimem hardwarové stránky. Podrobnější informace lze v ucelené míře získat na přednáškách MIMP (doc. Pinker).

Další potřebné informace lze nalézt:

Při cvičeních předmětu MIMP se 8051 programuje v jazyce C, jehož znalost je tedy nezbytností. Používaný překladač C51 firmy Keil se drží standardu ANSI C se specifickými rozšířeními pro řadu 8051. Používaná verze 3.2 obsahuje kompilátor C, assembler, linker a některé pomocné “prográmky”. Na počítačích v laboratoři MIMP je k dispozici dávka, které se předloží zdrojový text v “C” a výsledkem jsou buď hlášení o chybách nebo program, který je možno nahrát do vývojové desky s 8051. Ten se může naprogramovat do paměti EEPROM/EPROM a vložit do patice na desce nebo poslat po sériové lince do paměti RAM vývojové desky a tuto paměť pak přepnout jako paměť programu. Toto řeší program monitor, který dokáže pracovat na upravené vývojové desce (jedna z diplomových prací na KAE).

Obsah překládací dávky C51.BAT je možno diskutovat s vyučujícím. “Cesta” zdrojového textu programu pak vypadá asi následovně:

obrázek .....

Pro zjednodušení práce se v laboratoři MIMP používá vývojové prostředí Borland C++ 3.1, ve kterém je možné pohodlně psát programy i s pomocí např. “Syntax Highlighting”. Po vyvolání externího překladače jsou do prostředí vráceny informace o překladu, chybách apod. Je možné samozřejmě používat i jiné vývojové prostředí.

Příklad 1 čítač na bráně P1:

#include <reg51.h> // tento hlavickovy soubor obsahuje
// definici jmen specialnich funkcnich
// registru SFR

int main (void)
{
    int i; // pomocna promenna
    P1=0; // nastav hodnotu na branu P1

    while(1) // nekonecna smycka
    {
        P1++;    // inkrementuj hodnotu na brane,
                 // odpovidajici registr je pro cteni/zapis
                 // a chova se jako unsigned char
        for(i=0;i<8000;i++) // cekaci smycka pro vizualni kontrolu
            ;
    }

    return 0;   // doporuceno i v pripade nekonecne smycky
                // jinak prekladac muze dat varovani ...
}

Je nezbytně nutné především “includovat” soubor REG51.H, který obsahuje deklarace jmen speciálních funkčních registrů (SFR). Tyto registry řídí činnost jednotlivých součástí mikropočítače. Zpravidla jsou přístupné jako bajtová hodnota, v jazyce C je to pak chápáno jako unsigned char. Některé registry jsou přístupné pro čtení i zápis, jiné pouze pro čtení nebo pro zápis. To je podrobně popsáno v dokumentaci k 8051, používané SFR se proberou i zde. Některé registry jsou přístupné i po jednotlivých bitech, jejich jména též obsahuje REG51.H a shodují se s názvy podle Intel-dokumentace.

Uvedený příklad je triviální, tvoří 8-bitový čítač na bráně P1. Základní typ 8051 má celkem 4 brány, P0 - P3. Všechny jsou 8-bitové vstupně/výstupní, ale brány P0 a P2 jsou použity k připojení vnější paměti programu a dat, brána P3 je sdílena s některými speciálními vývody (přerušení, čítač/časovač apod.). Zbývá tedy brána P1. Její vývody jsou vyvedeny na konektor vývojové desky. Vizuální kontrolu by bylo možné provést např. připojením LED diod na výstupy brány P1.

Používaný překladač C51 umožňuje psát poznámky do kódu ohraničené nejen /* ... */, ale i podle stylu C++ označit jako poznámku cokoliv od značky // do konce řádku. Dále je důležité si uvědomit, že komentářové závorky /* ... */ nelze vnořovat.

Příklad 2 využití interního čítače

#include <reg51.h> // deklarace registru SFR
#define TH_1MS (65536-1000)/256 // definice hodnot
#define TL_1MS (65536-1000)%256

int main(void)
{
    int i=0; // promenna pomocneho citace

    TMOD=0x01; // nastaveni rezimu citace CT0
    TH0=TH_1MS; // nastaveni pocatecniho stavu
    TL0=TL_1MS;
    TR0=1; // spusteni citace CT0

    while(1)
    {
        while(!TF0) // dokud citac nedopocita
            ;
        TF0=0; // vynuluj priznak pro pristi pruchod
        TH0=TH_1MS; // nastavit hodnotu do CT0
        TL0=TL_1MS;

        i++; // pomocny citac
        if(i>100) // 100 * 1000µ s = 0.1 vteriny
        {
            i=0;
            P1<<=1; // posun vlevo
            if(!P1) // je-li 0 (“vysunuto”)
                P1=1; // zacni na 0000 0001
        }
    }
}

Mikroprocesor 8051 obsahuje 2 stejné nezávislé 16-bitové čítače. CT0 je všeobecně použitelný, CT1 je primárně určen ke generování přenosové rychlosti sériového přenosu, ale pokud není potřeba komunikovat, je také volný. Čítače mohou pracovat ve 4 režimech, které se nastavují v registru TMOD. Dolní nibble patří CT0, horní pak CT1. Dolní 2 bity určují režim čítače. Zde se používá režim 1, což je plně 16-bitový čítač. Čítače počítají nahoru a při přetečení 0xFFFF -> 0x0000 nastaví příznak TFx a mohou vyvolat přerušení. V režimu čítače je na vstup čítačů připojena hodinová frekvence dělená 12, což je pro krystal 12 MHz přesně 1 MHz. Registry čítače jsou označeny THx (vyšší bajt) a TLx. Pokud se tedy má za 1 ms provést přetečení, je třeba nastavit hodnotu -1000 (viz program). Ještě je nutno čítač spustit, což řídí bit TRx.

V hlavní smyčce programu se čeká na přetečení čítače (příznak TF0, pak se čítač znovu naplní, aby mohl znovu počítat. Protože nejdelší interval, který lze při krystalu 12 MHz změřit, je 65.5 ms, je použito čítání milisekund a čas je pak prodloužen ještě čítačem programovým (proměnná i). Každých 100 ms se proto “posune” rozsvícená LED v řadě 8 diod připojených na bránu P1.

Mezi přetečením a znovunaplněním čítače uplyne nějaká doba (záleží na konkrétním překladu kódu), proto měřený interval není přesně 1ms. V časově kritické aplikaci by to bylo třeba zohlednit. Druhou možností by bylo využít automatického znovunaplnění čítače (režim 2), kdy lze ale čítač použít pouze jako 8-bitový, což omezuje nastavení intervalu na max. 256µs.

Příklad 3 využití přerušení

#include <reg51.h>
#define T_100US (256-100)

int main(void)
{
    TMOD=0x02; // nastav rezim 2 citace CT0
    ET0=1; // povol preruseni od CT0
    EA=1; // globalne povol zpracovani preruseni
    TH0=T_100US; // nastav hodnotu pro reload
    TR0=1; // spust citac

    while(1) // jen se ceka na provadeni interruptu
        ;
}

void timer0(void) interrupt 1 // obsluha preruseni
{
    static unsigned int pomi=0; // programove prodlouzeni citace
            // MUSÍ byt typu STATIC !!

    if (P1==0x20) // pokud je nastaven bit 5
    T0=~T0; // 5 kHz, strida 1:1

    pomi++;
    if(pomi<1000) // 1000 * 100µ s = 100 ms
        return; // není-li, tak konec obsluhy intu

    pomi=0;
    P1<<=1; // opet “pohybliva” log. 1 na P1
    if(!P1)
        P1=1;

    return;
}

Tento příklad poskytuje stejný efekt, jako příklad 2, navíc využívá piezo-měniče připojeného na pin T0 (je to součást multifunkční brány P3). Vytváří signál s půlperiodou = 100µ s, tedy 5 kHz, což je blízké rezonačnímu kmitočtu měniče. Zvuk se ozývá tehdy, když je aktivní dioda na bitu 5 brány P1.

Pro využití přerušení je nutno provést několik kroků: povolit akceptování daného přerušení (zde bit ET0, každý zdroj přerušení má “svůj” bit), povolit přerušení globálně (bit EA), spustit čítač (bit TR0) a hlavně napsat obsluhu přerušení. Je to funkce typu void bez parametrů a překladači je třeba říci klíčovým slovem interrupt, pro které přerušení je obsluha určena (přerušení 1 je od CT0). Překladač sám řeší úklid proměnných apod.

Režim čítače je 8-bitový s automatickým reloadem (režim 2), čítá se v registru TLx, hodnota pro reload je v THx. Nedochází zde k žádné prodlevě, neboť reload je proveden přímo obvodem čítače okamžitě po přetečení. Ještě je třeba poznamenat, že obsluhou přerušení se nuluje příznak přetečení TFx, což bylo nutno provést “ručně” při programové obsluze v příkladu 2.

Příklad 4 připojení DA převodníku

#include <reg51.h>

int main(void)
{
    P1=0;

    while(1)
    {
        while(P1<0xFE)
            P1+=2;

        while(P1)
            P1-=1;
    }
}

Tento jednoduchý příklad předpokládá připojení DA převodníku na bránu P1 (LSB převodníku odpovídá bitu 0 brány, MSB bitu 7). Generuje se trojúhelníkový průběh s náběžnou hranou dvojnásobně strmou než doběžnou (dvojí inkrement proti jednomu dekrementu). Pozor na podmínku 1. cyklu while, pokud by bylo <0xFF, podmínka se nikdy nesplní, neboť P1 je jako datový typ unsigned char, který přetéká 0xFF -> 0x00. Nelze ani testovat na přetečení (==0), pak by vznikl nechtěný zákmit do hodnoty 0. Řešením by zřejmě mohlo být použití cyklu do - while. Kmitočet výsledného analogového signálu závisí na překladu kódu, pro přesné časování je opět vhodné použít čítač.

Příklad 5 sériová komunikace

#include<reg51.h>
#include<stdio.h> // navíc funkce vstupne/vystupni

int main (void)
{
    char a;

    SCON=0x52; // nastaveni registru seriove komunikace
    TMOD=0x20; // nastaveni rezimu citace CT1
    TR1=1; // spust citac CT1
    TH1=0xF3; // prenosova rychlost 2400 Bd

    puts("AHOJ"); // posli retezec

    while(1)
    {
        a=_getkey(); // precti znak (ceka se na nej)
        P1=a; // pro kontrolu na branu P1
        putchar(a); // a posli zpet
    }
}

V tomto příkladu se vypíše po sériové lince úvodní pozdrav a pak se jen přijaté znaky posílají zpět. Standardní funkce vstupu a výstupu deklarované v stdio.h pracují se sériovým kanálem. Je proto nutné použít nějaký terminál (resp. terminálový program na PC), který ukazuje, co se na linku poslalo a odesílá na ní znaky podle stisku kláves. Pro přehlednost je v terminálu vhodné si vypnout lokální echo, jinak se ukazují znaky odchozí i příchozí.

V registru SCON se nastavují parametry sériové komunikace, zde 8-bitový asynchronní přenos. Výše již bylo řečeno, že čítač CT1 generuje přenosovou rychlost, proto je nutné ho nastavit do módu 2 (8-bitový reload) a vložit hodnotu 0xF3 pro rychlost komunikace 2400 Bd. Nesmíme zapomenout čítač CT1 spustit. Pak se pošle řetězec funkcí puts(...) a ve smyčce se přijímají znaky z linky a posílají okamžitě zpět. Funkce typicky čekají na příjem/odeslání znaku. Pro příjem je použita funkce _getkey(), protože standardní funkce getchar() provádí “echování” přijatého znaku a _getkey() nikoliv.

Od příjmu i vyslání znaku lze vyvolat přerušení, standardní obsluha toho však nevyužívá, pouze testuje příznakové bity RI (přijat znak) a TI (odeslán znak).

Příklad 6 připojení LCD displeje

#include <reg51.h>
sbit EN=P3^3; // ridici bit displeje (zapis)
sbit RS=P3^5; // ridici bit, registr/data

void sendToDisp(char c, bit reg) // posli data do displeje
{
    int i; // promenna pro citac

    RS=reg; // pin RS (registr/data)
    P1=c; // data na P1
    EN=1; // zapisovy puls hrana L-H
    EN=0; // hrana H-L

    if((reg==0)&&((c&0xFC)==0x00)) // podle druhu dat
        for(i=0;i<500;i++) // dlouhe cekani
            ;
    else
        for(i=0;i<30;i++) // kratke cekani
            ;
    return;
}

int main(void)
{
    char *cptr,*s="Ahoj DISPLEJ";

    EN=0; // prvotni nastaveni rizeni LCD
    sendToDisp(0x01,0); // Display Clear
    sendToDisp(0x06,0); // Entry Mode Set
    sendToDisp(0x0f,0); // Display, Cursor, Blinking On/Off
    sendToDisp(0x38,0); // Set HW configuration
    sendToDisp(0x80+0,0); // nastav pozici kurzoru na 0

    for (cptr=s;*cptr;cptr++) // po znacich posli retezec
        sendToDisp(*cptr,1); // jako data

    while(1)
        ;
}

Na vývojových deskách s 8051 je připraven konektor pro LCD displej 2 řádky po 16 znacích (typ LM 16255). Datové signály displeje jsou připojeny k bráně P1, řídící signály pak na bity brány P3. Stačí 2 řídící signály, RS určuje, zda se bude jednat o operaci s daty (RS=1) nebo o operaci s řídícími registry (RS=0). Další řídící signál je EN, kdy EN=1 určuje aktivní data. Signál R/W, který určuje, zda jde o zápis nebo čtení, je nastaven trvale na zápis, takže z displeje nelze číst. To sice trochu vadí v kontrole, zda je požadovaná operace dokončena (příznak BUSY), ale protože jsou známo nejdelší doby provedení operací, je možné čekat určitý čas programově. Téměř všechny operace trvají 40µs, pouze operace inicializační (povely 0x01 a 0x02) trvají 1.64 ms. To je zohledněno ve funkci sendToDisp(...), kde dobu čekání určuje podmínka. Takže pro zápis povelu (reg==0) hodnoty 0x01 a 0x02 je použito delší čekání. Hodnoty jsou odhadnuty empiricky tak, aby dostačovaly pro krystal 12 MHz. Podmínka by mohla srozumitelněji vypadat: if ((reg==0) && ((c==1)||(c==2))) ...

V hlavním programu se pak musí nejprve inicializovat displej, aby vůbec zobrazoval. Doporučuji použít tyto hodnoty, jinak je samozřejmě možno si je upravit v souladu s dokumentací. Zápis pozice kurzoru má v povelu nastaven nejvyšší bit (D7), ostatní bity určují adresu v paměti. První řádek začíná na adrese 0x00, druhý na adrese 0x40. Navíc je displej vybaven kromě vlastního generátoru znaků i možností uživatelsky definovaných znaků (tzv. CG RAM).

Speciální datový typ sbit je možností, jak elegantně pracovat s jednotlivými bity registrů, které tuto bitovou adresaci umožňují. Je to datový typ ekvivalentní typ bit, pouze je pevně svázán se “svým” registrem (zde P3) a pozicí v něm (bit 3 a 5).