Programowanie 6502
Basic czyli o tym, jakim dobrym kumplem jest QA
Często asembler bywa wykorzystywany do tworzenia krótkich programów maszynowych, które dołącza się następnie do programu
w BASIC-u. Wielu programistów
korzysta z tej możliwości, bo różnica w szybkości działania między
programem w BASIC-u, a programem w języku maszynowym jest
oszałamiająca. Rozkazy maszynowe pozwalają także zrealizować
prosto takie zadania, które w BASIC-u są trudne do wykonania lub
wręcz niemożliwe. Wywołanie programu maszynowego umożliwia
funkcja BASIC-a
USR(adres)
która zwraca wartość liczbową
z zakresu 0..65535. adres wskazuje miejsce w pamięci, gdzie znajduje się program maszynowy. Należy
podkreślić, że jest to funkcja, a nie
rozkaz, nie może więc stać sama,
lecz tylko jako wyrażenie będące
argumentem jakiegoś rozkazu, np.
X=3*USR(1536)+2
Oczywiście musisz wpierw zadbać, by pod wskazanym adresem
(tutaj: 1536) znalazł się pożądany
program maszynowy. Do tego zadania można zatrudnić Quick Assembler. Z objęć interpretera BASIC-a przejdź więc do DOS-u lub
COS-u, pisząc
DOS
zachowawszy wcześniej ewentualny
program w BASIC-u, ponieważ wywołanie QA spowoduje jego skasowanie. Uruchom QA i napisz taki
program:
OPT %10101
ORG $600
PLA
RTS
END
Nie jest chyba dla nikogo tajemnicą, że $600 i 1536 oznaczają tę
samą liczbę. Najprostszy program
musi się składać co najmniej
z dwóch rozkazów, a nie z samego
RTS, bo BASIC przed przekazaniem sterowania naszemu programowi umieszcza na stosie oprócz
adresu powrotu jeszcze jeden bajt,
który należy usunąć przy pomocy
rozkazu PLA. Ten program można
zatytułować "Nie rób nic".
Wykonaj Assembly, aby umieścić
ten krótki program w pamięci.
Opuść QA i wróć do BASIC-a.
W normalnych systemach (takich,
jak DOS 2.5 z CP, DOS XL, SpartaDOS, COS) służy do tego rozkaz
CAR. Program na szóstej stronie
pozostaje nienaruszony, ponieważ
ani DOS, ani BASIC nie korzystają
z tego obszaru. Najprostszy sposób
wywołania naszego "programu", to
? USR(1536)
w wyniku czego BASIC wyświetli
liczbę zwracaną przez tę funkcję.
W najprostszym przypadku (jak
ten) będzie to "echo", czyli adres
wywoływanego programu. Jest on
na wstępie umieszczany w słowie
$D4 (rejestr FR0), co stanowi efekt
uboczny transkodowania podanego
adresu. Po zakończeniu wykonywania programu maszynowego BASIC
rozumie zawartość słowa FR0 jako
rezultat funkcji USR. Aby przekazać tą drogą jakąś istotną wiadomość, program powinien umieścić
swoją liczbę w słowie FR0, np.:
FRO EQU $D4
DLPTR EQU $230
ORG $600
DLP PLA
LDA DLPTR
STA FR0
LDA DLPTR+1
STA FR0+1
RTS
Ten program ma za zadanie przekazać adres "display list". Rozkazy
OPT i END dla uproszczenia pomijam, zakładając, że każdy wie gdzie
i jak je dopisać. Żeby oszczędzić
sobie czasu przy przechodzeniu
z QA do BASIC-a, a zwłaszcza z powrotem, proponuję zrobić tak:
1. Ustawić w QA Setup/Memhi
tak, aby rozmiar bufora wynosił
ok. 2000 bajtów (to w zupełności wystarczy dla naszych testów). Dla DOS-u 2.5 z CP może
to być np. $7000.
2. Umieścić tę samą liczbę
w krótkim "programie":
OPT %10101
ORG 8
DTA B($FE)
ORG $2E7
DTA A($7000)
END
i wykonać Assembly, co spowoduje ustawienie systemowego
wskaźnika MEMLO na podaną
wartość oraz ustawienie komórki WARMST, aby zapobiec
znikaniu programu w BASIC-u.
Jeśli edytor zawiera już jakiś
program, to ten można dopisać
przed nim, gdyż rozkaz END
"odetnie" całą resztę. Po
asemblacji można ten fragment
usunąć, przywracając stary
tekst do łask. Kto ma w pamięci XLFrienda, ten może odpowiednie "poki" wpisać wglądownicą.
3. Przygotowany tekst programu
dla USR-a zasemblować, w wyniku czego QA umieści go na
szóstej stronie.
4. Ustawić adres Setup/Run na
$A000. Jest to adres startu interpretera BASIC-a.
5. Wykonać Run, w wyniku czego QA uruchomi BAS1C jako testowany program!
Po skończonej zabawie z BASIC-em
będzie można wrócić do QA rozkazem DOS. Można też wielokrotnie
przerywać pracę programu
w BASIC-u kombinacją klawiszy
SHIFT/BREAK (by np. porównać
efekty działania programu z tekstem w edytorze) i powracać do
BASIC-a przez Run, nie tracąc ani
tekstu w QA, ani programu w BASIC-u (miłośnikom MAC-a proponuję wypróbowanie tej sztuczki
w swoim asemblerze).
Uwaga! Należy się powstrzymać od
użycia klawisza RESET. Można co
prawda ponownie wykonać krok 2.,
co pozwoli powrócić do BASIC-a, ale
później mogą być kłopoty z rozkazem
DOS.
Teraz wykonanie
? USR(1536)
powinno spowodować pojawienie
się liczby 39968. Pod tym adresem
znajduje się zwykle program ANTIC-u, czyli DL. Wypróbuj to tak:
20 D=1:DL=USR(1536)
25 FOR I=8-D TO D STEP (D-4)/3
30 POKE DL,16*I:NEXT I
35 D=8-D:GOTO 25
Jeśli wszystko jest dobrze, program wywoła miarowe ruchy ekranu. Można je zatrzymać klawiszem
BREAK.
Zamieszkać na zawsze w BASIC-u
Byłoby niepraktyczne postępować za każdym razem w opisany
wyżej sposób. Na ogół asembler
stosuje się tylko na etapie tworzenia i poprawiania programu maszynowego. Potem wygodnie jest umieścić go na stałe w tekście BASIC-owym. Istnieją rozmaite sposoby
umieszczenia danych programu
maszynowego w wierszach programu w BASIC-u. Oto Jeden z nich:
50 ? "100 DATA ";:A=1536
55 B=PEEK(A):? B;
60 IF B=96 THEN ? :END
65 7 ",";:A=A+1:GOTO 55
Wykonanie tego programu
(GOTO 50) zaowocuje pojawieniem
się wiersza DATA, zawierającego
dane kodu maszynowego. Wystarczy teraz wjechać na ten wiersz
kursorem i nacisnąć RETURN, by
stał się częścią programu. Należy
tylko jeszcze dopisać rozkazy, które przeniosą te kody z wiersza
DATA na szóstą stronę:
10 FOR I=0 TO 11:READ X
15 POKE 1536+I,X:NEXT I
(wiersze 50..65 nie są już potrzebne).
Ta metoda ma dwie wady. Po
pierwsze: ciąg cyfr i przecinków
w DATA nie jest jeszcze programem maszynowym, lecz musi zostać przekształcony w ciąg bajtów.
Po drugie: operacja przepisywania
trwa długo (a w przypadku dużej
liczby danych nawet bardzo długo).
Znaczki-dziwaczki
Można też umieścić dane maszynowe w BASIC-u w inny sposób:
50 ? "10 DIM X$(12):X$=";;A=1536
55 ? CHR$(34);
60 B=PEEK(A):? CHR$(27);CHR$(B);
65 A=A+1:IF B<>96 THEN 60
70 ? CHR$(34):END
Zaakceptowanie powstałego
w ten sposób wiersza przyłącza go
do programu w BASIC-u (wiersze
50..100 stają się niepotrzebne).
Wiersz 15 należy usunąć. Teraz
nasz program maszynowy zawarty
jest w zmiennej X$. W swej naturalnej postaci. Gotowy do wykonania. Bez potrzeby przenoszenia. Ale
gdzie to konkretnie jest? Mówi
o tym funkcja ADR(), która zwraca
adres zmiennej w BASIC-u. A więc
wystarczy poprawić:
20 D=1:DL=USR(ADR(X$))
by nasz program przykładowy
znów pląsał bez kłopotów.
Ten sposób traktowania danych
maszynowych ma dużo zalet i jeszcze więcej wad.
Zalety: dane zajmują mniej miejsca w tekście programu, nie wymagają przepisywania w docelowy obszar pamięci, dane kilku
osobnych procedur nie kolidują ze sobą.
Wady: dane są mniej czytelne,
program maszynowy praktycznie znika po NEW, program zawierający kody $9B (koniec
wiersza) i $22 (cudzysłów) nie
da się łatwo przedstawić w ten
sposób.
Najpoważniejsza jednak wada tej
metody ujawnia się, gdy zdamy sobie
sprawę, że dla większości programów nie jest obojętne, w którym
miejscu pamięci się znajdują. Inaczej
mówiąc, do zastosowania tej techniki
nadają się tylko takie programy, których kod nie zależy od wartości
ORG. Takie programy nazywane bywają "przemieszczalnymi". Użycie dowolnego rozkazu odwołującego się
do wnętrza programu (JSR, JMP, czy nawet LDA) niweczy tę właściwość, np. fragment
JMP TUTAJ
...
TUTAJ RTS
NIE jest przemieszczalny, bo argument rozkazu JMP w oczywisty
sposób zależy od położenia programu. Natomiast równoważny (także
pod względem długości) fragment
CLC
BCC TUTAJ
...
TUTAJ RTS
jest przemleszczalny, ponieważ
asembler tłumaczy rozkaz skoku
względnego zawsze jednakowo, niezależnie od położenia programu
w pamięci.
Wobec wymienionych niedogodności umieszczanie programów
w stałych tekstowych należy zawsze
poprzedzić głębokim namysłem.
Z drugiej strony, z uwagi na dostępną w słowie FR0 informację o położeniu programu, zawsze da się go
tak skonstruować, aby był przemieszczalny.
Przekazywanie parametrów
Dość często oprócz odebrania rezultatu funkcji chcielibyśmy przekazać do niej parametry, które
miałyby wpływ na jej działanie. Typowym przykładem są bardzo przydatne w programowaniu, nieobecne
w ATARI BASIC-u, bitowe operacje
logiczne. Wywołanie funkcji AND
dla dwóch argumentów zapiszemy
przykładowo tak:
? USR(1536,X,1)
To pozwoli stwierdzić, czy liczba
X jest nieparzysta. BASIC przekaże
parametry (traktowane jak liczby
16-bitowe bez znaku), wpychając je
kolejno na stos, od ostatniego do
pierwszego. Autorzy BASIC-a wpadli przy tym na wielce dowcipny pomysł, żeby zapamiętywać najpierw
młodszy bajt, potem starszy, co
uniemożliwia bezpośredni dostęp
do parametrów. Po umieszczeniu
(i zliczeniu) tych liczb BASIC kładzie na stosie jeszcze jeden bajt,
określający, ile jest parametrów.
Ten właśnie bajt, równy 0, musieliśmy zdejmować ze stosu w podprogramle wywoływanym przez
USR(1536), gdzie nie było wcale parametrów. Dopiero na spodzie,
pod tymi wszystkimi danymi znajduje się adres powrotu dla rozkazu
RTS. Napisz
DOS
i jesteś znów w QA.
OPT %10101
ORG $600
Na wstępie godzi się sprawdzić,
czy podano właściwą liczbę parametrów. W przeciwnym wypadku
cała praca na nic, bo program się
na ogół zawiesi (pomyśl, dlaczego).
BELL EQU $F556
PLA zdejm liczbę
TAX
CPX #2 czy 2 param?
BEQ AND
BNE Q (jmp)
FLUSH PLA
PLA
Q DEX
BPL FLUSH
JMP BELL
W przypadku podania złej liczby
parametrów nasza funkcja wrzaśnie i wróci do BASIC-a
Teraz dopiero można się zabrać
do wykonania właściwej operacji.
Będzie to iloczyn logiczny dwu liczb
16-bitowych. Najwłaściwszym miejscem przechowywania pośrednich
wyników wydaje się rejestr FR0.
FRO EQU $D4
AND PLA
STA FRO+1
PLA
STA FR0
PLA
AND FRO+1
STA FRO+1
PLA
AND FR0
STA FR0
RTS
END
Przejdź do BASIC-a (skacząc do
adresu $A000). Wykonaj test, który
upewni Cię, że program działa dobrze:
10 ? "Podaj liczbe":INPUT X
20 IF USR(1536,X,1) THEN ? "nie";
30 ? "parzysta":GOTO 10
Janusz B. Wiśniewski
|