Jak napsat program pro 8051 a klony (např. Atmel 89C2051).

Jelikož píši téměř veškerý software pro chipy rodiny 51 v assembleru, nebudu se zde žádnými vyššími programovacími jazyky podrobně zabývat. To však neznamená, že by člověk znalý pouze například BASICu neměl pochopit o co tu jde. Tento materiál nemá za úkol nahradit učebnici ani popsat všechno do detailu. Takových materiálů je na webu dost. Úkolem tohoto materiálu je dát začátečníkovi základní představu o tom, jak se to dělá a ne popisovat instrukční soubor. Rodinou 51 se rozumí např. 80C31, 80C32, 89C51, 89C52, 89C55, 89C2051 apod.

Co je to vlastně programování ?

Tato na pohled triviální otázka, může úplným začátečníkům možná říci více, než zbytek tohoto článku. Pod pojmem programování se u jednochipů rozumí buď napsání programu, který poběží v jednochipu, nebo jeho vložení do jeho paměti, tedy někdy můžete slyšet také vypálení. Tady se budeme zabývat tím prvním, tedy napsáním toho, co se pak vloží do paměti jednochipu, nebo nějaké externí paměti programu. Pokud Vás teď budu nudit, tak zbytek odstavce klidně přeskočte. Jednochip je prakticky velice hloupé zařízení (stejně jako počítač do jehož monitoru se právě díváte), které neumí nic jiného, než přehazovat hodnoty od někud někam a popřípadě s nimi provést nějakou matematickou operaci. Celý program není nic jiného, než jakýsi návod, co se kam má přehodit, co od čeho odečíst nabo třeba přičíst a to v pořadí, jak je to napsáno pod sebou. Aby to nebylo tak jednoduché, může se v tomhle návodu (programu) přeskakovat a skákat a to podle něčeho. Představte si, že jdete nakoupit a máte na lístku napsáno:

  • Chleba
  • Mléko
  • Máslo
  • Brambory
  • Kaviár

A teď se vlastně budete řídit tím programem. Tedy to koupíte. Tak tohle je přesně přístup vyšších programovacích jazyků, kde již někdo za Vás vyřešil, že je nejprve nutné mozkem nařídit jedné noze jít mírně pod těžiště a druhou taky nastavit a kterým svalem se má co udělat, aby jste vstali ze židle. Teď musíte udělat krok, což se zase dá rozepsat jako podprogram a pokud jste došli až k botám, je nutné si je obout a zavázat, což je docela složitý podprogram po návratu z něhož je nutné se narovnat. Teď si asi říkáte kde je ten chlaba. No asi nechcete abych takhle popsal několik set kilo, takže je prostě nutné, vysvětlit jednochipu naprosto všechno, jak to udělat, nebo použít nějaký vyšší jazyk, kam už to někdo napsal všechno a Vy si jen vyberete, tedy cestu do obchodu vynecháte a rovnou koupíte ten blbej chleba. Nevýhodou je, že když tam autor kompilátoru vyššího jazyka nenapsal návod na kaviár, budete ho muset smontovat z másla a brambor, které tam jsou a to asi nebude zrovna to nejlepší řešení i když to někomu může chutnat jako kaviár. A pokud mu to nedá, použije pár instrukcí v assembleru a jsme tam kde jsme byli, tedy u assembleru. Takže i pokud se rozhodnete psát programy pro jednochipy ve vyšším jazyce, není na škodu znát alespoň základy assembleru. A abych se vrátil k tomu odskakování podle něčeho, představte si na lístečku, brambory a pod nimi v závorce rýže, což je pro Vás kus kódu, říkající, že pokud budou brambory, následující instrukci o koupi rýže vynechte, pokud brambory nebudou, provedeme následující instrukci a koupíme rýži. Nebo pokud se má totéž nakupovat každý den, pak je to vlasně smyčka a po nakoupení všeho a jednodenní pauze skočíte na začátek lístečku a všechno se opakuje.

Jednochip musíme naprogramovat tak, aby se na nožičky jednochipu dostaly takové úrovně, které nařídí třeba LCD displeji s řadičem něco vypsat. Musíme tedy nastavovat piny bran jednochipu, na kterých je display přivázán tak, aby posloupnost na pinech byla pro display pochopitelná. Jako každá součástka, má i takový LCD display jakési minimální časy, které musíme displeji poskytnout, aby námi nařízené operace stihl. Tady je tedy zase nutné podle použitého taktování jednochipu (krystalu), vyrobit správně dlouhé prodlevy mezi přehazováním stavu pinů vedoucích do displeje. Je také možné to napsat pro nejrychlejší možné taktování (krystal) a pak se smířit s tím, že u pomalejšího všechno déle trvá, nebo použít nějakou konstantu, kterou vždy změníte pro konkrétní taktování. Můžete také využít vyšší jazyk, který to už umí, nebo si vzít na webu kus kódu, který to dělá a upravit si jej pro své potřeby a používat jej. Nejlepší je samozřejmě si vzít PDF soubor od ovládané periférie a napsat si to podle svých představ. Trvá to nejdéle, ale víte přesně (alespoň si to můžete myslet), kdy jak proč a co to dělá. Použitím vyššího jazyka ztrácíte možnost přesně program časovat podle doby trvání instrukcí, tedy kdy se má co dít přesně na mikrosekundy nebo i přesněji a to je u jednochipových aplikací někdy dost důležitá věc. Možná to některý kompilátor vyššího jazyka umí, jen o tom nevím. Některé aplikace, které je nutné velice rychle napsat a je nám jedno, jak rychle se to provádí a kolik to vezme paměti je asi vhodné řešit ve vyšším jazyce, ale pokud se chcete jednochipy zabývat trochu seriózněji, vezměte si kus kompilátoru pro assembler 8051 kdekoli na webu. Prostě jednochip není PC a že ho bude uživatel programovat a ne užívat je pro mne hrůzná myšlenka. Pokud si uvědomíte, co všechno může dělat jednochip s ubohými 2kB paměti, musí Vám být z PC nevolno.

Architektura 8051 z pohledu programátora.

Chipy řady 51 obsahují paměť programu buď přímo na chipu (89C51 - 4kB FLASH) nebo neobsahují paměť programu žádnou (80C31) a pak je nutné ji připojit externě. Existují jednochipy i ve verzi OTP, která se dá vypálit jen jednou provždy - pro začátky by to asi byla nejdražší varianta, 89C51 nebo 89C2051 je pak varianta začátku nejlevnějšího díky elektricky mazatelné paměti programu. Dále jednochipy řady 51 obsahují interní paměť dat 128B přímo na chipu (popř. 256B řada 52). Co se periférií týče, jsou plnokrevné 51 vybaveny čtyřmi osmibitovými branami P0 až P3. Nutno podotknout, že připojením externí paměti dat nebo programu se brány P0 a P2 používají pro adresovou a datovou sběrnici a bity 6 a 7 brány P3 se využívají jako signály /RD a /WR pro externí paměť dat - pokud je připojena. Přímo lze zapojit až 64kB externí paměti programu a max. 64kB externí paměti dat (neuvažujeme-li stránkování paměti pomocí pinů I/O bran). Pokud se externí paměti nevyužívají, pak jsou brány P0 až P3 libovolně využitelné. Za zmínku jistě stojí implementace sériového rozhraní a přerušení externím signálem. Toto sice nesouvisí přímo s programováním, ale je nutné vědět, jak je konkrétní zapojení realizováno pro jeho ovládání softwarem.

Special Function Registers

Tyto registry jsou dostupné, stejně jako interní paměť dat a ovlivňují chování jednochipu. Zde se nastavuje, co má být na výstupu bran, z nich se čtou vstupy na branách, používají se pro nastavování sériového kanálu, povoluje se přerušení, nastavují časovače a uchovávají příznaky například přetečení apod. Tyto registry jsou umístěny nad interní pamětí dat, tedy od adresy 80H, přičemž ne všech 128B je využito pro nějaký registr. U některých klonů 51 jsou právě ve volných místech umístěny další SFR, které ovlivňují další chování procesoru (TS80C31X2). Do těchto registrů se zapisuje a čte z nich stejně jako je tomu u interní paměti dat. Tedy např. jednoduše instrukcí MOV. Pokud pak tedy chceme, aby na bráně P1 byla hodnota 05Ah, pak napíšeme jen MOV P1,#05AH ,protože hodnota P1 je u assemblerů obsažena buď v souboru definic, připojeného na začátek programu, nebo si můžeme přiřazení napsat sami a nebo psát přímo číslo SFR, tedy napsat MOV 90H,#05AH, kde 90H je právě adresa P1. Teď se možná někdo ptá, co tam znamená ten dvojkřížek. Inu ten zamená, že jde o hodnotu a ne o registr tohoto čísla. Pokud napíšeme hodnotu bez křížku, což se v zápalu boje s assemblerem lehce stane, zapíše se místo hodnoty do registru obsah jiného registru a nejlepší na tom je, že pokud je nechtěně použitý registr zrovna ve vhodné hodnotě, může program i pracovat, protože dostal co jsme chtěli, ale příště se může chovat naprosto záhadně. Je třeba si to opravdu hlídat, špatně se to hledá a dokáže to dost zkazit radost z používání jednochipů. Možná Vás teď napadlo, kde je umístěných oněch zbývajících 128B interní paměti dat u rodiny 52, která má 256B interní paměti dat? Má se to tak, že k této paměti nad 128B, tedy od 80h výše se dá přistupovat pouze instrukcemi s nepřímým adresováním. Takže pokud chceme zapsat do brány P0, pak je kód MOV 80h,#125 (například hodnota 125 decimálně) a pokud chceme zapsat data do adresy 80h a ne do brány P0, pak použijeme MOV R0,#80H a MOV @R0,#125. Tady je na místě zase vysvětlení, že zavináč zde znamená nepřímé adresování, tedy do interní paměti s adresou umístěnou v registru R0 zapiš 125 decimálně no a do registru R0 jsme si předchozí instrukcí uložili adresu tohoto místa, tedy hodnotu 80h. Vím, že to může vypadat trochu zmateně, pokud jste ještě s assemblerem neměli co do činění, ale v tom případě to bude zřejmě chtít číst několikráte a možná bude všechno jasnější.

Popis SFR - tedy který je k čemu.

P0,P1,P2 a P3

jsou registry určené k ovládání bran. Pokud do nich zapíšeme nějakou hodnotu, pak se tato hodnota objeví na výstupu těchto bran. Tyto registry jsou adresovatelné i po bitech, takže není třeba vždy řídit celou bránu, ale je možné nahodit a shodit jen určitý konkrétní bit. Tedy shodit k 0 instrukcí CLR P1.2 a do 1 nastavit SETB P1.2 - tady jsme ovládali druhý bit brány P1. P1.2 tady odpovídá adrese 92h, jen pro ilustraci, tedy P1.2 je adresa toho konkrétního bitu. Protože jsou z hlediska elektroniky brány zapojeny jako s otevřeným kolektorem s PULL-UP odporem asi 50k, a vstupy jsou vedeny do procesoru přímo ze vstupů, je jasné, že lze pin brány nahozený do 1 beztrestně uzemnit a čtením brány pak přečteme opravdový stav pinu a ne obsah registru. Naproti tomu připojit napájecí napětí např. +5V na pin shozený k zemi je trestné a výše trestu je přímo úměrná ceně procesoru, nebo minimálně odnětí brány procesoru. A další věc, která si zaslouží pozornost. Zatímco instrukcí MOV A,P1 získáme do A stav na pinech procesoru, instrukce jako např. ANL P1,#0Fh bude pracovat s hodnotou uloženou v registru procesoru a ne se stavy na pinech, jinými slovy použije k AND operaci s číslem 0Fh to, co jsme do P1 naposledy vložili, a neovlivní to stažení pinů z vnějšku k zemi. Platí to pro instrukce, které musí bránu jako by nejprve přečíst a pak zpět zapsat. Tedy např. instrukce jako např. JB nebo JNB odskakující v závislosti na stavu bitu berou hodnotu bitu jako skutečný stav brány, tedy na obsah registru se neohlíží. Všechny piny P0 až P3 jsou po resetu v 1, tedy vysoké úrovni. Je to z důvodu, že jsou připraveny pro čtení a neublíží tak něčemu, co je k nim připojeno výstupem. Že lze číst vstupy pouze z pinů nahozených programem do 1, je zřejmě. Pinu staženému k 0 (je prakticky uzemněn) totiž nijak 1 z vnějšku nevnutíte. Neodpustím si jeden detail pro začátečníky i když spíše z hardwarového hlediska. Pokud připojujete na nějaký I/O pin tvrdé napětí třeba z výstupu nějaké logiky, pak nespoléhejte na to, že výstup nikdy neshodíte programem k 0, čímž by došlo ke zničení procesoru proudem do země. Napsat chybu do programu není tak těžké jak by se zdálo a navíc procesor, který je napájen napětím mimo povolené meze, může dělat cokoli a bránu klidně stáhnout jen v důsledku vyjetí z programu do nepatřičných mezí. Ochranný odpor rozumné hodnoty mezi vstup a výstup, je-li to možné, nebo ovládání pinu stahováním k zemi přes tranzistor i když se to zdá zbytečným, nemusí být vyhazováním peněz. Jen je třeba si uvědomit, že přes příliš velký odpor již bránu nemusíme do země stáhnout (brány mají PULL-UP odpor 50k mezi +Vcc a pinem).

ACC - A registr, tedy accumulator.

Jde o registr, který se využívá pro aritmetické operace jako je sčítání apod. Je to asi nejpoužívanější registr vůbec. Dosti často se používá k předávání parametrů pro funkce nebo k vracení výsledků, ale to už záleží jen na tom, jak si to napíšete, nebo jak to napsal autor Vámi převzaté funkce. Pokud se někdo podiví nad zápisem XRL A,ACC , pak je to jen nulování registru A, protože XORnutím jakýchkoli dvou stejných čísel dostaneme výsledek 0. Je to tedy jen možnost, jak ušetřit byte v programu oproti MOV A,#0 .

B - B registr.

Ten je užíván jako druhý registr pro násobení, nebo dělení. Instrukce MUL AB vynásobí A*B a výsledek násobení dvou osmibitových čísel (zde bez znaménka), může být větší než 255. Proto se výsledek uloží do A nižší byte výsledku a do B vyšší byte. Při dělení DIV AB zase vydělí A/B a výsledek uloží do A a zbytek po dělení do B. Jinak se dá uchovávat v registru B cokoli Vás napadne, tedy jej volně využívat.

DPTR - Data pointer, tedy ukazatel.

Skládá se ze dvou bajtů DPH vyšších 8 bitů a DPL pro nižších 8 bitů. Využívá se jako ukazatel do paměti. Takže například MOV DPTR,#1000H a MOVX A,@DPTR naplní registr DPTR hodnotou 1000h a druhou instrukcí přeneseme obsah externí datové paměti na adrese dle DPTR (zde 1000h) do registru A. Jelikož existuje i instrukce INC DPTR, která zvýší hodnotu celého 16-ti bitového DPTR o 1, můžeme se velice pohodlně pohybovat s ukazatelem po paměti a vybírat její obsah třeba ve smyčce. Registr DPTR se dá využít i jako normální interní datová paměť. Lze jím třeba počítat počet smyček, nechávat si v něm hodnoty apod. - zkrátka jako u jakéhokoli volně použitelného registru.

IE - Interrupt enable.

Zkrátka která přerušení má procesor akceptovat. V podstatě, je-li nějaké přerušení povoleno a nastane (třeba sériový kanál přijal byte), pak procesor odskočí do podprogramu na adrese odpovídající typu přerušní, nebo spíše adrese odpovídající události, která jej způsobila.

IE je popsán bity:

IE.7 EA Je-li v 1, pak globálně povoluje přerušení
IE.4 ES Povolení přerušení od sériové linky
IE.3 ET1 Povolení přerušení přetečením časovače 1
IE.2 EX1 Povolení přerušení externím zdrojem přerušení INT1
IE.1 ET0 Povolení přerušení přetečením časovače 0
IE.0 EX0 Povolení přerušení externím zdrojem přerušení INT0

Kam se konkrétně odskakuje při jaké události ?

Adresa 00h RESET
Adresa 03h /INT0 (externí přerušení stažením pinu /INT0 k zemi)
Adresa 0Bh TIMER0 (přetekl časovač 0)
Adresa 13h /INT1 (externí přerušení stažením pinu /INT1 k zemi)
Adresa 1Bh TIMER1 (přetekl časovač 1)
Adresa 23h Sériový kanál odeslal či přijal byte

Obsluhou přerušení se vynuluje příznak, oznamující přerušení. Pouze u sériového kanálu jej musíme vynulovat programově, protože jsou 2 (TI a RI) a jen jejich čtením poznáme, zda se přerušení vyvolalo odesláním či přijetím bajtu.

IP - Interrupt priority.

Priorita přerušení je daná. Pokud tedy přijde významnější přerušení, přeruší se méně významné během jeho obsluhy.

Obsahuje bity:

IP.0 PX0 Externí 0
IP.1 PT0 Timer0
IP.2 PX1 Externí 1
IP.3 PT1 Timer1
IP.4 PS Sériovy port

Po resetu jsou všechny bity nulovány a jejich nastavením do 1, zvýšíte prioritu konkrétního přerušení. Priority se stejně nastaveným bitem, jsou seřazeny od nejvyšší po nejnižší tak jak jsou uvedeny popořadě. Zvedneme-li tedy některý prioritu nastavující bit do 1, je jeho priorita vyšší než všech ostatních v 0, ale je v odpovídajícím pořadí (jak je výše uvedeno) s ostatními v 1.

PCON - Power control register.

PCON bit7 SMOD Po resetu v 0, při nastavení do 1, zvýší přenosovou rychlost sériového portu na dvojnásobek.
PCON bit3 GF1 General purpose flag bit - tedy pro libovolné použití.
PCON bit2 GF0 General purpose flag bit - tedy pro libovolné použití.
PCON bit1 PD Power down bit. Nastavením přejde do power down módu, není u neCMOS 51 implementován.
PCON bit0 IDL Idle mode bit. Nastavením přejde do power idle módu, není u neCMOS 51 implementován.

Nastavením PD a IDL zároveň má PD přednost. Je jasné, že SMOD se netýká řízení spotřeby. Tento registr není bitově adresovatelný, tedy je třeba jej naplnit celý jedním bajtem v kuse. PD a IDL jsou cestou, jak například u jednochipu napájeného z akumulátorů snížit spotřebu v době, kdy od něj nic nechceme.

PSW - Program status word.

PSW.7 CY Carry flag
PSW.6 AC Auxiliary carry
PSW.5 F0 Flag0 - pro volné použití
PSW.4 RS1 Register bank selector bit1
PSW.3 RS0 Register bank selector bit0
PSW.2 OV Overflow flag
PSW.1 - Libovolně použitelný
PSW.0 P Parity flag. Každá instrukce jej nastaví dle počtu jedniček v A.

Pokud jste již měli co do činění s jiným assemblerem, pak Vám možná chybí Z (zero) příznak. Přitom instrukce JZ (skoč při nule) existuje. Je to tím, že nezkoumá příznak, ale skáče podle toho, jestli je v registru A nula nebo ne. Takže klidně můžete přenést do A nějakou hodnotu z interní datové paměti, třeba MOV A,POM , kde POM je například bajt na adrese 30H (podle toho, jak jsme si ho nadefinovali) a pak podle jeho hodnoty skákat JZ NEKAM , kde NEKAM je návěší někde v programu napsané na začátku řádku s dvojtečkou a určuje kam se skočí, když A je v 0. Není-li A v 0, pak se nikam neskočí a pokračuje se dále. Existuje i instrukce JNZ, která skáče jen když A v nule není, tedy přesně opačně. Naproti tomu instrukce jako JC a JNC testují příznak Carry, takže napsáním SETB C a následným JC NEKAM se nastaví carry do 1 a pak, když je carry (což tady je) se skočí NEKAM.

Přesný popis, které instrukce ovlivňují které příznaky hledejte v popisu instrukčního souboru.

RS1 a RS0 určují, kde se v interní paměti procesoru nachází registry R0 až R7. Pokud jsou oba v 0, pak se nachází R0 hned na počátku paměti na adrese 0, a další banky následují vždy po 8 bajtech. Pozor na SP. Po resetu procesoru se implicitně za banku 0 ukládají hodnoty do zásobníku, takže pokud chcete banky přepínat a používat i zásobník, pak je nejlépe zásobník přesunout někam výše.

SBUF - Serial data buffer.

V tomto registru najdete bajt, který přijal sériový port, nebo uložením bajtu do tohoto registru jej odešlete sériovým portem. O tom, že byl bajt přijat či odeslán se můžete dozvědět prozkoumáním SCON registru.

SCON - Serial port control register.

Slouží k nastavení sériového portu a k informaci o přijetí a odeslání bajtu.

SCON.7 SM0 Serial port mode specifier, tedy nastavení módu sériového portu.
SCON.6 SM1 Serial port mode specifier, tedy nastavení módu sériového portu.
SCON.5 SM2 Povoluje multiprocesorovou komunikaci.
SCON.4 REN Nastavením se povolí příjem dat sériovým portem.
SCON.3 TB8 Pokud je nastaven 9bit mód, pak toto je devátý bit pro odeslání.
SCON.2 RB8 Pokud je nastaven 9bit mód, pak toto je devátý přijatý bit.
SCON.1 TI Nastaví se po odeslání bajtu sériovým portem. Je nutno jej mazat softwarem.
SCON.0 RI Nastaví se po přijetí bajtu sériovým portem. Je nutno jej mazat softwarem - tedy nemaže se sám ani obsluhou přerušení.

Módy sériového portu:

Mode0 SM0=0 SM1=0 posuvný registr s rychlostí Fosc/12
Mode1 SM0=0 SM1=1 8-bit UART s nastavitelnou rychlostí přenosu
Mode2 SM0=1 SM1=0 9-bit UART s rychlostí přenosu Fosc/64 nebo Fosc/32
Mode3 SM0=1 SM1=1 9-bit UART s nastavitelnou rychlostí přenosu

Z uvedených módů je dle mého názoru nejužívanější mód 1, kdy jde vlastně o komunikaci známou ze sériových portů PC, v tomto případě 8N1, tedy bez parity s jedním stopbitem. Jen je nutné převést CMOS úrovně na úrovně RS232C například pomocí MAX232, ICL232 nebo jiným převodníkem.

A pokud Vás ještě zajímá, jak rychle to bude komunikovat, tedy na kolika bps, pak to spočítáte dle vzorce:

BaudRate=((SMOD+1)*Fosc)/(32*12*(256-TH1))

TH1 je zde konstanta, řídící rychlost přenosu, nebo spíše dobu do přetečení timer1, který svým přetékáním řídí sériový kanál, Fosc je kmitočet oscilátoru, nebo krystalu chcete-li a SMOD je hodnota bitu, který když je nastaven, zvýší rychlost komunikace na dvojnásobek.

Bit TI lze nejjednodužšeji použít tak, že jej smažeme, vyšleme bajt naplněním registru SBUF a čekáme na jeho nastavení instrukcí JNB, takže po odeslání bajtu program pokračuje dále. Příjem lze realizovat čekáním na nastavení bitu RI, následným přečtením SBUF a pak bit RI smažeme pro rozpoznání dalšího příjmu. Problém je ovšem to, že pokud do sériového kanálu bajt nepřijde, program cyklí navždy a nic jiného se neděje, tedy mimo přerušení. U jednodužších programů, přepočítaných dle doby instrukcí na přesný čas, tedy tam kde se smyčkuje v programu vždy za určitý přesný čas, lze testovat RI v těchto časech (pokud nejsou delší než přijetí jednoho bajtu) a při nastavení RI přijmout bajt, doplnit časy na přesnou dobu smyčky a pokračovat v programu. Toto ale doporučuji opravdu jen pokud Vám záleží na přesné době provádění částí programu, jinak je jistě výhodnější použít přerušení, takže procesor po přijetí bajtu odskočí do podprogramu na adrese příslušné pro přerušení od sériového portu a tam se vyřeší zpracování bajtu, smazání RI a instrukcí RETI návrat do hlavního programu.

SP - Stack pointer.

Adresa bajtu, kam se uloží instrukcí PUSH nebo odskokem do podprogramu hodnota. V SP je adresa o jednu nižší, než kam se skutečně bajt umístí, protože SP se před vložením inkrementuje a dekrementuje se až po vyzvednutí hodnoty. Pozor, po resetu je SP=7, tedy při prvním použití přepíše hodnotu R0 v bance 1. Pokud banky nepřepínáte a víte jistě, že nebudete potřebovat zásobník delší než do adresy 20h, kde začínají bitově adresovatelné registry pro všeobecné použití, nebo používáte interní paměť dat až někde výše než skončí zásobník je všechno fajn. Jinak je lepší přehodit zásobník naplněním hodnoty do SP. Je také dobré vědět, že u klonů řady 52 lze umístit SP i na vyšší adresy než do 7Fh, tedy využít pro tento účel druhých 128B interní paměti dat a to např. nastavením SP=#7Fh. K horním 128B je přístup ztížen tím, že je lze adresovat jen nepřímo. Takže umístit tam zásobník, pokud horních 128B paměti nevyužíváte může být velice příjemné. Je to dost dobrý důvod doplatit 2Kč a koupit místo 80C31 typ 80C32, pokud Vám vyhovuje používání externí ROM paměti. A ještě malá poznámka. Pokud Vám nedovolí kompilátor přeložit PUSH A, zkuste PUSH ACC . Nezapomeňte, že odskoky do podprogramu jako např. LCALL naplní zásobník dvěma bajty, tedy nejprve nižšími a pak vyššími osmi bity adresy.

TCON - Timer/counter control register

Registr pro řízení časovačů/čítačů.

TCON.7 TF1 Nahozením do 1 oznámí, že nám přetekl timer1.
TCON.6 TR1 Nahozením do 1 povolíme timer1, shozením do 0 ho zastavíme.
TCON.5 TF0 Nahozením do 1 oznámí, že nám přetekl timer0.
TCON.4 TR0 Nahozením do 1 povolíme timer0, shozením do 0 ho zastavíme.
TCON.3 IE1 External interrupt 1 edge flag. Nastaví se při shození pinu /INT1 do nuly.
TCON.2 IT1 Interrupt 1 type control bit. Jinými slovy si zvolíme, jestli k externímu přerušení dojde sestupnou hranou nebo nízkou úrovní na pinu /INT1.
TCON.1 IE0 Totéž jako IE1, ale pro /INT0
TCON.0 IT0 Totéž jako IT1, ale pro /INT1

TMOD - Timer/counter mode control register.

Nastavení časovačů/čítačů. Není adresovatelný po bitech, tedy je nutné nastavit celý bajt současně.

Timer1

TMOD bit7 GATE Je-li v 1, pak běží čítač/časovač jen je-li pin /INT1 ve vysoké úrovni (a je nastaven TR1=1 v TCON), je-li v 0, pak běží jen v závislosti na TR1.
TMOD bit6 C/T Přepínání mezi čítačem a časovačem. Je-li 0, pak pracuje jako časovač, je-li 1 pak jako čítač.
TMOD bit5 M1 Výběr módu 1.
TMOD bit4 M0 Výběr módu 0.

Timer0

TMOD bit3 GATE Je-li v 1, pak běží čítač/časovač jen je-li pin /INT0 ve vysoké úrovni (a je nastaven TR0=1 v TCON), je-li v 0, pak běží jen v závislosti na TR0.
TMOD bit2 C/T Přepínání mezi čítačem a časovačem. Je-li 0, pak pracuje jako časovač, je-li 1 pak jako čítač.
TMOD bit1 M1 Výběr módu 1.
TMOD bit0 M0 Výběr módu 0.

Módy:

mód=0 M1=0 M0=0 13-bit časovač - kompatibilní s 8048.
mód=1 M1=0 M0=1 16-bit časovač/čítač THx a TLx tvoří 16-bit hodnotu (za x si dosaďte 0 nebo 1).
mód=2 M1=1 M0=0 8-bit Auto-reload časovač/čítač - po přetečení se naplní TLx použité k čítání hodnotou z THx.
mód=3 M1=1 M0=1 TL0 je jeden 8-bit časovač/čítač ovládaný bity pro Timer0 a TH0 je druhý 8-bit ovládaný bity pro Timer1. V tomto módu je Timer1 zastaven - tedy spíše hodnoty TL1 a TH1.

TH0, TL0, TH1, TL1

jsou jak jste již jistě pochopili hodnoty v čítačích/časovačích. Časovače vlastně pracují tak, že každých 12 period oscilátoru je zvýšena jejich hodnota o 1. Čítač počítá impulsy na pinu T0 nebo T1. K přetečení dojde v okamžiku, kdy se dopočítá z poslední možné hodnoty (255 u 8-bit a 65535 u 16-bit) zpět do 0. Přetečením dojde k nastavení příznaku přetečení časovače (každý má svůj) a je-li to povoleno, pak i k vyvolání přerušení. Pokud použijete mód 2, tedy auto-reload, pak po každém přetečení je zpět naplněn TLx hodnotou z THx, tedy k přetečení dojde tak často, jak je zvolena hodnota v THx.

Pokud používáte sériový kanál s volitelnou rychlostí přenosu, třeba do PC, pak se využívá timer1 jako časovač, řídící přenosovou rychlost sériového kanálu a pracuje v módu 2, který jsem již popsal. Samozřejmě lze i jinak. U procesorů řady 52 existuje ještě timer2, tedy jeden navíc, oplývající dalšími možnostmi a co se mi zdá důležité, dovoluje řídit rychlost sériového přenosu namísto timeru1 a to 16-bit konstantou, tedy dá se docílit i při nepříliš vhodném kmitočtu použitého krystalu lepší přesnosti standardních rychlostí sériového portu.

Proč je to čítač i časovač ? Protože může také čítat a to impulsy přivedené na vstup T0 nebo T1 procesoru. To jestli čítá impulsy nebo oscilátor, se zvolí bitem C/T příslušného timeru v TMOD. Můžeme tedy např. měřit jedním timerem dobu a druhým počet pulsů za tuto dobu.

Asi Vás již napadlo, že by se také dalo na /INT0 přivádět delší kladné impulsy a gatovat (povolovat) jimi timer0 v módu časovače, takže pokud si ještě zapneme přerušení od /INT0, změří nám časovač délku impulsu (při 12MHz krystalu odpovídá hodnota mikrosekundám) a vyvolá přerušení po shození /INT0 do 0, tedy po skončení pulsu. Jen musíme vždy timer0 vynulovat vždy před měřením. Co se týče kódu, prakticky se jenom nastaví SFR a ostatní už zařizuje hardware procesoru, tedy až na obsluhu přerušení ve které můžeme vynulovat onen timer0. Abychom zabránili vícenásobnému vyhodnocení přerušení, můžeme také přepnout bitem IT0 v TCON detekci přerušení na sestupnou hranu, tedy jeden impuls, jedno přerušení. Timery jsou velice silným nástrojem. Například pokud spouštíte monostabil s délkou pulsů řízenou termistorem jedním pinem procesoru a výstup připojíte na /INT0, pak jen pouhým nastavením timeru0 jak bylo výše popsáno, vynulováním TH0 a TL0 a pulsem na pin spouštění onoho monostabilu změříte teplotu pouhým vyčkáním ve smyčce na shození pinu P3.2 (/INT0 a P3.2 jsou tentýž PIN) nebo obsluhou přerušení (je jen nutno si přepočítat dobu pulsu na teplotu). Může se to zdát složité, takže pokud se Vám to zdá nepochopitelné, vůbec o tom neuvažujte, to přijde časem samo.

Ještě budeme potřebovat vědět, které bajty interní paměti můžeme použít pro své proměnné.

Tedy:

  • Adresa 0h až 1Fh je první až čtvrtá banka registrů R0 až R7 a také podle nastavení po resetu se od 8h používá pro ukládání do zásobníku.
  • Adresa 20h až 2Fh jsou volně využitelné registry, jsou bitově adresovatelné, tedy lze nastavit každý bit zvlášť bez vlivu na ostatní bity v registru.
  • Adresa 30h až 7Fh jsou volně využitelné registry, jednotlivé bity registrů nelze nastavovat každý zvlášť, ale jen celý bajt najednou.
  • Adresa 80H až 0FFh je jen u řady 52 (32) a vyšších a registry jsou adresovatelné jen nepřímo, nebo lze použít pro zásobník.
Takže bychom mohli začít psát program.

Není přece třeba si všechno pamatovat, když to nevím, tak si to přečtu.

Nejdříve by ale bylo fajn si stáhnout nějaký kompilátor. Slyšel jsem, že dobrý programátor píše ve vyšším jazyce, lepší v assembleru a nejlepší to už rovnou píše zazipované. Takže pokud chcete místo jmen instrukcí psát čísla prosím, ale pak nechápu proč tohle čtete. Dobrý assembler je MLASM51, pro jehož použití Vám stačí 286 s DOSem (tedy dle mne je super a je to vlastně kromě textového editoru to jediné, co potřebujete k vytvoření HEX souboru, který vložíte do jednochipu). Jak ten HEX soubor do jednochipu dostat popíšu snad příště, teď potřebujeme vytvořit to, co do jednochipu vložíme. Pokud nechcete číst manuál přiložený k MLASM51, stačí Vám vědět, že kompilace se spustí ASM51 POKUS.ASM , kde POKUS.ASM je soubor se zdrojovým kódem, který jsme napsali. Pro jednoduchost si nakopírujte soubory ASM51.EXE a soubor příslušný k danému jednochipu tedy třeba MOD51 do adresáře kde píšete program, třeba POKUS.ASM . Tyhle 3 soubory nám stačí, takže jdeme vytvořit ten POKUS.ASM . Neptejte se mne prosím jak používat textový editor a pokud to nevíte vyčkejte, až Vám Někdo s velkým N nařídí se naučit ho ovládat a určí Vám který !

Takže píšeme:

Soubor POKUS.ASM by to chtělo si v klidu prohlédnout.

Takže jsme pravě napsali program, který je na nic, ale je to jakási kostra, ze které se dá ledacos vymazat a ledacos do ní připsat. Jak si s tím poradíte je již na Vás. Popisovat instrukční soubor nebudu, je toho plný web a pokud se chcete dozvědět co která instrukce dělá, jistě si najdete popisy, které Vám budou vyhovovat. Já mohu jen doporučit 80C51_FAM_PROG_GUIDE_1.PDF od Philips Semiconductors. Pokud se stane odkaz nefunkčním, zkuste na http://www.philips.semiconductors.com hledat jeho jméno. Je to dle mého názoru velice slušný materiál s detailním popisem instrukcí a vyplatí se mít jej po ruce.

Ještě je fajn vědět jak nastavit jednotlivý bit u registru či v paměti, kde to nejde. Pokud si vezmete jakoukoli hodnotu a provedete logicke AND mezi tou hodnotou a nějakým číslem - říká se mu maska, pak Vám zůstanou zachovány v původním stavu jen ty bity, které mají v masce hodnotu 1, zbytek se vynuluje. Logickým OR zase vyjedničkujete jen ty bity, kde je v masce 1. Pokud programujete cokoli již déle, byla to zbytečná poznámka.

Pokud Vám program nepoběží napoprvé nezoufejte, napoprvé program sice může dělat co jste chtěli, ale až pak třeba zjistíte co jste chtěli ještě a jen o tom nevěděli. A neházejte hned flintu do žita (nemám rád chleba s broky), když to nepojede ani na potřetí. Když budete v koncích, vyházejte z programu skoro všechno a nechte jen to, aby bylo vidět, že to jede. Pokud ano, pak to třeba zkuste vzít a vyhazovat jednotlivé kusy. Většina chyb spočívá v tom, že si někde přepíšete hodnotu, třeba během přerušení, použijete místo konstanty registr číslo x a nebo máte špatně polaritu podmínky, tedy odskakujete opačně než by jste chtěli, ale neustálým čtením už to není vidět. Taky si dejte pozor na ukončování podprogramů volaných instrukcí ACALL nebo LCALL - tedy nezapomeňte na RET a u obsluhy přerušení na RETI. Pokud neběží program vůbec a fakt už nevíte, kde by to mohlo být a jste si téměř jisti, že je program OK, zkuste taky zkontrolovat napájení, jestli kmitá oscilátor a vůbec jestli je dobře jednochip zapojen. Pohybujete se totiž v oblasti, kde pájedlo leží v těsné blízkosti klávesnice. Někdy je dokonce jednodužší prohodit pár drátů, než změnit program. Pokud se učíte jednochipy programovat, pak je mi jasné, že asi o elektronice ledacos víte, takže doladit něco raději cínem než klávesnicí může být i příjemnější (pokud si nepopálíte prsty). Pokud jste program spočítali přesně na určité časy a nyní jsou 3x delší, podívejte se, jestli nemáte krystal na třetí harmonické, takže je z 24MHz rázem jen 8MHz.

Tolik asi tak na úvod, jak programovat 8051. Další materiály jsou uvedeny v LINKS. Žádná učebnice ani materiál Vám nedá do hlavy to, co si tam nedáte sami.

Prosím nepište mi dopisy typu - Napiš mi program na ..., já už si to vypálím. Na to opravdu nemám čas, jsem stejně jako Vy poháněn chlebem. Pro tyto účely jsem zřídil stránky JUNGLE. Jinak e-maily dostávám rád.

Autorův e-mail: jiri@bezstarosti.cz