Nauka asemblera. Część pierwsza.

  Naukę rozpoczniemy, wykorzystując dokumentację CA80 oraz analogie z mikrokontrolerami, bo CA80 jest w rzeczywistości ich odpowiednikiem.


Wyobraźmy sobie, że jest rok 1985, nie ma jeszcze Internetu, a w księgarniach i bibliotekach pojawiają się pierwsze książki o mikrokomputerach. Są one zwykle bardzo ogólne i trudno jest znaleźć coś, od czego można zacząć naukę. Nauczyciele mają ten sam problem - brak źródeł, na podstawie których mogliby opracować lekcje. Wtedy właśnie pojawia się CA80 z jego dokumentacją. Oprócz braku źródeł, powszechny był też brak pieniędzy, więc trzeba było wybierać. Moją przygodę z Z80 rozpocząłem jeszcze wcześniej, dzięki nauczycielowi automatyki w technikum. To on udostępnił mi materiały niezbędne do nauki oraz komputer SA-80, który w mojej szkole wykorzystywany był w pracach dyplomowych.


Ponieważ nie mamy Internetu, musimy zadowolić się tym, co mamy na papierze. Komputera PC, ani nawet ZX Spectrum też nie mamy, więc z pomocą przyjdzie nam znowu papier. Najlepiej w kratkę, ale nie jest to konieczne. Napiszmy wreszcie pierwszy program - jak przystało na (mikro)kontroler - migajmy diodą!

Program w pseudo C.
Algorytm w pseudo C.


W C to tylko kilka linijek kodu, a jak to zrobić w ASM?


Najpierw zapoznajmy się ze sprzętem. Schemat znajdziemy w MIK05B, który w późniejszych wydaniach został dodany do MIK05. Do dyspozycji mamy trzy porty we/wy układu 8255. Domyślnie wszystkie są ustawione jako wejścia, więc musimy to zmienić. Wybierzmy PA, jako nasze wyjście. Opis MIK04 str. 136. 

Słowo sterujące 8255.
Konfigurowanie portów 8255.


Rozkaz, którym słowo sterujące wyślemy do 8255, znajdziemy na końcu MIK05B.

OUT (n),A
Fragment pogrupowanej tablicy rozkazów Z80.


Rozkaz OUT (n),A wysyła zawartość akumulatora pod adres n. Adres portu również znajdziemy w MIK05B.

Adres portu na schemacie.
Fragment schematu CA80.


Znaleziony adres wskazuje PA. Adres rejestru CONTR wyliczymy dodając 11 na pozycji A1A0 (we/wy Z80 adresowane są jednym bajtem. W rzeczywistości starszy bajt adresu również jest ustawiany, ale teraz to możemy pominąć). Przyjęło się, że adresy objęte nawiasem okrągłym wskazują wartość komórki pamięci lub rejestru we/wy, więc PA = 0E0H (po nowemu 0xe0), a CONTR = 0E3H. Mamy już początek programu: 

Początek programu (setup).
Dwa rozkazy ustawiające PA jako wyjście.


Teraz musimy jakoś odmierzać czas, żeby obserwować miganie diody. W MIK05 na 149 stronie znajdziemy opis zmiennej systemowej TIME. Jest to komórka pamięci RAM o adresie 0FFEAH, której wartość jest zmniejszana o 1 (dekrementowana) w każdym przerwaniu NMI. Możemy sprawdzać tę wartość i bez żadnych zmian uzyskamy opóźnienie 512 ms (0FFH * 2ms = 255 * 2 ms = 510 ms i jeszcze 2ms na "przekręcenie" licznika). Operacje arytmetyczne i logiczne możemy wykonywać tylko na akumulatorze (rejestr A), więc najpierw musimy odczytać wartość z komórki TIME, a potem sprawdzić czy już jest np. zero. Znowu z pomocą przychodzi MIK05B.

Fragment MIK05.
TIME jest odpowiednikiem SYSTICK-a.

Fragment tablicy rozkazów Z80.
OR A ustawia flagę Z, kiedy w akumulatorze jest 0.

Rozkazy skoków Z80.
JP NZ,nn wykona się gdy flaga Z nie jest ustawiona.


Mamy już "delay", więc pozostał do zrobienia "toggle". Moglibyśmy na przemian wpisywać do portu zero i jeden, ale jesteśmy leniwi i wykorzystamy sumę modulo dwa czyli XOR. Przy okazji ochronimy wartości pozostałych bitów portu.

Prawie gotowy program w asemblerze.
Program prawie gotowy.


Mamy już cały program w asemblerze, więc włączamy nasz "kompilator". Po lewej stronie specjalnie zostawiłem puste kratki, żeby teraz wpisać w nie kody maszynowe rozkazów. Zaczynamy od zwyczajowego adresu 0C000H, po którym piszemy pierwszy bajt pierwszego rozkazu. Jeżeli rozkaz składa się z większej liczby bajtów, piszemy je w tej samej linii. Jeżeli argumentem jest liczba dwubajtowa, np. adres w pamięci, najpierw piszemy młodszy bajt - little endian. Adres rozpoczynający kolejną linię jest większy od poprzedniego o długość rozkazu w poprzedniej linii. Oczywiście wszystkie wartości zapisujemy szesnastkowo i dla uproszczenia nie dodajemy na końcu litery H, ani zera na początku liczb zaczynających się od litery. Jesteśmy ludźmi, więc łatwo odróżniamy liczby od napisów.

Asemblację wykonujemy za pomocą tablicy rozkazów z MIK05B.
Program w postaci kodów maszynowych.


Jak to działa? W skrócie:

  • do akumulatora wpisujemy słowo sterujące dla 8255 ustawiające PA jako wyjście,
  • wysyłamy słowo sterujące do rejestru CONTR portu,
  • odczytujemy wartość zmiennej TIME,
  • sprawdzamy, czy TIME = 0, OR A ustawia flagę Z, gdy w akumulatorze jest 0,
  • jeżeli nie zero skaczemy do etykiety DELAY (C004),
  • odczytujemy stan portu PA do akumulatora,
  • wykonujemy bitowo XOR na zawartości akumulatora i liczbie 1, czyli negujemy najmłodszy bit, a pozostałe pozostawiamy niezmienione,
  • wpisujemy nową wartość do PA
  • skaczemy do etykiety DELAY.


Niestety zrobiłem błąd, bo założyłem, że TIME zdąży zmienić wartość na 0FFH... Potrzebna jest modyfikacja programu, musimy zaczekać, aż TIME się "przekręci", albo wpisać tam nową wartość. Drugi wariant jest prostszy, więc go zastosujmy. Wpiszmy do akumulatora 0FFH i zapamiętajmy w RAM. Opóźnienie skróci się o 2 ms, co nie ma dla nas żadnego znaczenia.

Teraz bez błędów.
Konieczna była poprawka.


Program można jeszcze skrócić, stosując skoki względne. W skokach tzw. "długich" podajemy dwubajtowy adres, a w "krótkich" jednobajtową wartość w kodzie U2, która zostanie dodana do wartości PC. PC - licznik programu wskazuje adres następnego rozkazu, więc możemy skoczyć do 127 bajtów w przód, lub do 128 w tył. Wymaga to umiejętności liczenia na liczbach szesnastkowych, więc na razie z tego zrezygnujemy.


Pozostaje wpisać nasz program do komputera. Do tej czynności służy zlecenie monitora *D. Jeżeli na wyświetlaczu mamy "CA80" lub "Err CA80", to wciskamy [DC000=], jeżeli nie - zaczynamy od [M].


Dalej wpisujemy kolejne bajty programu zakończone [=]. Jeżeli się pomylimy, możemy wpisywać kolejne cyfry, aż pozostaną na wyświetlaczu właściwe. Jeżeli pomyliliśmy adresy, możemy cofać się klawiszem [.]. Kiedy wszystkie bajty znajdą się na właściwych miejscach, możemy uruchomić program zleceniem *G. Wracamy do monitora [M] i dalej [GC000=]. Nawet jeśli program jest poprawny, nic się nie stanie, bo nie podłączyliśmy diody... Wyjście portu niestety jest dosyć słabe - maksymalny prąd to 4mA, ale różne źródła mówią nawet o 2,5mA. Układ nie ma wewnętrznego ogranicznika prądu, więc musimy być ostrożni. W zależności od użytej diody, musimy zastosować odpowiedni rezystor. Dla uproszczenia przyjmijmy, że musimy odłożyć 2V przy prądzie 2mA, czyli około 1k powinien wystarczyć.


Komentarze

Popularne posty z tego bloga

Uruchamiamy CA80 na RCbus.

Magnetofon CA80.