1
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
2
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 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
3
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)?
4
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
1
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
1
STATUS: DS
1
REFRESH:
DS
1
D_SEK:
DS
1
TIME:
DS
1
5
Copyright by tomasz baszczok
http://www.tdv.cad.pl
TMP:
DS
1
AL_NR:
DS
1
RSEG
BITVAR
TMP_F:
DBIT
1
ALARM: DBIT
1
ACTIV:
DBIT
1
S_BY:
DBIT
1
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.