Tajemnice ATARI

PROGRAMOWANIE PROCESORA 6502
W KOMPUTERACH ATARI XL/XE cz. IV

Ludzie listy piszą...

   Wnikliwi czytelnicy dopatrzyli się nieścisłości w poprzednich odcinkach. Napisałem bowiem kiedyś, że znacznik V jest zmieniany tylko pod wpływem rozkazów ADC, SBC, PLP, BIT, zaś na znacznik C wpływają tylko: ADC, SBC, PLP, ROL, ROR, ASL, LSR. Zapomniałem przy tym zupełnie o rozkazach modyfikujących bity rejestru znaczników. Rozkazy te należą do grupy rozkazów wewnętrznych procesora (nie komunikują się z pamięcią). Pozwalają wymusić konkretne stany niektórych znaczników. Oto ich wykaz:

CLC zeruje znacznik C
SEC ustawia znacznik C
CLI zeruje znacznik I
SEI ustawia znacznik I
CLV zeruje znacznik V
CLD zeruje znacznik D
SED ustawia znacznik D

   Jak widać, nie każdy znacznik da się dowolnie ustawić. Znaczenie i sposób wykorzystania tych rozkazów będą omawiane w miarę nadchodzących potrzeb.

   Inni czytelnicy, użytkownicy QA, donoszą, że mimo starannego przepisania przykładów z pierwszego odcinka (przykłady polegały na zmianach kolorów w trybie tekstowym) nic im się na ekranie nie zmieniło. Myślę, że są oni w błędzie. Quick Assembler bowiem używa osobnego ekranu dla testów użytkownika i osobnego dla siebie. Nieporozumienie polega na tym, że program po zmianie kolorów natychmiast przekazuje sterowanie do QA i ekran testowy znika. Dzieje się to tak szybko, że oko nie zdąży zarejestrować efektu. Ekran użytkownika jest wszakże starannie przechowywany i zawsze można do niego zajrzeć naciskając klawisze SHIFT/CONTROL/SPACJA (podręcznik, s. 37).

   Nie podejrzewam bowiem miłych mych czytelników, że uruchamiają programy od niewłaściwego adresu (przykłady zaczynają się od 1152, czyli $480 szesnastkowo, zatem adres Setup/Run musi być ustawiony na 480).

   Ponieważ w wielu listach poruszany jest problem braku kursu programowania w podręczniku dołączonym do pakietu QA, pragnę podkreślić, że podręcznik ten jest ukierunkowany "gałkologicznie", to znaczy ma za zadanie wyjaśnić, co gdzie nacisnąć, aby zestaw zaczął działać. Sztuki programowania, niestety, nie sposób nauczyć na 64 stronach (obawiam się zresztą, że i 640 stron byłoby za mało). Niniejszy cykl przybliża nieco zagadnienia programowania, ale żeby zostać orłem, to nie wystarczy. Programowanie w języku asemblera jest dość trudne, więc lepiej łyknąć trochę teorii (podręcznik [l]), należy też znać dobrze wnętrze swego komputera (polecam książkę [2]), trzeba orientować się w sposobie wykorzystania poszczególnych obszarów pamięci (kłania się [3]), dobrze jest znać różne kruczki i sztuczki (zawarte w [4]) pomaga też ogonie oczytanie informatyczne (warto przeczytać [5], [6], [7], [8]).

[1] Rodney Zaks - Programming the 6502
[2] Lutz Eichler, Bernd Grohmann - ATARI 600XL/800XL Intern
[3] Ian Chadwick - Mapping the ATARI
[4] pr. zbiorowa - De Re ATARI
[5] Niklaus Wirth - Algorytmy + Struktury danych = Programy
[6] Dennie van Tassel - Praktyka programowania
[7] Niklaus Wirth - Wstęp do programowania systematycznego
[8] Edsger W. Dijkstra - Umiejętność programowania

   Szczególnie natomiast odradzam programowania w języku asemblera tym osobom, dla których BASIC okazał się za trudny. Jest z tym podobnie, jak ze skokami akrobatycznymi do wody: trzeba najpierw nauczyć się pływać.

Podprogramy i stos

   Możliwość definiowania powtarzalnych ciągów rozkazów w postaci wydzielonych fragmentów (tzw. procedur czyli podprogramów) i wywoływania ich z dowolnego miejsca w programie, tak często niedoceniana, ma podstawowe znaczenie dla przejrzystości i niezawodności programu. Używa się pary rozkazów: JSR (skok do podprogramu) i RTS (powrót z podprogramu). Wykonanie rozkazu JSR, znanego także jako "skok ze śladem", składa się z dwóch etapów. W pierwszym procesor umieszcza na stosie adres służący do odnalezienia dalszego ciągu programu głównego (rozkazu następującego po JSR). Drugi etap - to skok, analogicznie jak przy rozkazie JMP: jako następny wykona się rozkaz spod adresu danego argumentem. Rozkaz RTS przywraca wykonywanie przerwanej JSR-em sekwencji rozkazów poprzez pobranie ze stosu zapamiętanego tam adresu. Oczywiście rozkazy te można wykonywać tylko w rozumnej kolejności, bo nie poprzedzony JSR-em RTS nie ma przeważnie żadnego sensu. Przyjrzyj się przykładowi:
       OPT %10101 

*--- system

ZEGAR  EQU 20
POKEY  EQU $D200 
OKRES  EQU POKEY+0 
BARWA  EQU POKEY+1

*-- stale

CZAS   EQU 5
BARW   EQU $A0
GLOS   EQU $08
TON_l  EQU 100
TON_2  EQU  80
TON_3  EQU  67   
TON_4  EQU  50



       ORG $480

*--- program główny

POCZ   LDA #TON_l 
       JSR GRAJ 
       LDA #TON_2
       JSR GRAJ
       LDA #TON_3
       JSR GRAJ
       LDA #TON_4
       JSR GRAJ
       RTS

*-- podprogram: graj dzwiek
* (okres dzwieku w A)

GRAJ   EQU *
* wlacz granie
       STA OKRES
       LDA #BARW+GLOS
       STA BARWA
* poczekaj
       CLC
       LDA ZEGAR
       ADC #CZAS
CZEK   CMP ZEGAR
       BNE CZEK
* zamilcz
       LDA #0
       STA BARWA
* powrót z podprogramu 
       RTS

       END
    Główny program składa się z czterech wywołań procedury GRAJ. Każde wywołanie powoduje wyemitowanie dźwięku o czasie trwania określonym etykietą CZAS. Ponieważ dźwięki różnią się częstotliwością, program główny przekazuje do podprogramu informację liczbową. Najwygodniej użyć w tym celu rejestru, który inicjuje się przed wywołaniem procedury. W tym przypadku jest to akumulator. Rozkaz RTS w procedurze GRAJ powoduje powrót do głównego programu, w miejsce wywołania, tuż po odpowiednim rozkazie JSR. Rozkaz RTS w głównym programie powoduje powrót do asemblera (o ile jest to QA, to Twój testowany program jest wywoływany właśnie rozkazem JSR).

   Jeżeli podprogram wywołuje kolejny podprogram, używając rozkazu JSR, to nic nie szkodzi, ponieważ kolejne adresy odkładają się na stosie nie niszcząc poprzednich, dokładnie tak, jak to sugeruje jego nazwa. Kolejne rozkazy RTS będą pobierać dane w odwrotnej kolejności, niż były układane (adres z pierwszego JSR-a zostanie zdjęty na końcu).

   Stos jest w stanie pomieścić 256 bajtów (czyli 128 adresów) i "chodzi w kółko", to znaczy przy przepełnieniu zaczyna "zjadać" najwcześniej składowane dane. W praktyce nie dostajemy do dyspozycji całego stosu, bo nasz program wywoływany jest zwykle przez jakiś inny podprogram głównego programu systemu operacyjnego. Trzeba więc zadbać, by podprogramy nie wywoływały się nawzajem bez końca. W praktyce, dla większości poprawnie napisanych programów, pojemność stosu jest (z dużym zapasem) wystarczająca.

   Stos zajmuje stronę 1 pamięci komputera i jest w złym guście umieszczać tam cokolwiek innego.

Notacja szesnastkowa

   Powyższy program zawiera kilka liczb rozpoczynających się od znaku "$". Są one zapisane w notacji szesnastkowej. Wielu adeptów sztuki programowania podchodzi do liczb szesnastkowych jak do jeża. Tymczasem ten sposób zapisu jest prosty i wdzięczny. Przy zwięzłości przewyższającej nawet system dziesiętny daje wyraźny podział na bajty, typowy dla notacji dwójkowej. Pozwala to wyodrębniać starszy i młodszy bajt liczby dwubajtowej (adresu) bez żadnych przeliczeń.

   Na pierwszy rzut oka można stwierdzić, na której stronie pamięci znajduje się dany adres, np.: $480 - czwarta, $612 - szósta. Jeżeli nieobca Ci jest idea zapisu dwójkowego, to szesnastkowy przełkniesz bez kłopotu, ponieważ istnieje między nimi odpowiedniość podległa prostym regułom. Każdą czwórkę bitów liczby dwójkowej zastępuje się pojedynczym symbolem, I tak:

%0000 = $0 (0)
%0001 = $1 (l)
%0010 = $2 (2)
%0011 = $3 (3)
%0100 = $4 (4)
%0101 = $5 (5)
%0110 = $6 (6)
%0111 = $7 (7)
%1000 = $8 (8)
%1001 = $9 (9)
%1010 = $A (10)
%1011 = $B (11)
%1100 = $C (12)
%1101 = $D (13)
%1110 = $E (14)
%1111 = $F (15)

   To pokrywa wszystkie możliwe kombinacje czterech bitów. Pamiętać tylko należy, że bity w liczbie dwójkowej liczy się od prawej strony (gdyby ich brakło do pełnej czwórki, można z lewej uzupełnić zerami).

   Zatem liczba %1110010001110111 to po ludzku $E477. Proste? Układ dźwiękowy ATARI, zwany POKEY, zajmuje w pamięci komputera stronę o numerze $D2. Jego rejestry znajdują się pod adresami $D200, $D201, $D202, itd... Tak właśnie odwołuje się do nich program przykładowy.

Procedury systemowe

   Pamięć stała (ROM) w naszym komputerze zawiera szereg procedur, które można, a często nawet trzeba wykorzystywać w swoich programach. Prosty przykład:
JSR $F556
   Uzupełnij jak zwykle o OPT, ORG z przodu i RTS, END z tyłu. Co to robi? W większości komputerów ta procedura generuje dźwięk ostrzegawczy, znany jako BELL. Jeżeli Twój komputer działa inaczej, to być może procedura BELL siedzi tam pod innym adresem (albo Twój głośnik nie działa!). Wykorzystanie takiej procedury odbywa się na ryzyko programisty, bo komputery różnią się między sobą. Bywają jednak adresy pewne, których niezmienność gwarantowana jest przez firmę ATARI. Większość z Was zna z pewnością procedury, które mają swe początki pod adresami $E471 (Self Test), $E474 (ciepły start, w znacznej mierze podobny do sytuacji po naciśnięciu klawisza RESET), $E477 (zimny start, wymuszający stan komputera jak bezpośrednio po włączeniu). Procedury z nich trochę nietypowe, bo nie wykazują ochoty wracać do wywołującego je programu (nie kończą się rozkazem RTS), przeto nie poświęcimy im wiele uwagi. Lecz widać przy okazji, że ich adresy początkowe odległe są o 3 bajty. Początki tych oficjalnych procedur ułożone są bowiem w rodzaj tablicy, której każdy element jest rozkazem skoku JMP (taki rozkaz ma właśnie długość trzech bajtów) do właściwego miejsca, gdzie kontynuowana jest dana procedura.

   Ta tablica skoków (zwanych w literaturze wektorami) zaczyna się pod adresem $E450, a kończy gdzieś, hen... Różne źródła różnie o tym mówią. W moim komputerze ostatni rozkaz JMP leży pod adresem $E48C, nie wszystkie jednak z nich są dobrze udokumentowane.

   Jedną z najprzydatniejszych procedur systemowych jest procedura wejścia/wyjścia znana jako CIO lub CIOV. Zaczyna się ona pod adresem $E456. Można jej użyć np. do wyświetlenia tekstu na ekranie:
*--- wyswietlanie napisu

       OPT %l0l0l

IOCB   EQU $340
IO_COM EQU IOCB+2
IO_ADR EQU IOCB+4
IO_LEN EQU IOCB+8

CHN0   EQU 0
PISZ   EQU 11
EOL    EQU $9B
CIOV   EQU $E456

       ORG $480

       LDX #CHN0
* rozkaz wyslania napisu
       LDA #PISZ 
       STA IO_COM,X
* adres tekstu
       LDA TEXT
       STA IO_ADR+1,X
* rozmiar tekstu 
       LDA ILE
       STA IO_LEN+1,X
* wykonaj procedure WE/WY
       JSR CIOV
* powroc do QA
       RTS

* tekst do wyswietlenia
TEXT   DTA  C'Oto ten tekst!'
       DTA  B(EOL)
ILE    EQU *-TEXT

* koniec programu
       END
   Informacje niezbędne dla wykonania operacji przekazuje się w tak zwanym bloku sterowania WE/WY, zwanym IOCB. Ponieważ tych bloków jest kilka, trzeba wskazać właściwy poprzez odpowiednie ustawienie rejestru X. Wartość 0 oznacza blok nr 0 (związany na stałe z ekranem). W komórce określonej tu jako IO_COM przekazuje się żądanie (11 oznacza wysłanie informacji na ekran). W słowach IO_ADR i IO_LEN przekazujemy informację o tym, co wysłać i ile tego jest. Pozostałych bajtów bloku IOCB (ma on długość 16) nie musimy w tym przypadku wypełniać. Po wyświetleniu tekstu procedura CIOV powraca do naszego programu.

Dyskowy plik binarny

   Dotychczasowe przykłady uruchamiane były pod kontrolą QA. Nie jest to trudne, bo wystarczy ustawić Setup/Run tak, aby wykonywanie programu rozpoczęło się od właściwego adresu (najczęściej pokrywa się on z argumentem pierwszego ORG-a). Uzyskanie samodzielnego programu, który można wywołać bezpośrednio spod DOS-u lub COS-u też nie jest trudne, choć wymaga kilku zabiegów.

   Przede wszystkim zmiany wymaga argument rozkazu OPT. Zapis pliku w formacie dyskowym następuje podczas drugiego przebiegu asemblacji, o ile w argumencie OPT jest ustawiony bit 5, a wyzerowany 6. Rozkaz ten będzie więc miał postać
OPT %01xxxxx
gdzie literkami x oznaczono bity związane z generowaniem kodu do pamięci, drukowaniem, wyświetlaniem na ekranie i trybem "listowania". Ponieważ są one całkowicie niezależne od bitów włączających zapis na dysk, można je ustawiać dowolnie, wedle potrzeb. Ale bez przesady! Podczas nagrywania kodu na taśmę pełne listowanie programu może być przyczyną przestojów, co spowoduje wzrost odstępów między rekordami. Z kolei równoległe umieszczanie kodu w pamięci ogranicza rozmieszczenie programu, gdyż QA broni się przed zniszczeniem. Dlatego w praktyce podczas zapisu kodu na dysku, a zwłaszcza na kasecie, pozostałe opcje się wyłącza. Wskazane jest wykonać najpierw próbną asemblację bez kodu, aby upewnić się, że program jest syntaktycznie poprawny.

   Ostatnią operacją przed stworzeniem pliku wynikowego z programem może być nadanie pożądanej nazwy temu plikowi. Robi się to w menu File/Obj, gdzie figuruje zwykle nazwa domyślna, którą QA ustawia zawsze podczas Load i Save wzorując się na nazwie pliku z programem (domyślne rozszerzenie będzie .OBJ). Nazwa N0NAME, którą QA przyjmuje w chwili uruchomienia, jest nazwą awaryjną i należy unikać wykorzystania jej do nazywania rzeczywistych plików. Oznacza ona po prostu BRAK NAZWY i sygnalizuje, że nic tam jeszcze nie wpisywaliśmy. Dla zapisania kodu na kasecie trzeba oczywiście wpisać C:. W przypadku korzystania z COS-u dobrze jest wczytać i znów zapisać kod programu kopierem NameCopy, aby nadać nazwę, ponieważ QA sam tego nie robi.

   Czy już wszystko gotowe? Ostatni przykład programu świetnie się nadaje do takiego eksperymentu. Gdy plik wynikowy już gotów, można wyjść z QA. Pod DOS-em (mam na myśli system dystrybuowany przez AVALON wraz z QA) pisze się po prostu nazwę pliku, a o ile rozszerzenie jest .COM, to można je pominąć. Pod COS-em pisze się **.

   I co? I nic. To dobrze. DOS lub COS załadował program do pamięci pod właściwy adres, lecz na tym koniec. Aby go teraz uruchomić, napisz RUN 480. Jak zrobić, żeby program sam się uruchamiał, będzie za miesiąc.

Janusz B. Wiśniewski



Powrót na start | Powrót do spisu treści | Powrót na stronę główną

Pixel 2001