Mikrokomputer CA80 programowanie, assemblerowa piaskownica przegląd wybranych procedur systemowych |
|
Stronka ta skoncentrowana jest na podstawach programowania CA80, do tego w końcu ów komputerek stworzono. Dość dokładny opis procedur systemowych CA80 znajduje się w tomiku MIK05, tam też znajdziemy ich kod źródłowy oraz mapę zajętości górnych obszarów pamięci RAM. Pełny listing monitora to MIK08 i to jest kolejna broszurka, z której lekturą naprawdę warto się zmierzyć. Przykłady poniżej mocno nawiązują do tego, co znajdziemy w MIK05, nie chciałam jednak powielać prostych programików z MIK, prezentowane tu aplikacje są nieco bardziej rozbudowane i celowo podkręcone, demonstrując przy okazji kilka sympatycznych właściwości translatora ⇨ SB-Assembler .
Wyjątkowo, oprócz źródła ⇨ example_draft.asm wskazuję pliki wynikowe: ⇨ example_draft.hex - plik z kompilatem *.hex (może być *.bin) zależnie od tego, jak dalej chcemy przenieść binaria do CA80. *.bin to raczej dla emulatora EPROM AVT-270, *.hex dla ⇨ Ładowarki do Pamieci lub wysyłki via RS232 ⇨ example_draft.lst - listing, włącznie z liniami plików zagnieżdżonych dyrektywą .in. Przy mnemonikach widzimy kody maszynowe instrukcji oraz koszt ich wykonania w taktach zegarowych. Na bardzo upartego z listingu idzie bezpośrednio wpisać programik do CA80 zleceniem *D, a jest to tym prostsze, że SB-Asm kolejne bajty słów-parametrów 16-bitowych rozkazów (np. ld HL,nnnn) układa w ludzkiej kolejności młodszy-starszy, można przepisywać niejako na żywca. ⇨ example_draft.sym - spis symboli z odniesieniami, przydaje się przy dokumentowaniu aplikacji, czasem ułatwia przeprowadzenie modernizacji (w nowomowie: redesign) na poziomie wersji źródłowej, pokazuje liczniki użyć danego literału (RefCount), a najciekawsze są te z zerowymi wystąpieniami oraz te maksymalnie nękane w naszym kodzie. Każdy program użytkownika zaczynamy jawnym ustawieniem stosu, w CA80 przyjęła się wartość $FF66. Druga zwyczajowo przyjęta rzecz to lokalizacja programu - bank U12 (to numer scalaka na płytce), od adresu $C000. Teoretycznie mamy tu 16kB ($4000) pamięci na nasz kod maszynowy, no łoł. Krzemowa rzeczywistość jest jednak szara i prozaiczna - w znakomitej większości przypadków bank ów obsadzony jest układem 6264 o pojemności 8kB, z racji niepełnego dekodowania obszary $C000...$DFFF oraz $E000...$FFFF są tożsame. Oznacza to także tyle, że choć dno stosu ustawiamy na $FF66 to tak naprawdę fizycznie wykorzystany zostanie adres $DF66 (kto chce niech sprawdzi zleceniem *D). Piszę to, aby zasygnalizować, że na stos nie mamy wcale tak wiele przestrzeni jak mogłoby się wydawać z teoretycznego rozmiaru banku. Pracujący stos będzie pomykał w kierunku niższych adresów - jeżeli damy ciała w jego obsłudze, na przykład źle zbilansowanymi ilościowo rozkazami push|pop, stos zamaże nam kod w RAM i będzie po zabawie. Szczęściarze posiadający konfigurację pamięci typu kostka 62256 (32kB) w podstawce U12 (to raczej w odniesieniu do nowego CA80) mają ciągły obszar RAM z zakresie $8000...$FFFF i tam hulaj dusza. Poniżej znajdziemy kolejne programiki demonstracyjne procedur systemowych CA80, to swego rodzaju API zdejmujące z nas problemy z obsługa klawiatury, wyświetlacza czy z wprowadzaniem danych (liczb/parametrów) do programu. Warto z nich korzystać, ponieważ zapewnia to kompatybilność kodu pomiędzy starą i nową wersją CA80 no i jest w sumie oszczędnością czasu, mamy gotowce na podstawowe życiowe sytuacje. Wszystkie programy demonstracyjne z GitHub ⇨ bienata/CA80 można sobie skompilować jedną linijką:
Procedura COM wyświetla siedmiosegmentowy kod znaku przekazany w rejestrze C zgodnie z ustaloną jako parametr PWYS pozycją wyświetlacza. Przykład kręciołka na wyświetlaczu widzimy poniżej. ⇨ example_COM.asm
Wywołanie COM jest proste i nie wymaga komentarza, zerknijmy za to na procedurę delay z rozkazem halt. To znana w CA80 sztuczka generująca opóźnienia wykorzystująca fakt, że w tle cały czas pracują dla nas przerwania NMI. A jak wiadomo, procesor Z80 z błogostanu wywołanego rozkazem halt może wyjść albo na skutek sprzętowego resetu, albo właśnie przyjęcia przerwania. NMI w systemie CA80 pojawiają się co 2 ms, wykonanie halt zastopuje program główny do najbliższego przerwania, dając finalne 2 ms opóźnienia. Wołając halt w pętli, jak w przykładzie powyżej możemy generować praktycznie dowolne opóźnienia, oczywiście niestety marnując moc obliczeniową procesora, w tym przykładzie jest to akurat akceptowalne. Zobaczmy też, jak inicjowany jest rejestr B, służący jako licznik obiegów pętli koordynowanej rozkazem djnz. Translatorowi podajemy wyrażenie arytmetyczne, różnicę adresów etykiet `semiTableEnd` i `semiTable`, ponieważ elementy są jednobajtowe wynik odejmowania da nam ilość elementów w tabeli. Ewentualne dołożenie kolejny elementów automatycznie przesunie etykietę (pamiętamy, aby ona była za ostatnim elementem tabeli, na pierwszym wolnym kolejnym adresie), translator sam wyliczy jaką wartością należy załadować licznik w rejestrze B. Sztuka jest bardzo przydatna, należy tylko mieć na uwadze, że maksymalna wyliczona wartość nie może być większa od $FF pod rygorem błędu translacji. Działanie programu example_COM.asm widzimy na filmiku: Procedura PRINT wyprowadza na wyświetlacz ciąg znaków (komunikat) wskazany adresem w rejestrze HL i ograniczony znacznikiem $FF (dalej EOM /end of message/). Treść komunikatu (kolejne znaki) to kody siedmiosegmentowe wyświetlacza. Przykład wykorzystania procedury poniżej (już okrojony do niezbędnego minimum): ⇨ example_PRINT.asm
Tu, oczywiście oprócz wywołania procedury PRINT pragnę pokazać jak SB-Assembler potrafi nam ułatwić życie, a przynajmniej zwiększyć czytelność kodu źródłowego. Zacznijmy od sztuki z automatycznym obliczaniem rozmiaru tabeli. W tym wypadku mamy do czynienia z trzema komunikatami message1,2,3 których adresy zgromadzone są w tabelce `messageTable`. Adresy są szesnastobitowe, więc jak nietrudno zgadnąć - rozmiar tabeli wyliczamy odejmując adres jej końca (pierwszego bajtu po ostatnim elemencie) od adresu początku. Całość dzielimy przez dwa, tak właśnie wyliczana jest wartość `messageTableLength`. I tu zapewne marszczymy brwi - no ale jak? Przecież wyrażenie `$-messageTable/2` to na logikę (i szkolna kolejność operacji) różnica pomiędzy bieżącym wskaźnikiem i połową adresu początku... No, tu trzeba się nieco przyzwyczaić do filozofii SB-Asm i tego, że wylicza on wartości po kolei, więc najpierw odejmie, a potem produkt podzieli przez dwa. Niezbyt to fajne i trzeba zwyczajnie uważać. Drugi niby drobiazg to sposób zapisu mnemoników Z80. Program przechowuje adres bieżącego komunikatu w rejestrze indeksowym IX, kolejne komunikaty (adresy) wybiera inkrementując ten rejestr o 2. Proszę zwrócić uwagę na sposób przepisania do rejestrów H i L wartości z komórek wskazywanych przez IX. Mamy w Z80 rozkaz `ld r,(IX+d)` gdzie r jest jednym z rejestrów roboczych, d - to przesunięcie/displacement o zakresie -128..+127. Oczywiście można zapisać +0, +1 i też będzie ładnie, ale wprowadzając stałe odpowiednio ADDR_LO, ADDR_HI oszczędzamy czytelnikowi zgadywania co poetka miała na myśli. Fragment kodu:
jest chyba czytelniejszy - do L trafia młodszy bajt komórki wskazanej IX, do rejestru H - bajt starszy. Oczywiści nie należy przesadzać z tego rodzaju parametryzacją, ale stosowana z umiarem - może naprawdę ułatwić życie osobie czytającej nasze źródła. Filmik demonstrujący zastosowanie procedury PRINT: Procedura CLR zeruje zawartość bufora wyświetlacza w/g wskazanej pozycji PWYS. Samo wywołanie jest banalne i widać je poniżej, warto natomiast powiedzieć kilka zdań o tym jak posługiwać się parametrem PWYS (to te stałe `.db NN` za większością wywołań procedur systemowych). PWYS w wielkim skrócie steruje pracą procedur ekranowych, informując je od której pozycji wyświetlacza mają zacząć pisanie/kasowanie - to młodsze 4 bity oraz iloma pozycjami wyświetlacza ma zająć się procedura - to cztery starsze bity. Przykładowo: cały wyświetlacz czyli 8 pozycji, począwszy od 0 daje nam finalnie $80. Dwie cyfry hex począwszy od prawej wyświetlimy z PWYS=$20, a cztery cyfry od lewej podając PWYS=$44. Komentarze przy wywołaniach CLR są chyba jasne: ⇨ example_CLR.asm
Filmik demonstracyjny: ❦ procedura CO - $01e0 - wyświetlenie cyfry hex z rejestru C Procedura CO drukuje cyfrę szesnastkową (0..$F) podaną w rejestrze C zgodnie z ustawioną pozycją wyświetlacza. Przykładowy programik: ⇨ example_CO.asm
Zwróćmy uwagę na nieco odbiegający od poprzednich układ instrukcji przy wywołaniu procedury systemowej. Po tradycyjnym `call PROCEDURA` mieliśmy równie tradycyjne `.db PWYS`, a tu:
Zastosowany tu zabieg to megaprosty przykład programu samomodyfikującego się, ano tak. Zarezerwowany bajt zaraz za rozkazem `call nnnn` jest opatrzony etykietą (displayPos) i zawartość tej komórki jest modyfikowana w trakcie działania programu, wybierane są kolejne pozycje wyświetlacza na których pojawiają się cyfry 0..F. Z technicznego punktu widzenia ten bajt nie jest zmienną programu rezerwowaną w dedykowanym segmencie, on należy do segmentu CODE, a jednak go zmieniamy i to skutecznie. Prezentuję to jako ciekawostkę, takie programy są trudne w uruchamianiu, no i oczywiście można je puszczać tylko w pamięci RAM, ten fragment wgrany do EPROM zwyczajnie nie zadziała. Na filmiku poniżej widzimy pracujący programik: Można chwilę się nim pobawić, na przykład opatrując komentarzem wywołanie `call CLR` wraz z dotyczącym go parametrem PWYS. Program będzie wtenczas radośnie mazał po wyświetlaczu, pozostawiając jako ostatnio ustawioną zawartość, wygląda to mniej więcej tak: ❦ procedura LBYTE - $0018 - wyświetlenie dwucyfrowej liczby hex z akumulatora Procedura LBYTE wyprowadza szesnastkową zawartość rejestru A na wyświetlacz według zadanej pozycji wyświetlacza, program: ⇨ example_LBYTE.asm
Tu odkrywczego w sumie nic nie ma, zauważmy tylko, że skoro LBYTE drukuje ośmiobitową liczbę hex (00..FF), to z powodzeniem wydrukuje dwucyfrową liczbę BCD (00..99), kwestia jak się umówimy odnośnie interpretacji zawartości akumulatora. Tak ogólnie to procedura jest użyteczna do pokazywania wszelkich współczynników czy innych parametrów, filmik z działania następujący: ❦ procedura LADR - $0020 - wyświetlenie czterocyfrowej liczby hex z HL Procedura LADR wyprowadza na wyświetlacz szesnastkową zawartość rejestru HL (w kolejności H,L) zgodnie z zadaną pozycją wyświetlacza. ⇨ example_LADR.asm
Przykład jak widać jest mocną wariacją na temat programu z LBYTE, jedyne co wprowadziłam to pokazywanie nie tylko zawartości elementu tabeli (LBYTE) ale i jego szesnastobitowego adresu w pamięci, właśnie procedurką LADR. Całość w działaniu znajdziemy na filmiku: ❦ procedura CSTS - $FFC3 - sprawdzenie stanu klawiatury Procedura CSTS dokonuje szybkiego przeskanowania klawiatury, gdy stwierdzi wciśnięty klawisz wychodzi z ustawioną flagą CY i kodem klawisza w akumulatorze, gdy nic nie wciśnięto CY=0, oto prosty programik: ⇨ example_CSTS.asm
W tym programie warto zastanowić się nad obsługą warunków - zależnie od wciśniętego guzika wołamy niby-różne procedury do ich obsługi. Tak naprawdę to procedura jest jedna, ale u nas ma trzy (może dowolnie więcej) punktów wejścia. Oczywiście dalsza kontrola sterowania wymaga użycia lamerskich skoków bezwarunkowych. A co do skoków - zwróćmy też uwagę na wykorzystanie skoków relatywnych, adres docelowy nie jest wartością bezwzględną (czyli gdzie należy wylądować) ale raczej - odległością, czyli jak daleko, w przód lub tył. Wykorzystanie skoków relatywnych (jr - jump relative) jest fajne, bo daje nam w efekcie kod relokowalny, który może być bez ponownej translacji uruchomiony w dowolnym miejscu pamięci, przynajmniej teoretycznie. W przykładzie powyżej całą zabawę psuje nieco ... `call nnnn` i pomimo że i program główny i procedurka obsługująca klawisze korzystają ze skoków relatywnych, to pomiędzy nimi jest twarda zależność w postaci stałego adresu dla `whenXXXKeyPressed`. Działanie procedury CSTS na żywo: ❦ procedura CI - $FFC6 - pobranie znaku z klawiatury Procedura CI czeka na naciśnięcie klawisza, gdy to się stanie generowany jest sygnał dźwiękowy, w akumulatorze mamy kod tablicowy guzika. Dla zwykłych przycisków flaga Z procesora jest ustawiana na 0. Gdy procedura ustawi Z=1 to oznacza wciśnięcie [=] - tu flaga CY jest 1 lub [.] gdy flaga CY=0. Program testowy: ⇨ example_CI.asm
Program komentarza specjalnego nie potrzebuje, zauważmy jednak ciemne strony klecenia w assemblerze - linijki od etykiety `dotOrEqual`, literał C pojawia się ciurkiem, raz to rejestr C raz flaga CY, początkującym często od tego witki opadają, ale z biegiem czasu zaczynamy taki kod ze zrozumieniem czytać, a póki co film: Dodatkowy film poniżej wręcz pretensjonalnie pokazuje, że procedura CI zwraca sterowanie po wciśnięciu klawisza, takie zachowanie należy mieć na uwadze projektując interfejs dla człowieka w naszej aplikacji. ❦ procedura TI - $0007 - pobranie cyfry z klawiatury z echem Procedura TI pobiera z klawiatury cyfrę szesnastkową wyświetlając jednocześnie jej echo na zadanej pozycji wyświetlacza. Informacja o guzikach [=][.] - jak dla procedury CI. Procedura widzi tylko cyfry 0..F, a programik testowy wygląda następująco: ⇨ example_TI.asm
Program jest bliźniaczy do CI, tylko bez kasowania wyświetlacza procedura CLR, a działa następująco: ❦ procedura PARAM - $01f4 - pobranie słowa 16-bit do HL z echem Procedura PARAM pobiera cztery cyfry szesnastkowe układając je w rejestrze HL, echo pokazywane jest zgodnie z ustawioną pozycją wyświetlacza. Wprowadzanie kończymy klawiszem [=] co ustawy CY=1 lub klawiszem [.] który ustawi CY=0. Zastosowanie: ⇨ example_PARAM.asm
Ten programik celowo jest zamotany, a mianowicie - wyświetlamy człowiekowi prompt 'Adr=', procedurką PARAM pobieramy cztery cyfry szesnastkowe, program potraktuje je jako adres pamięci CA80 i zależnie od wartości napisze nam do którego banku on należy. Dla przypomnienia, adresacja w CA80 jest następująca:
Program na okazję każdego z banków ma stosowny komunikat na wyświetlacz (`socketU9`...`socketU12`), adresy komunikatów zgromadzone są w tabeli `bankNamesArray`. Sztuka cała w tym, aby je dopasować do tego, co wprowadził człowiek. Bierzemy więc z liczby szesnastobitowej z rejestru HL dwa najstarsze bity, bo one właśnie odpowiadając liniom adresowym A15,A14 procesora Z80 identyfikują banki pamięci. Rotując w lewo dwa razy mamy z tego dwa najmłodsze bity wartości akumulatora, będą one z zakresu 0..3 czyli zaczynają pasować - toż to indeks komunikatu w tabeli. A skoro adresy komunikatów są dwubajtowe, każdy indeks mnożymy przez 2 (dodając samego do siebie) i tak dostajemy wskaźnik na miejsce, gdzie jest treść dedykowana danej kombinacji bitów wprowadzonego adresu, czyli nazwę (opis) banku.❦ procedura EXPR - $0213 - pobranie 16-bit parametrów na stos, z echem Procedura EXPR pobiera dwubajtowe parametry, których ilość jest zadana via rejestr C, kolejno wprowadzane przez użytkownika liczby są odkładane na stosie, wierzchołek stosu to ostatnio wprowadzona wartość. Kolejne liczby separujemy klawiszem [.], wprowadzanie kończymy klawiszem [=]. Programik demonstracyjny: ⇨ example_EXPR.asm
segment zmiennych programu
No i przy tym programiku, choć prostym pomału wychodzimy z piaskownicy, będzie konkretniej. Zacznijmy od tego, co tak naprawdę powyższy kod realizuje, a mianowicie: zależnie od narzuconej podczas kompilacji ilości parametrów pobiera owe procedurą EXPR i składuje w stosownej lokalizacji w pamięci RAM. Następnie w nieskończonej pętli wyświetla nazwę parametru, w ludzkiej postaci, licząc od 1 oraz jego szesnastobitową wartość. Tyle scenariusza, teraz o technikaliach. Wspominałam wcześniej o automatycznym wyliczaniu rozmiaru tabeli zmiennych oraz dziwnościach związanych z kolejnością operacji arytmetycznych jaką stosuje SB-Assembler. Aby nie męczyć się tymi detalami - najprościej je jakoś ukryć, w końcu czego oczy nie widzą to i sercu nie żal. Zauważmy zatem pojawienie się na samym początku programu dyrektywy `.in` załączającej do naszej aplikacji plik o wymownej nazwie `utilities.inc`, czyli wszelkie przydasie. Interesujące nas aktualnie fragmenty poniżej: ⇨ utilities.inc
Powyżej zdefiniowane są dwa makra `>BEGINARRAY` oraz `>ENDARRAY`, które otwierają i zamykają definicję tabeli zmiennych. Rozmiar elementu może być w sumie dowolny, podajemy go w makrze domykającym definicję. Para makr definiuje dodatkowe symbole wypracowane na podstawie nazwy podanej jako parametr i tak: powstaje symbol `xxxSize`, który jest rozmiarem tabeli w bajtach, powstaje symbol `xxxCount`, którego wartość odpowiada ilości elementów tabeli (rozmiar tabeli/rozmiar elementu). Na koniec mała, aczkolwiek przydatna rzecz - symbol `xxxLast` - wskazuje na ostatni element tabeli (nie na pierwszy wolny adres za, ale na ostatni w obrębie tabeli). To przydatne jest gdy chcemy iterować po elementach tabeli od końca, co zaraz z resztą zobaczymy. Tej parki makr możemy używać w dowolnym segmencie pamięci, czy to ROM, czy RAM, bez znaczenia. Takoż bez znaczenia jest sposób alokacji adresów - można korzystać z `.db`, `.dw`. `.bs N`, wszelkie wyliczenia są przez translator dokonywane w drugim obiegu, gdzie następuje finalna, twarda adresacja wszelkich dostępnych etykiet. W naszym programie makr tych użyjemy do konstrukcji tabeli dwubajtowych wartości o nazwie `parametersArray`, lokalizację ustawimy im z daleka od kodu, na przykład od adresu $8000. Konsekwencją naszych makr będzie możliwość skorzystania z symboli `parametersArrayCount` - translator wyliczy 4 elementy tabeli, oraz `parametersArrayLast` - to zostanie ustawione na adres czwartego elementu tabeli. Po skompletowaniu serii wartości na stosie procedura EXPR zwróci nam sterowanie no i teraz martw się babo, co z tą stertą liczb masz zrobić. Tu przydaje się właśnie sztuka z ***Last, możemy zdejmować ze stosu kolejne wartości i układać w pamięci począwszy od ostatniej lokalizacji. Po zakończeniu pętli (etykieta `saveParams`) mamy tabelkę `parametersArray` ładnie wypełnioną wprowadzonymi liczbami i to w odpowiedniej kolejności. Teraz należałoby je pokazać - to odbywa się w drugiej pętli opatrzonej etykietą `showParams`. Widzimy tu zastosowanie ***Count - w bardzo prosty sposób możemy zainicjować licznik pętli ilością elementów, wszystko jest już wyliczone. Licznik pętli (rejestr B) zmienia wartości w dół, my chcemy pokazywać kolejne nazwy Pxx w górę i to nie od zera (po komputerowemu) ale od jeden, bardziej po ludzku. Stąd właśnie konieczność dodania w locie +1 do wartości wyświetlanej na ekraniku. Zauważmy też, że program sam dostosuje się do zmian rozmiaru tabeli, polecam dla sportu dodać kilka nowych elementów (dyrektywą `.bs 2`) i zaobserwować jak zmieniły się wartości wyliczone przez translator. Oczywiście filmik z działania naszego programu: ❦ procedura HILO - $023b - inkrementacja HL i porównanie z DE, iterator Procedura HILO jest dość specyficznym stworzeniem na tle dotychczas opisywanych, pełni rolę pomocniczą i korzystamy z niej niejako w tle. Do pracy wymaga zainicjowanych dwóch par rejestrów HL oraz DE, z których to HL jest inkrementowany przy każdym wywołaniu procedury. Następnie zależnie od relacji z wartością rejestru DE ustawiana jest flaga CY=1 gdy DE<HL, CY=0 gdy DE≥HL. Fakt, że procedura operuje na wartościach szesnastobitowych doskonale pretenduje ją do wszelkich transferów obszarów pamięci czy wypełniania jej w zadanym zakresie adresów. Programik demonstracyjny to prosta kopiarka do RAM, podajemy adres początku, końca oraz adres docelowy, pod który należy dane przesłać: ⇨ example_HILO.asm
W powyższym programie zagadek żadnych już nie ma, zwróćmy tylko uwagę, że pracujemy głównie z rejestrami - wartości adresów, które procedura EXPR chomikowała na stosie zbieramy do rejestrów roboczych, do bezpośredniego wykorzystania. No i jeżeli chcemy jakąś bardziej skomplikowaną pętlę koordynować procedurką HILO (taki niby-odpowiednik rozkazu `djnz`), to należy koniecznie zadbać o ochronę wykorzystywanych przez nią rejestrów HL oraz DE, widzimy to w procedurze `showProgress`, gdzie prologu i epilogu jest więcej niż faktycznego kodu roboczego. Kopiarka w działaniu prezentuje się następująco: ❦ procedura ZMAG - $0626 - zapis wskazanego obszaru na magnetofon z podaniem nazwy zbioru Procedura systemowa ZMAG umożliwia zapis obszaru pamięci wskazanego via rejestry HL (początek, pierwszy bajt) i DE (koniec, ostatni bajt) na taśmę magnetofonową. Dodatkowym parametrem procedury jest nazwa (identyfikator) zbioru przekazywany w rejestrze B. Podczas zapisu CA80 pokazuje nazwę zbioru, następnie kolejne adresy zapisywanych bloków danych, należy mieć to na uwadze planując nagrywanie dłuższych plików. Szczegółowe omówienie współpracy z magnetofonem znajdziemy w rozdziale 1.9 tomiku MIK05 o analogicznej nazwie. ❦ procedura ZEOF - $067B - zapis rekordu EOF na magnetofon Procedura systmowa ZEOF zapisuje na taśmie magnetofonowej rekord EOF, czyli jednobajtowy identyfikator pliku (podawany w rejestrze B) i adres wejścia do programu podawany w parze HL. Rekord EOF zapisujemy za własnym programem nadając mu identyczną nazwę - to zapewni jego proste (bez podawania adrese startowego) uruchomienie zleceniem *G. Zapis EOF jest odnotowywany na wyświetlaczu Przykład poniżej dotyczy obu wymienionych procedur ZMAG,ZEOF - program testowy ⇨ example_ZMAG_ZEOF.asm zapisuje na taśmę szesnastobajtowy obszar danych od adresu 0xD000 nadając zbiorowi nazwę 0xAA. Następnie zapisuje rekord EOF, nadając mu identyczną nazwę (0xAA) oraz utrwalając adres początkowy dla zlecenia *G na wartość 0x1234.
Skromny efekt działania: oznaczający wygenerowanie w formie analogowej następujących sekwencji bajtów:
Sygnał wysyłany na magnetofon przez przykładowy program wygląda następująco: ![]() Przy odrobinie wysiłku można policzyć na powyższym rysnuku zakodowane Manchesterem paczki kolejnych bitów - mamy ich 95, wynika to z zapisu sekwencji synchronizacji (32 bajty), rekordu danych (24 bajty), ponownie synchronizacji przed EOF (32 bajty) oraz samego rekordu EOF - 7 bajtów. Oczywiście można także posłuchać przykładowego programu ⇨ example_ZMAG_ZEOF.mp3 Sam rekord EOF (poprzedzony sekwencją synchronizacji) zapisywany procedurą ZEOF wygląda tak: ![]() Oraz powiększenie strumienia bitów, widzimy jak kodowane są kolejne zera i jedynki w jednym pakiecie danych. ![]() ❦ procedura OMAG - $071B - odczytanie zbioru rekordów o wskazanej nazwie z taśmy Procedura OMAG kompleksowo obsługuje odczyt zbioru z taśmy magnetofonowej, ładowane do pamięci są tylko te rekordy, których nazwa jest identyczna z zadeklarowaną w rejestrze B, przykład wywołania poniżej:
Wykonanie powyższego programu ⇨ example_OMAG.asm spowoduje załadowanie kodu przykładowego programiku dla ZMAG/ZEOF, zleceniem *D możemy zweryfikowac np. zawartość pamięci od adresu 0xD000 - kolejne liczby hex. Powyższy opis jest ściśle związany z rozdziałem `4.0 Definicje procedur systemowych` tomiku MIK05 dokumentacji CA80 i proszę go potraktować jako nieco bardziej rozbudowany suplement. Chcąc naprawdę świadomie i efektywnie korzystać z procedur systemowych CA80 należy wspomniany rozdział dokładnie przeczytać i z każdą z procedurek samodzielnie poeksperymentować. Bardzo ważne jest zrozumienie filozofii APWYS/PWYS, czyli zmiennych koordynujących pisanie po wyświetlaczu. Proszę też zauważyć, że wszystkie prezentowane tu przykłady dotyczą wywołań systemowych, gdzie parametr PWYS jest przekazywany w formie stałej następującej po rozkazie `call nnnn`, to jest tak zwane (w MIK05) wywołanie z aktualizacją PWYS. #slowanawiatr, kwiecień/listopad 2019 |
![]() |
![]() |
![]() |