Tajemnice ATARI

PROGRAMOWANIE PROCESORA 6502
W KOMPUTERACH ATARI XL/XE

Klocki raz jeszcze

   W ostatnim już odcinku na temat modularnego programowania pokażę dwa pożyteczne programy, które przy minimalnym wysiłku można uzyskać z klocków przedstawionych do tej pory. Kto nie ma poprzedniego numeru TA, może go nabyć korespondencyjnie (szczegóły na str. 10). Tych fragmentów programu, które zostały w nim wydrukowane, nie będę już dziś powtarzał.

   Oba programy należą do grupy tzw. transkoderów, czyli programów przekształcających dane z jakiejś jednej postaci w drugą. Styl pracy takiego kodera do złudzenia przypomina zwykły kopier. Różnica polega na zamianie danych w pewien sekretny sposób. Ponieważ pamięć nie jest z gumy, stosujemy w przypadku transkoderów zasadę oszczędności miejsca: jeżeli konwersja zmniejsza rozmiar danych, to należy ją przeprowadzać podczas odczytu (więcej się zmieści); w przeciwnym razie kodujemy dane podczas wysyłania ich do pliku wyjściowego, by nie rosły w pamięci. Oczywiście modyfikacja danych wymaga czasu, takie działanie podczas transmisji powoduje zwolnienie odczytu lub zapisu, jeśli więc dane nie zmieniają objętości, lub zmieniają ją nieznacznie, to można dokonać przekodowania całości w pamięci, pomiędzy operacjami odczytu i zapisu, jak robi to poniższy

1. Eol Eater

   Ten miły program służy do wycinania z plików wszystkich znaków o kodzie 155, stosowanych w systemie ATARI dla oznaczenia końca wiersza. Podstawowym zastosowaniem Eatera jest kompresja danych do gry The Jet, którą wydrukowano na str. 28. Plansze do gry tworzy się dowolnym edytorem, lecz powstające przy tym końce linii są dla gry niepotrzebnym (choć nieszkodliwym) balastem, po "przepuszczeniu" planszy (podkreślam: samej planszy, a nie gry) The Jet przez Eol Eater-a staje się ona krótsza o tyle bajtów, ile liczyła wierszy.
* TA EOL EATER
* autor: JBW & De Jet
* (c) 1992 Tajemnice ATARI
   Początek podobny do TA Copy, nie? W rzeczywistości te programy różnią się w niewielu szczegółach. Najlepiej więc wziąć tekst kopiera, wywalić z niego klocek read, a zamiast niego wstawić
*--- wczytaj plik
read     lda #4
         jsr open
         bmi rret
         lda size
         sta io_len,x
         lda size+1
         sta io_len+1,x
         jsr mcio
         bmi rret
   Na razie tak samo, lecz przytaczam to dla porządku
* ustaw adresy
         clc
         lda bufa
         sta addr
         sta word
         adc io_len,x
         sta used
         lda bufa+1
         sta addr+1
         sta word+1
         adc io_len+1,x
         sta used+1
   Tym razem słowo USED przechowuje chwilowo adres końca wczytanego pliku, aby było wiadomo, gdzie skończyć. Do przebiegania po danych wejściowych służy WORD, zaś ADDR wskazuje miejsce w buforze, gdzie wpisywane są bajty rezultatu. Ponieważ ten drugi wskaźnik nigdy nie prześcignie pierwszego, można bez obawy wykorzystać wspólny bufor.
eat_loop equ *
         ldy #0
         lda (word),y
         cmp #eol
         beq eat_eol
         sta (addr),y
         inc addr
         bne *+4
         inc addr+1 
eat_eol  equ *
    Metoda konwersji jest prosta: jeżeli wykryjemy EOL, to wystarczy po prostu nie przepisywać tego bajtu (nie zmienia się adres docelowy ADDR). Zawsze natomiast zwiększa się adres źródłowy WORD. Porównanie go ze strażnikiem USED pozwala na zakończenie procesu w stosownym miejscu.
         inc word
         bne *+4
         inc word+1
         lda word
         cmp used
         lda word+1
         sbc used+1
         bcc eat_loop
    Pozostaje obliczyć nową długość pliku:
         sec
         lda addr
         sbc bufa
         sta used
         lda addr+1
         sbc bufa+1
         sta used+1
rret     rts
   I kosmetyczna poprawka w danych, by wyświetlała się prawidłowa winieta programu
*--- dane

data dta b(eol)
     dta c' TA Eol Eater '*
     dta b(eol)
    zamiast tytułu "TA Copy" (reszta danych bez zmian).

    Autorem procedury wcinającej Eole jest Dariusz Żołna.

    Dygresja: posiadacze Panthera mogą rzecz całą wykonać dużo prościej. Wystarczy zdefiniować tabelę konwersji z jedynym wpisem:
#155=
   co się czyta "zamień wszystkie EOL na nic", aktywizować ją, a następnie "wydrukować" ułożoną planszę na plik dyskowy lub kasetowy opcją File/Print.

2. Hex DATA Coder

   Narzędzie to służy do zamiany programów maszynowych na wiersze DATA tak, by mogły być łatwo prezentowane na łamach czasopism. Każdy bajt pliku wejściowego zostanie zamieniony na odpowiadającą mu parę cyfr szesnastkowych. Te pary zgrupowane są po 13 w wierszu, a każdy wiersz zaczyna się od numeru i słowa DATA. Pierwszy wiersz zawiera pusty rozkaz REM (jest to minimum niezbędne dla Zgrywusa+) i ma numer 1000. Pozostałe wiersze numerowane są z krokiem 10.
* TA Hex DATA   autor: JBW
* (c) 1992 Tajemnice ATARI
Ponieważ taka konwersja danych powoduje ponad dwukrotne zwiększenie ich objętości, nie wykonuję jej w pamięci, lecz dopiero "na żywo" podczas zapisu pliku docelowego. Znów punktem wyjścia będzie TA Copy, lecz tym razem pozostawimy procedurę read bez zmian, wymienimy natomiast klocek write.
*--- zapisz plik
write    lda #8
         jsr open
         bmi wret
    Pierwszą czynnością po pozytywnym otwarciu pliku będzie zapisanie wiersza z rozkazem REM:
* zapisz REM
         lda d0_a
         sta io_adr,x
         lda d0_a+1
         sta io_adr+1,x
         lda <d0_1
         sta io_len,x
         lda >d0_1
         sta io_len+1,x
         jsr ciov
         bmi wret
   Podczas zapisywania pliku fragmentami, wystarczy modyfikować w bloku IOCB tylko adres i długość danych, pozostałe bowiem parametry system pozostawia nienaruszone. Również wartość rejestru X nie ulega zmianie wewnątrz procedury CIOV.

    Dane szesnastkowe gromadzi się w buforze o długości jednego wiersza. Trzeba na wstępie ustalić jego numer początkowy. Najprościej przenieść go z wiersza REM. Nie można zadeklarować numeru startowego po prostu jako danych w buforze, ponieważ przy powtórnym zapisie pliku numer musi wrócić do swej pierwotnej wartości.
* ustaw numer początkowy
         ldy #3
setn     lda d0,y
         sta d1,y
         dey
         bpl setn
   Przygotowanie do konwersji polega na ustawieniu komórki BYTE, która będzie używana do wskazywania aktualnego miejsca w wierszu DATA, gdzie wpisywane są dane. Ponieważ wykorzystana jest sprytnie procedura PHEX (wpisuje ona cyfry w napisie informacyjnym STAT), indeks jest obliczany względem początku tego napisu. Słowo WORD będzie służyć jako strażnik końca dla adresu ADDR, którym przebiegać będziemy po buforze.
* prolog konwersji
         lda <dat_
         sta byte
         clc
         lda bufa
         sta addr
         adc used
         sta word
         lda bufa+1
         sta addr+1
         adc used+1
         sta word+1
         lda d1_a
         sta io_adr,x
         lda d1_a+1
         sta io_adr+1,x
    Główna pętla konwersji pobiera kolejne bajty i wysyła je na wyjście za pomocą procedury BOUT. Operację przerywa się (ważne!) gdy BOUT zamelduje błąd.
* pętla konwersji
wri_     lda addr
         cmp word
         lda addr+1
         sbc word+1
         bcs wend  koniec?
         ldy #0
         lda (addr),y
         inc addr
         bne *+4
         inc addr+1
         jsr bout  wypisz
         bpl wri_  gdy ok
wret     rts
   Procedura BOUT nie wysyła przez CIOV każdego bajtu z osobna, lecz dopiero cały, kompletny wiersz. Dlatego zakończenie konwersji wymaga na ogół wysłania ostatniego, niepełnego wiersza:
wend     lda byte
         cmp <dat_+1
         jmp xclo
   Rozkaz CMP, tuż przed skokiem, wymusza odpowiednie zachowanie programu w miejscu XCLO. Rozkaz JMP nie zmienia przecież znaczników. Zapobiega to wysłaniu pustego wiersza, jeżeli zgromadzono w nim dopiero 0 bajtów. Sztuczka z zastosowaniem "<"zamiast "#" pozwala odwołać się do wartości, która nie jest jeszcze w tym miejscu znana asemblerowi.

   BOUT stanowi typowy przykład techniki, znanej jako przesyłanie z buforowaniem. Polega ono na gromadzeniu danych w wydzielonym miejscu pamięci, zwanym buforem, a faktycznym przesyłaniu ich do urządzenia wyjściowego porcjami o wielkości zależnej od pojemności bufora. Takie rozwiązanie w istotny sposób przyspiesza i usprawnia operacje we/wy. Oczywiście typowe urządzenia zewnętrzne wyposażone są w swe własne bufory, mamy tu więc do czynienia z buforowaniem wielopoziomowym. Dopóki zatem wpisywanie cyfr nie dojdzie do końca bufora (strażnikiem jest etykieta REM_), nie ma potrzeby wywoływać CIOV.
* zapisz bajt
bout     inc byte
         ldy byte
         inc byte
         jsr phex
         lda byte
         cmp <rem-1
xclo     bcc wrok
    Wypełnił się bufor! Trzeba zwiększyć numer wiersza. Drobne oszustwo polega na zwiększeniu o 1 trzycyfrowego licznika. Czwarta cyfra - zero jest tylko atrapą i nigdy nie ulega zmianie.
* kolejny numer wiersza
         ldy #2
advn     clc
         lda #1
         adc d1,y
         sta d1,y
         cmp #'9'+1
         bcc pdln
         lda #'0'
         sta d1,y
         dey
         bpl advn
         jmp quit  za wiele!
    999 wierszy danych umożliwia zakodowanie programu o długości ponad 12 KB. Kto uważa, że to mało, może zmodyfikować program, wprowadzając dodatkową cyfrę lub zagospodarowując wspomnianą wyżej atrapę. We fragmencie wywołującym CIOV musimy pamiętać, że nie zawsze wiersz jest pełny, trzeba więc obliczyć jego aktualną długość.
* zapisz wiersz
pdln     sec
         lda byte
         snc <d1-stat
         sta io_len,x
         jsr ciov
         bmi wret
         lda #0
         sta io_len,x
         lda #eol
         jsr ciov
         bmi wret
         lda <dat_
         sta byte
wrok     ldy #1
         rts
    Podanie w io_len zerowej długości danych spowoduje przesłanie pojedynczego znaku, który znajduje się w akumulatorze. Dane adresowe należy uzupełnić o
d0_a     dta a(d0)
d1_a     dta a(d1)
    Umieszczamy je oczywiście przed
dta a(0)
    Blok danych tekstowych wymaga kilku zmian. Na wszelki wypadek przytoczę go w całości, by było jasne co powielić, a co poprawić.
*--- dane

data  equ *
      dta b(eol)
      dta c' TA Hex DATA '* 
      dta b(eol)
      dta c'Source:',b(eol)
      dta c'Target:',b(eol)
      dta c'I/O error!'
      dta b(eol)
      dta c'Out of memory!'
      dta b(eol)
stat  dta c'Used $' 
use_  equ *-stat
      dta c'.... bytes of $'
siz_  equ *-stat
      dta c'....',b(eol)

d1    dta c'.... DATA '
dat_  equ *-stat
      org *+26
d1_1  equ *-d1

rem_  equ *-stat
d0    dta c'1000 REM',b(eol)
d0_1  equ *-d0

dnam  dta c'D0:'
text  org *+120

buff  equ *
    I to wszystko. Każdy z trzech omówionych programów (Copy, Eol Eater, Hex DATA) można bez trudu połączyć z Relocator-em, który zapewnia optymalne wykorzystanie pamięci komputera. Relokator był już prezentowany w TA. Dziś wersja nieznacznie zmodyfikowana. Umożliwia ona powtórne uruchomienie programu po powrocie do DOS-u (np. 2.5 z CP) lub COS-u, rozkazem RUN. Wykorzystane zostało słowo INITAD ($2E2), dzięki czemu działalność Re-lokatora ma miejsce PRZED faktycznym uruchomieniem programu. Relokator oblicza adres, pod którym znajdzie się program i wpisuje go do RUNAD ($2EO). To sprawia, że DOS na ogół samoczynnie uruchomi nasz program od tego adresu. Również ponowne "wskoczenie" do programu przez RUN (w wielu systemach jest taka opcja) odbędzie się z wykorzystaniem tego adresu.
MAIN__ EQU main
USER__ EQU rret


*------------------*
*  Relocator 1.1   *
*                  *
*      by JBW      *
*                  *
*    1992-02-10    *
*                  *
*------------------*


*--- page 0 --------

BYTE__ EQU $CE
DATF__ EQU $CF
DIST__ EQU $D0 (2)
SRCE__ EQU $D2 (2)
DEST__ EQU $D4 (2)
ADDR__ EQU $D6 (2)

*--- system --------

RUNA__ EQU $2E0  (2)
INIA__ EQU $2E2  (2)
MELO__ EQU $2E7  (2)

*--- move ----------

MOVE__ EQU *
       JSR USER__
* clear data flag
       LDA #0
       STA DATF__
* destination
       LDA MELO__
       STA DEST__
       STA RUNA__
       LDA MELO__+1
       STA DEST__+1
       STA RUNA__+1
* code source, distance
       SEC
       LDA <MAIN__
       STA SRCE__
       SBC DEST__
       STA DIST__
       LDA >MAIN__
       STA SRCE__+1
       SBC DEST__+1
       STA DIST__+1
*** move process ***
       LDY #0
       BEQ MOVL__ (JMP)
SEDA__ SEC
       ROR DATF__
MOVL__ EQU *
       LDA SRCE__
       CMP <MOVE__
       LDA SRCE__+1
       SBC >MOVE__
       BCC DCHK__
* done !
       RTS
* data flag check
DCHK__ BIT DATF__
       BVS MOV1__
       BMI TPE3__
INST__ EQU *
       LDA (SRCE__),Y
       STA BYTE__
       STA (DEST__),Y
       JSR INCA__
       TAX
       BEQ SEDA__
* instr type check
       CMP #$20  JSR
       BEQ TPE3__
       CMP #$40  RTI
       BEQ MOVL__
       CMP #$60  RTS
       BEQ MOVL__
       AND #$0D
       CMP #$08  x8,XA
       BEQ MOVL__
       BCC MOV1__
       TXA
       AND #$1F
       CMP #$09
       BEQ MOV1__
* 3-byte instruction
TPE3__ EQU *
       LDA (SRCE__),Y
       INY
       CMP <MAIN__
       LDA (SRCE__),Y
       DEY
       SBC >MAIN__
       BCC MOV2__
       LDA (SRCE__),Y
       INY
       CMP <MOVE__+1
       LDA (SRCE__),Y
       DEY
       SBC >MOVE__+1
       BCS MOV2__
* alter abs adresses
       LDA DIST__
       LDX DIST__+1
       BCC MOVA__
* move w/o changes
MOV2__ BIT DATF__
       BMI SEDA__
       LDA #0
       TAX
* move 2b address
MOVA__ EQU *
       STA ADDR__
       STX ADDR__+1
       SEC
       LDA (SRCE__),Y
       SBC ADDR__
       STA (DEST__),Y
       JSR INCA__
       LDA (SRCE__),Y
       SBC ADDR__+1
       JMP SD__
* move 1b data
MOV1__ EQU *
       LDA (SRCE__),Y
SD_    STA (DEST__),Y
       JSR INCA__
       JMP MOVL__


*--- inc SRCE,DEST -

INCA__ INC SRCE__
       BNE *+4
       INC SRCE__+1
       INC DEST__
       BNE *+4
       INC DEST__+1
       RTS


*--- start ---------

       ORG INIA__
       DTA A(MOVE__)


       END
   Kto rezygnuje z użycia Relokatora, może obniżyć adres podany w początkowym ORG, by uzyskać nieco więcej wolnej pamięci dla programu.

Janusz B. Wiśniewski



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

Pixel 2002