background image

Copyright by tomasz baszczok 

 http://www.tdv.cad.pl 

FAQ, które masz właśnie przed oczyma powstało głównie dla początkujących elektroników amatorów, chcących 
zmierzyć się z wyzwaniem jakim jest samodzielne programowanie mikrokontrolerów rodziny MCS ’51. Powstało wiele 
klonów tego mikroprocesora, które wyposażono w różne dodatkowe elementy typu przetworniki A/C, C/A, PWM (co 
też się może do sprowadzać do przetwornika C/A), dodatkowe porty itd. Ten tekst dotyczy przede wszystkim wersji 
podstawowej układu. 
Bardzo wygodnymi elementami dla elektronika amatora jest seria µC 89Cxxxx firmy ATMEL. Są to typowe układy 
oparte o rdzeń ’51, wyposażone w pamięć FLASH, dzięki czemu idealnie nadają się do uruchamiania prototypów. 
Programator dla tej serii można  łatwo skonstruować samemu, lub skorzystać z gotowych opracowań dostępnych w 
sieci. Jeszcze prostsze w użytku są nowe układy firmy Philips, też z pamięcią Flash, oraz dodatkowo wyposażone w 
interfejs SPI aczkolwiek są to już szczegóły techniczne wykraczające poza obszar tego FAQ. 
Tekst ten możesz dowolnie rozpowszechniać oczywiście pod warunkiem,  że robisz to za absolutne friko;-))))). Jeżeli 
masz jakieś pytania lub sugestie napisz do mnie 

tdv@cad.pl

 . 

 
P:  Czego potrzebuję żeby móc napisać i uruchomić własny program? 
O:
 Zakładam, że chodzi o całkowicie amatorskie pisanie programów. Czemu? O tym za chwilę. Potrzebujesz asemblera 
(lub kompilatora jeżeli zamierzasz pisać programy w języku wysokiego poziomu np. C), linkera i wskazany byłby jakiś 
symulator – debuger. W sieci można wyszperać sporo oprogramowania tego typu. Ja polecam program ProView32 
firmy Franklin Software INC. oraz µVision/51 i dScope-51 firmy Keil. Tu dochodzimy do amatorstwa;-)))). Są to 
programy komercyjne, które kosztują sporo dolarów, jednak firmy te zadbały o stworzenie wersji dla amatorów. Z 
pewnymi (czasami całkiem sporymi) ograniczeniami można korzystać z tych programów za darmo;-))))))). W 
internecie można  też znaleźć w pełni darmowe programy. Zajrzyj na moją stronę ( 

http://www.tdv.cad.pl

 ), powinno 

tam być kilka adresów, gdzie je można znaleźć. 
Do tego jest potrzebny jakiś sprzęt do zaprogramowania układu. Tu znowu odsyłam do zasobów sieci;-)))). 
No i oczywiście wskazana jest odrobina cierpliwości. 
 
P: Napisałem program (w symulatorze działa), zmontowałem układ, zaprogramowałem procesor, a układ nie 
działa. 
O:  
Powodów może być dużo. Na początek proponuję sprawdzić czy działa generator na końcówce 18 lub 19 (w 
zależności od typu procesora) powinno dać się zaobserwować przy pomocy oscyloskopu, lub zmierzyć 
częstościomierzem sygnał zegarowy odpowiadający użytemu rezonatorowi. Jeżeli generator działa właściwie należy 
sprawdzić czy końcówka EA (pin 31) jest właściwie podłączona. Przy korzystaniu z wewnętrznej pamięci programu 
powinna ona być podłączona do napięcia zasilania, przy pamięci zewnętrznej do masy. 
 
P: Zapisuję 1 do portu P0, a na wyjściu się ona nie pojawia. Te same instrukcje użyte na porcie P1 działają – 
dlaczego? 
O:  
Porty P1, P2 i P3 µC ’51 są wyposażone na wyjściach w wewnętrzne rezystory podciągające „pullup” (w 
rzeczywistości nie są to zwykłe rezystory), natomiast port P0 ma wyjścia typu otwarty dren (kolektor) dlatego aby móc 
go używać jako typowego portu wejścia – wyjścia należy dołączyć zewnętrze rezystory podciągające (np. w postaci 
drabinki rezystorowej). 
 
P: Dlaczego kiedy próbuję odczytywać stan portu, to co otrzymuję nie zgadza się z tym co na nim jest w 
rzeczywistości. 
O:  
Możliwości są dwie: pierwsza, używasz portu P0, a nie dołączyłeś oporników pullup. Druga możliwość: aby 
odczytać stan końcówki zewnętrznej procesora w rejestrze wyjściowym odpowiadającym danemu portowi musi być 
wpisana jedynka. Dopiero kiedy ten warunek jest spełniony można odczytać stan końcówki układu. 
 
P: Czy mogę bezpośrednio z portu procesora wysterować np. diodę LED? 
O:  
W standardowym µC ’51 nie. Ale np. w procesorach 89C1051, 89C2051 i 89C4051 da się to zrobić. Są to małe 
klony (obudowa DIP 20) produkcji Atmel’a, układy posiadają dwa porty i dodatkowo wbudowany komparator (można 
za nich łatwo zrobić przetwornik A/C sukcesywnej aproksymacji;-)))). Przy podłączaniu diody pamiętać trzeba, że są 
one w stanie ją wysterować ale pod warunkiem,  że będą sterowały stanem niskim (prąd wejściowy w stanie niskim 
wynosi 20mA). Jest jeszcze kilka innych procesorów z podobnymi możliwościami. 
 
P: Jakie rezonatory powinno się stosować w układach? 
O: 
Hmm, to zależy. Jest taka zasada if don’t need a speed, don’t make a heat. O co w tym chodzi? O to, że w układach 
CMOS (a praktycznie wszystkie nowoczesne układy są produkowane w tej technologii) moc wydzielana na elemencie 
zależy w sposób znaczny od jego częstotliwości pracy. Dlatego jeżeli nie ma potrzeby stosowania dużych 
częstotliwości (układy firmy Dallas mogą być taktowane nawet 33MHz) powinno się stosować częstotliwości niższe. 
Jakie? To zależy od konkretnego zapotrzebowania. Jest jedno szczególne wskazanie, a mianowicie układy, które mają 
odmierzać upływ czasu rzeczywistego. Cykl maszynowy podstawowej wersji ’51 trwa 12 taktów zegara. Na pierwszy 
rzut oka, do zrobienia zegarka idealnie się nadaje rezonator 12 MHz, ale tylko na pierwszy rzut oka. Znacznie lepszym 

background image

Copyright by tomasz baszczok 

 http://www.tdv.cad.pl 

rozwiązaniem są tu częstotliwości 11,0592MHz, 7,372800MHz czy 3,686400MHz. Uważny czytelnik zauważył,  że 
trzecia częstotliwość stanowi 1/3 pierwszej, a druga 2/3...  Dlaczego takie? 
’51 posiada dwa układy tajmerów (na przyszłość, za każdym razem kiedy piszę o ’51 mam na myśli wersję 
podstawową, jeżeli będzie chodziło o jakąś wersję rozbudowaną, zostanie to zaznaczone). Są to liczniki 
szesnastobitowe zliczające w górę od zadanej wartości. Liczniki te mogą pracować w czterech trybach (jak ktoś nie wie 
o co chodzi niech zajrzy do dokumentacji procesora). Z grubsza licznik działa na tej zasadzie, że wpisuje się do niego 
jakąś wartość i on od tej wartości zaczyna zliczać w górę. Po przepełnieniu licznika (czyli po przejściu ze stanu 
0x0FFFF (tu druga uwaga, dla odróżnienia kodu szesnastkowego będę się posługiwał notacją zaczerpniętą z języka C 
czyli 0x..... oznacza, że te kropeczki to kod szesnastkowy) na 0x00000) następuje wywołanie przerwania sprzętowego. 
W programie powinien być odpowiedni podprogram je obsługujący. Znaczy to, że maksymalnie możemy odmierzyć 
65536 cykli maszynowych (dla 11,0592MHz daje to około 70ms). Dlatego  żeby otrzymać np. czas jednej sekundy 
potrzebny jest jeszcze licznik programowy, ale to już nie stanowi problemu. Kłopoty leżą gdzie indziej. Mała tabelka: 

 Fr=12MHz 

Fr=11,0592MHz 

Odmierzany czas 

10ms 

10ms 

cykl maszynowy 

1µs 

1,08506µs 

liczba cykli koniecznych do 

zliczenia 

10 000 

0x02710 

9216 

0x02400 

wartość ładowana do licznika  

55536 

0x0D8F0 

56320 

0x0DC00 

Licznik timera składa się z dwóch ośmiobitowych połówek. THx i TLx, które ładuje się osobno (zakładam tryb 1 
tajmera).Dla 11,0592MHz ładujemy 0x0DC do części starszej i nic do młodszej, w przypadku rezonatora 12MHz trzeba 
załadować do liczników wartości 0x0D8 do części starszej i 0x0F0 do młodszej. I tu mamy problem. Nie wiemy ile 
cykli maszynowych potrwa przyjęcie przerwania (procesor może akurat obsługiwać przerwanie o wyższym 
priorytecie), a licznik tajmera ciągle liczy... OK., procesor już przyjął przerwanie i zaczyna jego obsługę, na początku 
powinno nastąpić ponowne załadowanie wartości początkowych do obydwóch połówek licznika czyli (12MHz) 0x0D8 
do starszej i 0x0F0 do młodszej... I tu ZONK... Bo my owszem możemy je tam władować ale jest pewne, że młodsza 
część licznika już zawiera wartość zliczoną od momentu przepełnienia licznika do teraz. I co z tym fantem zrobić? 
Trzeba by dodać to co chcemy załadować do tego co już tam jest. Niby to nic skomplikowanego ale jeżeli to nam 
przekroczy 255? Znowu trzeba by kontrolować czy starszej części też nie trzeba dodawać... A już szczytem pecha 
byłaby sytuacja kiedy podczas dodawania młodszej części licznika nastąpiłoby jego przepełnienie, bo dodawanie też 
przecież zajmuje cykle maszynowe... No i taki błąd byłby już całkiem spory bo chodziło by o kilkaset µs. 
A dla kwarcu 11,0592 jak to wygląda? Bardzo prosto;-)))). Ładujemy do starszej połówki licznika 0x0DC, a młodszej 
nie ruszamy... Bo i tak licznik miał zliczać od zera w części młodszej;-)))). Ktoś może tu powiedzieć np. no dobra, a jak 
już naliczył do 255? No to pech. Kto pisze programy obsługi przerwań takie, żeby przyjęcie przerwania trwało 255 
cykli maszynowych? W praktyce jeżeli jest dobrze napisany program jest to sytuacja wręcz niespotykana... 
Jeszcze jedna uwaga. Częstotliwość 11,0592MHz umożliwia też  łatwe dobranie współczynników przy transmisji 
szeregowej;-))))). Aby uzyskać szybkość transmisji 38.4kbps należy użyć rezonatora 7,372800MHz. 
 
P: Jak dobierać wartości początkowe dla tajmerów przy transmisji szeregowej? 
O:  
W trybach 1 i 3 portu szeregowego prędkość transmisji można ustalać programowo, poprzez regulowanie 
częstotliwości wywoływania przerwania tajmera (zakładam tajmer T1). Najwygodniej używać licznika w trybie 2 
(licznik ośmiobitowy z automatycznym ładowaniem przy przepełnieniu z TH1). Częstotliwość przepełnienia dana jest 
zależnością:  fp = fx / (12 * (256 – TH1), gdzie fp – częstotliwość przepełnienia, fx – częstotliwość sygnału 
zegarowego, a TH1 Wartość wpisana do TH1;-)))))). 
W takim trybie pracy licznika nie ma potrzeby obsługiwania go programowo, wystarczy na początku wpisać daną 
wartość do TH1 i uruchomić zliczanie, reszta dzieje się automatycznie. Kilka typowych wartości początkowych dla 
TH1 zebrano w tabelce (fx = 11.0592MHz): 

Szybkość transmisji 

600 

1200 

2400 

4800 

9600 

19200 

Wartość w TH1 

D0 

E8 

F4 

FA 

FD 

FD 

SMOD 

(PCON.7) 

0 0 0 0 1 

Dla 9600 i 19200 wartość jest ta sama, bo szybkość jest podwojona poprzez wpisanie 1 do SMOD. 
 
P: Piszę program w asemblerze na wzór pliku przykładowego. Co oznaczają instrukcje SEGMENT, CODE, 
DATA, BIT, RSEG i CSEG używane w tych plikach? 
O:  
Po kolei. Program konsolidujący musi wiedzieć, co ma umieścić w pamięci danych, co w pamięci programu i w 
którym miejscu (ważne np. dla wektorów przerwań). W/w instrukcje mogą być  używane w różny sposób (różna 
składnia) w różnych programach (zajrzyj do help’a). Generalnie: 
SEGMENT jest deklaracją jakiegoś segmentu w pliku źródłowym: twoja_nazwa SEGMENT typ_pamięci parametry, 
oczywiście nazwę możesz sobie wpisać dowolną, jako typ pamięci można zazwyczaj zapodać DATA, CODE, BIT, 
IDATA, XDATA, co oznacza kolejno: pamięć danych (wewnętrzną, adresowaną bezpośrednio (0..127)), programu, 
przestrzeń adresowaną bitowo, wewnętrzną pamięć danych (adresowaną pośrednio (0..255 w procesorach 8xC52)) i 

background image

Copyright by tomasz baszczok 

 http://www.tdv.cad.pl 

zewnętrzną pamięć danych. Podane dane mogą się nieco różnić dla różnych programów. Parametry zazwyczaj się 
pomija, są przydatne w szczególnych okolicznościach. 
RSEG (RSEG nazwa_segmentu) to oznaczenie, że następujący po nim fragment kodu lub dane) ma być umieszczony w 
odpowiednim segmencie, oczywiście wcześniej zadeklarowanym. RSEG oznacz, że kod (lub dane) są relokowalne. 
CSEG (CSEG AT addr) oznacza, że następujący niżej kod ma być umieszczony w pamięci programu (CSEG od 
CodeSEGment) począwszy od podanego adresu (addr). Przy czym addr musi dać się rozwinąć do poprawnego adresu w 
pamięci programu. 
 
P: Po wejściu do podprogramu chcę odłożyć na stosie akumulator, ale przy kompilacji zgłasza mi się błąd. 
O: 
Chyba wszystkie asemblery przyjmują w kodzie odwołanie do akumulatora poprzez wywołanie nazwy A. Instrukcje 
PUSH i POP nie przyjmują tej składni. Użyj PUSH ACC i POP ACC. Powinno zadziałać. 
 
P: Co oznacza znak $ np. w instrukcji DJNZ R0,$? 
O: 
Znak $ oznacza bieżący adres programu. W przykładzie powyższym znaczy tyle, że procesor po dekrementacji R0, 
jeżeli R0 nie jest równe 0 ma skoczyć pod ten sam adres, czyli wykonać po raz kolejny instrukcję DJNZ R0,$. 
Np. AJMP $; to pętla nieskończona;-))))))). 
 
P: Po co są dublowane deklaracje nazw rejestrów pomocniczych np. R0 i AR0? 
O: 
Podobnie jak wyżej, R0 nie da się odłożyć na stosie, AR0 powinno się dać. 
 
P: W procesorze jest kilka banków rejestrów pomocniczych Rx, jak się pomiędzy nimi przełączać? 
O:  
Do zmiany pliku rejestrów pomocniczych używanych służą dwa bity w słowie statusu RS0 i RS1. Jednak przy 
używaniu w programie więcej niż jednego pliku trzeba być ostrożnym. 
 
P: Używam procesora 87C52, ma on 256 bajtów pamięci danych, w jaki sposób używać tych dodatkowych 128 
bajtów? 
O: 
Wyższe 128 bajtów jest umieszczone w tej samej przestrzeni adresowej co rejestry specjalne. Asembler rozróżnia 
czy chcemy korzystać z obszaru pamięci czy rejestrów dzięki innym trybom adresowania. Dostęp do rejestrów 
specjalnych jest możliwy tylko poprzez adresowanie bezpośrednie, natomiast do pamięci poprzez adresowanie 
pośrednie. Wykorzystuje się do tego celu rejestry pomocnicze R0 i R1. 
 
P: Jak dekrementować wskaźnik DPTR? 
O:  
Nijak. Nie przewidziano do tego celu instrukcji. Zawartość DPTR’a można zmieniać jedynie instrukcjami MOV i 
INC. Jeżeli chcesz jego zawartość zmniejszyć musisz do tego celu użyć kilku instrukcji. W sieci znalazłem kiedyś coś 
takiego: XCH A,DPL ; JNZ $+4 ; DEC DPH ; DEC A ; XCH A,DPL; 
 
P: Czy mogę pisać programy w języku wysokiego poziomu? 
O: 
Możesz. Pod warunkiem, że dysponujesz odpowiednim kompilatorem. Z tego co wiem, są kompilatory większości 
popularnych języków programowania dla µC ’51. 
 
P: Czy program napisany w C będzie wystarczająco sprawny? 
O:  
W zdecydowanej większości tak. Aktualne kompilatory mają możliwość optymalizacji kodu programu pod kątem 
szybkości wykonywania lub zajmowanego miejsca. Zazwyczaj optymalizacja jest wielopoziomowa, więc ma się spore 
możliwości. W ostateczności proste procedury (np. obsługa przerwania zewnętrznego przyjmująca dane z 
przetwornika) można napisać w asemblerze. Choć wcale nie jest pewne, czy nasza „wizja” tej procedury będzie szybsza 
od tej napisanej w C i ułożonej przez kompilator. 
 
P: Piszę program w C. Przechodzi symulację, ale procesor milczy... 
O:  
A to pech... Zacznij od sprawdzenia pliku hex. Kompilatory (właściwie linkery) C mają to do siebie, że nie 
koniecznie układają dane w pliku hex według rosnących adresów. Programator może (choć nie musi) mieć z tym 
problemy... i wychodzi ZONK. W intelHex adresy są umieszczane jako 3, 4, 5 i 6 cyfra i według tych adresów powinny 
być posegregowane sekcje w pliku (zazwyczaj chodzi o 2 – 3 sekcje). 
 
P: Jak deklarować zmienne, żeby zajmowały po 1 bajcie? (int zabiera dwa). 
O: 
Unsigned char, lub char i włączyć odpowiednią opcję w kompilatorze żeby jego zakres był 0..255. 
 
P: Jak napisać procedurę obsługi przerwania w C? 
O: 
Przykład: void int0_servis (void) interrupt 0 {/*twoja procedura*/} 
Lub void to_servis (void) interrupt 1 {/*twoja procedura*/}; cała kabała w tym interrupt i numer przerwania, resztę robi 
kompilator;-))))))). 
 
P: W jaki sposób deklaruje się stałe i zmienne w asemblerze (bit, 8 bit i 16 bit)? 

background image

Copyright by tomasz baszczok 

 http://www.tdv.cad.pl 

O: Zacznijmy od tych zmiennych 16 bitowych. 
uC '51 jest w zasadzie 8 bitowy i jako taki nie ma możliwości operowania na wartościach 16 bitowych. Wszelkie takie 
operacje trzeba robić ręcznie, tzn. 16 bitowe zmienne trzeba składać z dwóch 8 bitowych, i na takich zmiennych trzeba 
operować. Jest to dosyć żmudne, aczkolwiek nie jest trudne, trzeba tylko panować nad tym co się robi. 
 
Deklaracje stałych: 
Konstrukcja wygląda następująco: 
 Symbol 

EQU 

wartość 

Gdzie symbol to Twoja nazwa dla zmiennej, EQU to dyrektywa asemblera, a w miejscu wartość wstawiasz liczbę. Przy 
czym jako wartość można wstawić też nazwę rejestru (np. R5). Przy kompilacji asembler zastępuje wszędzie symbol 
wartością zadeklarowaną przez Ciebie. Raz zadeklarowanej wartości nie można zmienić. 
Przykład: 
 Year 

EQU 

98 

W każdym miejscu programu gdzie wystąpi Year, będzie przy kompilacji użyta wartość 98. 
 
Zmienne 8 bitowe: 
 Nazwa: 

DS 

wyrażenie 

Gdzie Nazwa to nazwa, DS dyrektywa asemblera, a wyrażenie to ilość rezerwowanych bajtów. 
Przykład: 
 Czas: 

DS 

Czyli rezerwujesz 1 bajt na zmienna czas. 
 
Zmienne bitowe: 
 Nazwa: 

DBIT 

wyrażenie 

Znaczenie poszczególnych oznaczeń podobne jak poprzednio. 
Wszystkie te dyrektywy dotyczą rezerwacji pamięci w bieżącym segmencie. 
Poniżej przykład deklaracji w pliku programu: 
 
 STACK 

 SEGMENT 

 IDATA 

 

PROGRAM       SEGMENT     

 

CODE 

 

VAR          

SEGMENT     

 

DATA 

 

BITVAR       

SEGMENT 

 

BIT 

 

 

      

T0_H   

EQU 

03Ch 

 

 

      

T0_L   

EQU 

0B0h 

      

T1_H   

EQU 

0ECh   

 

      

T1_L   

EQU 

078h 

      

 

      

ENABLE 

EQU 

0B0H.0   

      

RW 

 

EQU 

0B0H.1 

      

RS 

 

EQU 

0B0H.7 

       

 

 

                    

 R7AD 

 EQU 

007H 

 R6AD 

 EQU 

006H 

 R5AD 

 EQU 

005H 

 R4AD 

 EQU 

004H 

 R3AD 

 EQU 

003H 

 R2AD 

 EQU 

002H 

 R1AD 

 EQU 

001H 

 R0AD 

 EQU 

000H 

 

 

             

 

RSEG     

VAR 

 CZ_WYM: 

DS 

8  

 CZ_BIEZ: 

DS 

8  

 KEYS: 

 DS 

1  

 T_KEYS: DS 

1  

 CZ_STAT: 

DS 

1  

 CZ_TMP: 

DS 

  STATUS: DS 

1  

 REFRESH: 

DS 

1  

 D_SEK: 

 DS 

 TIME: 

 DS 

background image

Copyright by tomasz baszczok 

 http://www.tdv.cad.pl 

 TMP: 

 DS 

 AL_NR: 

 DS 

 

 

 

RSEG     

BITVAR 

 TMP_F: 

 DBIT 

 ALARM: DBIT 

 ACTIV: 

 DBIT 

 S_BY: 

 DBIT 

 

 

 

RSEG     

STACK  

 

 

 

DS    020H 

 
 

CSEG     

AT 00H 

 AJMP 

INIT 

 

 

 CSEG 

 

 AT 

0BH 

 AJMP 

T0_SERV 

 

 

 CSEG 

 

 AT 

1BH 

 AJMP 

T1_SERV 

 

 

 

 RSEG 

 PROGRAM 

 

 ORG 

02CH 

INIT:   

 

 

 

;inicjalizacja procesora i dalszy ciąg programu. 

 
Ten fragment pochodzi z programu działającego  urządzenia.