PROGRAMOWANIE PROCESORA 6502 W KOMPUTERACH ATARI XL/XE - cz.1
Nieco o przesyłaniu danych (nieco danych o przesyłaniu)
LDA jest bez wątpienia jednym z najczęściej używanych rozkazów procesora 6502. Przenosi on pojedynczy bajt danych z pamięci do rejestru procesora zwanego akumulatorem. Argumentem rozkazu jest wskazówka, gdzie należy szukać tego bajtu. Na przykład rozkaz
LDA 712
spowoduje umieszczenie w akumulatorze liczby znalezionej w komórce pamięci o adresie 712. Jest to kolor obrzeża ekranu. Może zresztą słowo "przenosi" nie jest tu odpowiednie, ponieważ zawartość komórki 712 nie ulega przy tym wymazaniu, lecz pozostaje niezmieniona. Jest to zatem bardziej kopiowanie, powielenie wartości. Podobnie odwrotny rozkaz
STA 710
przesyła zawartość akumulatora do komórki 710, lecz akumulator nie zmienia swego stanu. Komórka ta określa kolor tła tekstu, a zatem po wykonaniu opisanej pary rozkazów tło będzie miało taki sam kolor, jak dotychczas obrzeże. Przeważnie jest to czerń. Otrzymamy więc jasne litery na całkiem czarnym ekranie. Zwróć uwagę, że, dzięki takiemu przeniesieniu koloru, nie musimy nawet wiedzieć, jaka to właściwie jest liczba. Spróbuj to wykonać używając dostępnego sobie asemblera. Program może wyglądać tak:
OPT 21
ORG 1152
LDA 712
STA 710
RTS
END
Rozkaz END stanowi informację dla asemblera, że ma zakończyć tłumaczenie programu. Znaczenie rozkazów OPT, ORG i RTS zostanie wyjaśnione niebawem. Chwilowo przyjmij, że muszą one w podanej kolejności i formie oskrzydlać każdy z przykładowych programów. Przepisz przykład starannie, bacząc, by nie pominąć potrzebnych spacji, ani nie dopisać zbędnych znaków, przetłumacz go i wykonaj zgodnie z wymogami posiadanego asemblera. Wszystkie użyte tu przykłady są napisane (i przetestowane!) pod kontrolą systemu Quick Assembler (nazywanego dalej QA) i będą również działać z JBW Assembler-em bez żadnych zmian. Są to zintegrowane pakiety edytora, asemblera i mini-debugera, umożliwiające bardzo szybkie przechodzenie od redagowania do testowania programu i z powrotem. QA udostępnia dla uruchamianego programu osobny ekran, do którego można zaglądać w każdej chwili, także w trybie edycji.
Podziwiając efekt działania programu zastanów się, jak teraz przywrócić ekranowi jego poprzednią barwę (nie myślę tu, rzecz jasna, o tak "fizycznych" metodach, jak klawisz RESET). Nie wiesz? Ja też. Po wykonaniu rozkazu STA poprzednia zawartość komórki pamięci niestety bezpowrotnie znika. Można czasem temu zaradzić, jeśli się wie, co tam kiedyś było. Spróbujmy napisać tak:
LDA #148
STA 710
Znaczek "#" z przodu argumentu wskazuje, że chodzi w tym przypadku o liczbę 148, nie zaś o komórkę o tym adresie. Tak więc
liczba wędruje do akumulatora nie z konkretnego miejsca pamięci, lecz z wnętrza samego rozkazu. Tak wyczarowana wartość ustala nowy kolor tła tekstu. Przepisz i wykonaj ten przykład, pamiętając o uzupełnieniu go dodatkowymi rozkazami, jak w poprzednim programie.
Jeśli jednak program ma być uniwersalny, odradzam stosowanie stałych liczbowych. W kolejnym przykładzie dla zamiany kolorów liter i tła posłużymy się dodatkowym rejestrem procesora, zwanym X. Rozkaz TAX przesyła liczbę z akumulatora do tego właśnie rejestru, zaś rozkaz TXA - z X do A (kumulatora).
LDA 709
TAX
LDA 710
STA 709
TXA
STA 710
Komórka 709 określa właśnie kolor liter. Uruchom ten przykład tak, jak poprzednie. Kolory uległy zamianie. Wykonaj program jeszcze raz, a kolory wrócą na swoje miejsca. To ważna cecha solidnego programowania: nie psuć niczego nieodwracalnie.
Trzeci rejestr ogólnego stosowania nazywa się Y. Grupa rozkazów: TAX, TXA, TAY, TYA przesyła dane pomiędzy rejestrami wewnątrz procesora. Rozkazy te nie odwołują się do pamięci, są przeto bardzo szybkie. Ich działanie nie wymaga chyba komentarza.
Do komunikacji rejestrów X i Y z pamięcią służą, podobnie jak w przypadku akumulatora, pary rozkazów LDX i STX oraz LDY i STY. Wiedząc to, możemy łatwo zaprogramować rotacgę (cykliczną zamianę) kolorów obrzeża, tła i napisów:
LDA 712
LDX 710
LDY 709
STA 710
STX 709
STY 712
Wykonaj ten program taką liczbę razy, aby kolory po zmianach powróciły do pierwotnego stanu.
Pięknie, patrząc jednak na tekst ostatniego przykładu nie jest łatwo domyślić się, o co tu chodzi. Jakikolwiek dłuższy program napisany w tym stylu byłby całkowicie nieczytelny. Na szczęście nie programujemy przecież w języku maszynowym, który składa się wyłącznie z ciągów liczb i jest zupełnie niezrozumiały dla człowieka. Język asemblera, zwany także językiem symbolicznym, umożliwia nadawanie liczbom nazw, które następnie mogą być zamiast tych liczb używane, podobnie jak kody rozkazów maszynowych zastąpione są trzyliterowymi kryptonimami. Mając na względzie czytelność programu, łatwość jego analizy lub modyfikacji i zmniejszenie prawdopodobieństwa popełnienia błędu, przepiszmy powyższy przykład tak:
RAMKA EQU 712
POLE EQU 710
LITERY EQU 709
LDA RAMKA
LDX POLE
LDY LITERY
STA POLE
STX LITERY
STY RAMKA
Rozkaz EQU służy właśnie do utożsamienia wymyślonego przez nas symbolu, takiego jak POLE, z pewną liczbą. Asembler będzie traktował wszystkie dalsze wystąpienia tego symbolu (zwanego też etykietą) tak, jakby był tą właśnie liczbą. Etykieta stoi zawsze na początku wiersza, który ją definiuje. Dlatego asembler wymaga, by w innych wierszach
pierwsza kolumna pozostawała wolna. W praktyce zostawia się z przodu więcej spacji, tak, by symbole rozkazów zaczynać od tej samej kolumny. Nie jest to niezbędne, lecz wprowadza do programu pożądany ład wzrokowy. Liczba tych spacji zależy od zwyczajów programisty, głównie od długości etykiet. Spróbuj teraz
przepisać ładnie poprzednie przykłady. Jeden z nich mógłby wyglądać tak:
MODRE EQU 148
POLE EQU 710
LDA #MODRE
STA POLE
Poznajesz, który? Po przepisaniu uruchom je, aby sprawdzić, czy działają tak samo. Etykieta może się składać z liter alfabetu, cyfr i znaków podkreślenia, pierwszy znak nie może być cyfrą.
Słów kilka o skokach i warunkach
Innym sposobem definiowania etykiety jest umieszczenie jej na początku zwykłego wiersza programu. Etykieta przyjmuje wtedy wartość adresu pamięci, pod którym znajdzie się po przetłumaczeniu kod opatrzonego nią rozkazu. Na przykład we fragmencie
LDA #MODRE
JMP TUTAJ
STA LITERY
TUTAJ STA RAMKA
instrukcja STA LITERY nie wykona się, ponieważ rozkaz skoku JMP wymusi dalsze wykonywanie programu od adresu TUTAJ, a jest to adres rozkazu STA RAMKA. Program nie zaburzony rozkazami skoków wykonuje się, rzecz jasna, z góry na dół. Spróbuj uruchomić ten program, dopisując brakujące fragmenty a poprzednich przykładów. Pamiętaj, że każda używana etykieta musi być dokładnie raz zdefiniowana.
O wiele przydatniejsze i częściej używane są rozkazy skoków warunkowych. Procesor realizuje je pod określonym warunkiem, mogą się więc wykonywać, lub nie. Przykładem takiego rozkazu jest BEQ, który zadziała wtedy, gdy poprzedzająca go operacja porównania liczb wykazała ich równość. Rozkaz CMP porównuje liczbę w akumulatorze z argumentem w pamięci, przy czym żadna z tych liczb nie ulega zmianie. Format tego rozkazu jest taki sam, jak LDA. Poniższy program oczekuje na naciśnięcie dowolnego klawisza, do tego czasu bowiem komórka 764 zawiera liczbę 255, a więc procesor wciąż powraca do rozkazu oznaczonego etykietą CZEKAJ. Gdy zawartość komórki 764 się zmieni, rozkaz skoku zostanie zignorowany.
KLAW EQU 764
NIC EQU 255
CZEKAJ LDA KLAW
CMP #NIC
BEQ CZEKAJ
LDA #NIC
STA KLAW
Ostatnie dwa wiersze usuwają kod naciśniętego klawisza, aby nie został on przyjęty przez program, pod którego kontrolą testujemy swoje przykłady. Puste wiersze w programie polepszają
jego czytelność, dzieląc tekst na logiczne fragmenty. W asemblerach, które nie przyjmują pustych linijek, można postawić w pierwszej kolumnie znak komentarza. Pożądane są także krótkie opisy trudniejszych fragmentów programu czy poszczególnych rozkazów. W QA wiersz komentarza rozpoczyna się od znaku "*", komentarze można też umieszczać w wierszach programu, na końcu, oddzielając je od rozkazu co najmniej jedną spacją. Komentarze nie są asemblowane, nie wydłużają wynikowego kodu programu, a tylko poprawiają jego zrozumiałość, jak tu:
OPT 21 opcje
ORG 1152 początek
*------------------*
*ten program czeka *
* na naciśnięcie *
* klawisza RETURN *
*------------------*
* definicje
KLAW EQU 764
RETURN EQU 12
NIC EQU 255
* pętla oczekiwania
CZEKAJ LDA KLAW
CMP #NIC
BEQ CZEKAJ
* naciśnięto klawisz
LDX #NIC
STX KLAW
CMP #RETURN
BNE CZEKAJ
* gotowe.
RTS powrót
END koniec
Skok warunkowy BNE odbywa się w przypadku, gdy poprzedzająca operacja porównania wykazała nierówność.
W rzeczywistośći mechanizm skoków warunkowych jest nieco bardziej skomplikowany, a zarazem bardziej elastyczny, niż
w podanym wyżej, naiwnym objaśnieniu. Rozkaz skoku bada bowiem nie poprzednią instrukcję, do której przecież nie może wrócić, lecz stan pewnego specjalnego rejestru procesora, zwanego rejestrem znaczników. Znacznik należy rozumieć jako jednobitową komórkę mogącą przyjmować jeden z dwóch stanów: O lub 1.
Wspomniany rejestr może być traktowany zarówno jako jeden bajt danych, jak też jako osiem oddzielnych znaczników. Rozkazy BEQ i BNE badają znacznik Z (zero) i w zależności od jego stanu skok się odbędzie lub nie. Niektóre inne zaś rozkazy mogą wpływać na stan różnych znaczników. Na przykład rozkaz CMP ustawia znacznik Z (wpisuje l) gdy porównywane liczby są równe, a kasuje go (wpisuje O) w przeciwnym przypadku. Zauważ, że dzięki temu skok warunkowy nie musi być bezpośrednio następnym rozkazem po instrukcji porównania, o ile tylko dzielące je rozkazy nie naruszają interesującego nas znacznika. BEQ realizuje skok, gdy Z=1, BNE zaś, gdy Z=0.
Bywają też operacje arytmetyczne
Innym rozkazem, który zmienia stan znacznika Z jest DEX, rozkaz arytmetyczny, który zmniejsza zawartość rejestru X o 1. Jeśli wynikiem tego działania jest O, Z zostanie ustawiony, w przeciwnym razie - skasowany. Poniższy program odczekuje około S sekund, badając zegar systemu, który zmienia wskazanie co 1/50 sekundy.
ZEGAR EQU 20
SEK EQU 50
SEK2 EQU SEK+SEK
LDX #SEK2
CZEK1 LDA ZEGAR
* czekanie na zmianę
CZEK2 CMP ZEGAR
BEQ CZEK2
* zmniejsz licznik
DEX
BNE CZEK1
Jak łatwo zgadnąć, jest też rozkaz, który zwiększa zawartość rejestru X (nazywa się INX). Znajdziemy także analogiczne rozkazy dla rejestru Y: DEY
i INY. Wszystkie one ustawiają znacznik Z, jeśli wynikiem jest zero. Gdy już o symetrii mowa, mamy też rozkazy CPX i CPY działające tak, jak CMP, porównujące zawartość odpowiedniego rejestru indeksowego z zawartością komórki pamięci. Rozkazy zwiększania i zmniejszania znajdują zastosowanie w tzw. iteracjach, czyli fragmentach programu zapętlonych warunkowym skokiem wstecz dla wielokrotnego wykonania tej samej operacji. Tym sposobem można, na przykład, ustalić liczbę bajtów zerowych w obszarze pamięci od adresu 794 do 830 .
HATABS EQU 794
ILE EQU 36
LDX #0 liczba zer
LDY #0 nr bajtu
BADAJ LDA HATABS,Y
BNE NIEO
INX zwiększ, gdy O
NIEO INY następny bajt
CPY #ILE
BNE BADAJ
* tu X zawiera liczbę zer
Rozkaz LDA HATABS,Y odwołuje się do pamięci w sposób szczególny. Pobiera bowiem bajt nie spod adresu HATABS, lecz dalszego o liczbę zawartą w rejestrze Y. Dla przykładu, jeżeli Y zawiera 5, to pobrany zostanie piąty bajt tablicy HATABS. Fragment pamięci bowiem, do którego odwołujemy się tym sposobem (zwanym adresacją indeksową) można rozumieć jako tablicę bajtów o wielkości wyznaczonej zakresem zmian rejestru indeksowego. Tu pora na dygresję o liczebnikach porządkowych. Podczas odwołań do pamięci przyjmujemy, że początkowym numerem jest zawsze numer 0. Tablica HATABS, zawierająca, jak wiadomo, informacje o obsługiwanych przez system urządzeniach zewnętrznych ma długość 36 bajtów ponumerowanych od 0 do 35. Takie też wartości przyjmuje kolejno rejestr Y. Jasne jest chyba, dlaczego nie można zacząć liczenia od 1: efektywny adres odwołania do pamięci jest sumą adresu podanego wprost (HATABS) i liczby w rejestrze. Początkowy bajt jest przecież pod adresem HATABS + 0! I jeszcze (ostatni?) raz o rozkazie BNE: naprawdę oznacza on "skocz gdy nie zero". Dlatego pozwala wykryć zerowość argumentu w powyższym przykładzie (rozkaz LOA ustawia znacznik Z, oznaczający zerowy wynik operacji). Opisane wcześniej w sposób uproszczony działanie rozkazu CMP polega w istocie na wykonaniu odejmowania (którego wynik wszakże nigdzie się nie zapisuje) argumentu od zawartości A i dlatego wynik 0 oznacza równość tych liczb,
Dalszy ciąg nastąpi.
Janusz B. Wiśniewski
|