Tajemnice ATARI

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

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

    Pixel 2001