Tajemnice ATARI

PROGRAMOWANIE PROCESORA 6502
W KOMPUTERACH ATARI XL/XE

Programy z klocków

    Każdy, kto napisał już w życiu kilka programów, spostrzega, że pewne procedury powtarzają się w wielu z nich. A zatem przy pisaniu kolejnego dzieła korzysta się nieraz z gotowych rozwiązań. Quick Assembler z jego operacjami na blokach tekstu wydaje się wręcz stworzony do tego, by składać nowe programy ze sprawdzonych fragmentów starych.

   Jednak podczas takiego montowania programu napotyka się często na szereg trudności. Kawałki nie chcą pasować do siebie z uwagi na konflikty nazw, odmienne wykorzystywanie pewnych komórek pamięci, różny sposób przekazywania parametrów. Można temu zapobiec, jeśli już w trakcie pisania takiego często wykorzystywanego modułu będziemy pamiętać o kilku prostych zasadach. Pokażę to na przykładzie prostego programu.

Stwórz plik nagłówkowy

   Często odwołujemy się do procedur systemowych (takich jak CIOV, WARMST, SETVBV).
* TA POKER     autor: JBW
* (c) 1992 Tajemnice ATARI

          opt %100101


*-- procedury w ROM

afp       equ $D800
fpi       equ $D9D2
ciov      equ $E456
   Sięgamy też do tablic i pojedynczych komórek (jak IOCB, BOOT, CONSOL). Ustalmy dla nich, podobnie jak do procedur, stałe nazwy (najlepiej te najczęściej spotykane w literaturze). Będzie łatwiej zapamiętać, co dany kawałek robi. Przy okazji nasz program stanie się czytelny i zrozumiały dla innych. Te procedury wykorzystuje się w wielu programach. Stwórz osobny plik deklaracji EQU tych najważniejszych etykiet. Można go dołączać do każdego pisanego programu (najwygodniej - z RAM-dysku, rozkazem ICL).
*--- rejestry pakietu FP
fr0      equ $d4
cix      equ $f2
inbuff   equ $f3

*--- system

runad    equ $2E0 
initad   equ $2E2
dosrun   equ $A 
dosini   equ $C 
iocb     equ $340
   Jeżeli piszesz następny program, korzystający z innych procedur i komórek, możesz idopisać je do dotychczasowego zestawu. Dzięki zastosowaniu nazw uświęconych tradycją nie ma obawy, że zostały lub zostaną użyte w innym znaczeniu.
io_com   equ iocb+2
io_sta   equ iocb+3 
io_adr   equ iocb+4 (2) 
io_len   equ iocb+8 (2) 
io_mod   equ iocb+10 
io_aux   equ iocb+11
   Często w programach używamy stałych. Niech was ręka boska broni przed pisaniem "LDX #16"! Cóż to jest takiego? To może oznaczać wszystko. Jeżeli napiszesz "LDX #CHN1", to każdy (nawet ja) zorientuje się, o co chodzi.
*-- stale

chn0     equ 0   channel $0      
gett     equ 5   get text
putt     equ 9   put text 
getb     equ 7   binary get 
putb     equ 11  binary put
eol      equ 155 end of line
   Nie żałuj komentarzy. Wierzę, że nie cierpisz na sklerozę i będziesz jutro, a nawet pojutrze pamiętać, co robi Twój program. Ale zapomnisz za miesiąc. Albo za rok. Ostatnio przeżyłem takie właśnie rozczarowanie, sięgnąwszy do swych dawnych programów, by skorzystać z zawartych w nich pomysłów. Niestety! Programy były tak napisane, że nie potrafiłem z nich nic zrozumieć.

   Wiemy, które komórki strony zerowej nadają się do wykorzystania. W większości programów używa się zmiennych roboczych leżących w tym obszarze. Warto stworzyć uniwersalny ich zestaw. Nadaj im nazwy raz na zawsze. Przyda się z pewnością kilka komórek jednobajtowych (można je, stosownie do przeznaczenia, nazywać BYTE, CHAR, FLAG, COUNTER lub po naszemu BAJT, ZNAK, ZNACZNIK, LICZNIK) i dwubajtowych (ADDR, PTR, WORD albo ADRE, WSK, SŁOWO).
*--- strona 0

byte     equ $cb
addr     equ $cc
word     equ $ce
   Nie martw się. Jeśli nasz uniwersalny plik nagłówkowy nie będzie od razu doskonały. Każdy następny program przynosi nowe doświadczenia. Gromadź je cierpliwie, modyfikując zestaw definicji. Od czasu do czasu sprawdź, czy Twoje stare programy asemblują się jeszcze z tak ulepszonym zestawem będą, jeżeli zmiany polegały tylko na dodawaniu nowych, unikalnych definicji. Użytkownicy stacji dysków mogą ten plik dołączać za pomocą rozkazu ICL. Wielbiciele magnetofonu będą zapewne woleli wgrać go do edytora i dobudować do niego resztę programu.

TA Poker

   Program przykładowy będzie umożliwiał wykonanie instrukcji POKE z poziomu DOS-u lub COS-u. Czasem chciałoby się jej użyć, by np. zmienić kolory lub uruchomić silnik magnetofonu, a BASIC jest akurat odłączony...

   Jak każdy, tak i nasz program wykonuje operacje wejścia/wyjścia (trudno sobie wyobrazić taki program, który nie pobiera żadnych danych i nie objawia w żaden sposób efektów swej pracy). Najpospoliciej używaną ich formą jest wyświetlanie komunikatów i pytań. Procedura, która to robi, może (raz opracowana) być bez zmian przenoszona z programu do programu. Lepiej wszakże zachować ją (i jej podobne) w osobnym pliku, zwanym biblioteką, który można później wykorzystywać, podobnie jak nagłówek, dołączając do różnych programów.

   Najlepsze efekty daje konstrukcja "inteligentnych" procedur. Nasza będzie umiała znaleźć i wyświetlić odpowiedni komunikat, znając tylko jego kolejny numer. W tym przypadku będą potrzebne cztery: pusty - dla wprowadzania odstępów, tytułowy - na przywitanie, pytanie o adres, pytanie o bajt. W innym programie teksty mogą być inne - procedura się nie zmieni
*--- numery komunikatow

nul_m    equ 0
tit_m    equ l
adr_m    equ 2 
byt_m    equ 3
   Jest wskazane, by wykonanie programu zaczynało się od początku jego kodu. Nie jest to oczywiście konieczne w typowych przypadkach, lecz czasami bardzo się przydaje. Ułatwia na przykład dołączenie RELOCATORa, zrezygnowanie w SpartaDOS-ie z podawania adresu startu... Aby to uzyskać, można umieścić główny blok programu na początku. Równie dobre byłoby rozpoczęcie od JMP MAIN do miejsca, gdzie faktycznie zaczyna się program.
     org $8000
main     jsr init
* glowna petla
loop     ldx #nul_m
         jsr dsp_msg
* pobierz adres
         ldx #adr_m
         jsr get_text
         bmi loop
* pusty wiersz?
         dec io_len,x
         beq quit  koniec
* dekoduj adres
         jsr deco
         sta addr 
         sty addr+1
* pobierz bajt
         ldx #byt_m
         jsr get_text 
         bmi loop
* pusty wiersz?
         dec io_len,x
         beq loop  od nowa
* dekoduj bajt
         jsr deco
* wstaw bajt pod adres
         ldx #0
         sta (addr,x)
* jeszcze raz
         jmp loop
* powrot do DOS-u
quit     jmp (dosrun)

Plan

   Rozpoczynając pisanie rozumiałem, że trzeba na wstępie wykonać jakieś operacje przygotowujące, lecz nie byłem pewien jakie. Stąd "JSR INIT", potem coś się wymyśli.

   Główna pętla jest dość prosta. Konstruując ją, myślę o dostępnych mi klockach-procedurach i staram się przede wszystkim z nich układać akcję programu. Ich nieobecnością na razie się nie przejmuję. W najgorszym razie te fragmenty, których nie znajdę w dotychczasowej bibliotece, będę musiał dopisać (i dołączyć do biblioteki!). Rozpoczynam od wyświetlenia pustego wiersza (robi na ekranie odstęp po poprzednich tekstach). Wykona to procedura DSP_TXT. Pobranie adresu polega na wyświetleniu pytania, odebraniu tekstu (zrobi to procedura GET_TEXT) i zdekodowaniu go, by otrzymać liczbę. Wprowadzenie przez użytkownika pustego tekstu (sam RETURN) będzie oznaczało rezygnację. Długość tekstu można znaleźć w stosownych komórkach tablicy IOCB (rejestr X wskazuje konkretny blok). Ponieważ nie da się wprowadzić dłuższego tekstu od 120 bajtów (pomyśl, dlaczego?), cała liczba mieści się w jednym bajcie. Jest ona równa co najmniej 1, bo do tekstu wilcza się także kończący RETURN. Procedura DECO zwróci wprowadzone słowo w rejestrach A i Y. Analogicznie pobiera się bajt i... POKE! Ponowny skok na początek pozwala na wprowadzenie wielu "poków".
*--- wypisz tekst

dsp_msg  equ *
* odszukaj tekst nr X
         ldy #0
fm0      dex
         bmi mout
fmes     lda data,y
         iny
         cmp #eol
         bne fmes
         beq fm0   (jmp)
* wypisz
mout     txa
         ldx #chn0
         sta io_len,x
         clc
         tya  
         adc dtaa
         sta io_adr,x
         lda #0
         sta io_len+1,x
         adc dtaa+1
         sta io_adr+1,x
         lda #putt
         sta io_com,x
         jmp ciov

Podprogramy

   Odszukanie tekstu na podstawie numeru jest proste. Jeżeli każdy tekst zajmuje jedną linijkę (kończy się znakiem End-Of-Line). Dodatkowo ułatwia sprawo fakt, że wszystkie teksty zajmują mniej niż 256 bajtów, dzięki czemu można je objąć zakresem zmian rejestru indeksowego Y. Do wyświetlenia tekstu posłużyłem się rozkazem WE/WY "put text" (5), który powoduje wyświetlenie jednej linijki tekstu. Do procedury CIOV przekazuję jakąkolwiek, dużą długość, a operacja przebiega tylko do pierwszego E-O-L.
*--- pobierz tekst
get_text jsr dsp_msg
         ldx #chn0
         lda #gett
         sta io_com,x
         lda txta
         sta io_adr,x
         lda txta+1
         sta io_adr+1,x
         sta io_len+1,x
         jmp ciov
    Analogicznie przebiega odebranie tekstu. Dla rozkazu WE/WY gett podaje się dowolną długość, byle większą od spodziewanej, a w odpowiedzi w słowie IO_LEN otrzymujemy rozmiar przesłanych danych.
*--- dekodowanie
deco     lda txta
         sta inbuff
         lda txta+1
         sta inbuff+1
         lda #0
         sta cix
         jsr afp
         jsr fpi
         lda fr0
         ldy fr0+1
         rts
   Do zamiany wprowadzonego tekstu (kodów ATASCII cyfr) najwygodniej użyć procedur arytmetycznych zawartych w ROM-ie. Procedura AFP zamienia ciąg znaków na liczbę rzeczywistą w tzw. formacie zmiennoprzecinkowym (w skrócie FP). Adres tekstu przekazuje się w słowie INBUFF, zaś bajt CIX zawiera odległość liczby od początku tekstu. Rezultat znajdzie się w rejestrze FR0. Procedura FPI zamienia liczbę z formatu FP na dogodną dla nas dwu bajtową postać binarną. Rezultat znajdziemy także w FR0.

   Na etapie testów można używać procedury początkowej v postaci "INIT RTS". Później okazuje się zwykle, że należy na wstępie coś zrobić, np. wyzerować pewne komórki. Nasz program wyświetli tylko swój tytuł.
*--- procedura poczatkowa
init     ldx #tit_m
         jmp dsp_msg

Końcówka

Często zachodzi potrzeba przesunięcia programu w pamięci na granicę MEMLO. Dotyczy to zwłaszcza kopierów, które dzięki temu potrafią wykorzystać całą dostępną pamięć. Ponieważ pokazany na przykładzie POKER-a schemat postępowania jest typowy także dla prostych programów kopiujących i transkodujących, ma także (choć tym razem to zbędne) strukturę przystosowaną do RELOCATORa przedstawionego w TA 8/91.
*--- znak konca

         brk

*--- dane adresowe

dtaa     dta a(data)
txta     dta a(text)
         dta a(0)
   I na koniec dane tekstowe, bufor dla wprowadzanych liczb i adres uruchomienia dla DOS-u lub COS-u. W SpartaDOS-ie adres uruchomienia jest zbędny. Co więcej, rozkaz RUN działa w nim poprawnie tylko wtedy, gdy tego adresu nie było.
*--- dane

data     equ *
         dta b(eol)
         dta c' TA POKER '*
         dta b(eol)
         dta c'Address:'
         dta b(eol)
         dta c'Byte:',b(eol)

text     org *+120

*--- adres uruchomienia

         org runad
         dta a(main)
         
         end
   Zwróć uwagę na sposób zarezerwowania pamięci na bufor tekstu. Z listów do redakcji wynika, że nieraz masz z tym kłopoty. Rozkaz "ORG *+120" przesuwa wskaźnik lokacji programu o 120 bajtów w przód, a etykieta TEXT ma wartość równą adresowi początku tego obszaru.

   Procedury DSP_MSG, GET_TEXT, DECO nadają się do umieszczenia w bibliotece, na pewno przydadzą się w niejednym programie. Warto je tylko uzupełnić o krótki opis realizowanych funkcji, sposób podawania parametrów i odbierania rezultatów.

   Errata: W TA 7/91 (rekordowym pod względem liczby błędów) w programie na stronie 5 mylnie wydrukowano "LDA IOCB+5,X" zamiast poprawnego "STA IOCB+5,X". Serdecznie przepraszam.

Janusz B. Wiśniewski



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

Pixel 2001