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
|