Tajemnice ATARI

PROGRAMOWANIE PROCESORA 6502
W KOMPUTERACH ATARI XL/XE

TA Copy

    Dziś kolejny przykład programu z klocków: uniwersalny duplikator plików. Znaczna jego część pochodzi z programu TA Poker, którym zajmowaliśmy się w numerze 3/92. Te fragmenty, które wówczas wpisaliście, oznaczone są szarą "mgiełką".
* TA COPY       autor: JBW
* (c) 1992 Tajemnice ATARI

          opt %100101


*-- procedury w ROM

afp       equ $D800
fpi       equ $D9D2
ciov      equ $E456

*--- 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

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
   Widziałem w życiu dużo kopierów, lecz niewiele z nich można nazwać przyjaznymi. Często dla osiągnięcia jak największej pojemności bufora programy te niszczą wszystko, co oprócz nich "żyło" w komputerze.

   Nasz program będzie krótkim, podręcznym kopierem, wykorzystującym pamięć pomiędzy MEMLO i MEMHI, nie kolidującym więc z innymi programami (systemem operacyjnym, nakładkami, itd.).

memhi    equ $2E5
memlo    equ $2E7
driv     equ $301
skctl    equ $D20F

*-- stale

chn0     equ $00      
chn1     equ $10
gett     equ 5
putt     equ 9 
getb     equ 7 
putb     equ 11
eol      equ 155
eof      equ 136
shift    equ %00001000
    Zestaw przydatnych definicji poszerzył się o kilka nowych. Na stronie zerowej obok roboczych komórek BYTE, ADDR, WORD wyrosły nowe, ważne dla kopiera zmienne globalne:
*--- strona zerowa

byte     equ $cb
addr     equ $cc
word     equ $ce

used     equ $d0
size     equ $d2
   Opisują one aktualny stan kopiera, powinny być wyraźnie wyodrębnione i sugestywnie nazwane, by nie przyszła nam ochota użyć ich do jakichś innych celów. USED będzie zawierać rozmiar aktualnie wczytanego pliku, zaś SIZE - wielkość dostępnej pamięci.
*--- numery komunikatow

nul_m    equ 0
tit_m    equ l
get_m    equ 2 
put_m    equ 3
err_m    equ 4
mem_m    equ 5
sta_m    equ 6
    Dużym zmianom ulegną komunikaty (kopier jest bardziej rozmowny od Poker-a). Oprócz nagłówka i odstępu potrzebne są: wyświetlenie stanu, zachęta do wczytania pliku, zachęta do zapisania, komunikat o błędzie we/wy i o przepełnieniu pamięci.

Plan

   Planowanie zasad działania programu można utożsamić ze stworzeniem jego głównej pętli, która rozdziela zadania pomiędzy poszczególne procedury.
     org $8000

main     jsr init
* glowna petla
loop     jsr close
* wypisz status
         jsr dsp_stat
* pobierz nazwe pliku
         ldx #get_m odczyt
         lda used
         ora used+1
         beq *+3
         inx        zapis
         jsr get_text
         bmi loop
* nazwa pusta?
         dec io_len,x
         bne io
* zmiana trybu lub koniec
         lda used
         ora used+1
         beq quit
         lda #0
         sta used
         sta used+1
         beq loop  (jmp)
* zapis czy odczyt?
io       lda used
         ora used+1
         beq rd
* zapis
         jsr write
         jmp loop
* odczyt
rd       jsr read
         jmp loop
* powrot do DOS-u
quit     jmp (dosrun)

   Pętla zaczyna się od wywołania procedury CLOSE, zamykającej kanał nr 1. Jest to wygodny sposób zabezpieczenia się przed poprzednim, roztargnionym użytkownikiem tego kanału.

   DSP_STAT informuje klienta o aktualnym stanie kopiera. Tuż za nim następuje pytanie o nazwę pliku. Tekst pytania zależy od wartości słowa USED. Zero oznacza pusty bufor - zatem prosimy o plik do odczytu. Jeżeli natomiast ORA da niezerowy wynik, to znaczy, że bufor coś zawiera, pytamy więc o plik do zapisu.

   Aby uprościć maksymalnie sposób obsługi kopiera, przyjąłem wprowadzenie pustego wiersza jako jedyny rozkaz sterujący. W trybie zapisu oznacza on polecenie przejścia do trybu odczytu (i zarazem wyczyszczenie bufora), natomiast w trybie odczytu powoduje zakończenie pracy i przejście do programu nadrzędnego (DOS-u, COS-u...). Przełączenie z trybu odczytu na zapis odbywa się samoczynnie, po poprawnym odczytaniu pliku.

   Ten "pomysł" został wymuszony przez specyfikę standardowego wejścia ATARI (urządzenie "E:"), dostępnego przez kanał 0. Oddaje ono wprowadzony tekst dopiero po zatwierdzeniu go klawiszem RETURN. Najkrótszy zatem tekst to właśnie sam RETURN! Wiele kopierów stosuje tu rozkazy literowe, np. Q. Wtedy oczywiście trudno jest wczytać i zapisać plik o nazwie Q.

   Procedury READ i WRITE odpowiadają za odczyt i zapis pliku. Główna pętla wywoła jedną z nich w zależności od stanu słowa USED.

   Klocki wyświetlania i wprowadzania tekstów nie ulegają żadnym zmianom:
*--- 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
*--- 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
    Przybędzie natomiast procedura do wypisywania liczb w formacie szesnastkowym. Jej ważną cechą jest bardzo duża szybkość działania. Oczywiście w tym zastosowaniu prędkość nie gra większej roli, więc kto woli, ten może użyć procedur z ROM-u i wyświetlać liczby dziesiętnie.
*--- wypisywanie liczb
pwor     jsr phex
         txa phex
         pha
         jsr pxdig
         pla
         lsr @
         lsr @
         lsr @
         lsr @
pxdig    and #%00001111
         ora #'0'
         cmp #'9'+1
         bcc *+4
         adc #6
         sta stat,y
         dey
         rts
    Jest to w zasadzie zespół trzech procedur: PXDIG wyświetla pojedynczą cyfrę szesnastkową, PHEX wyświetla bajt (dwie cyfry), a PWOR - słowo (cztery cytry). Zastosowana tu została prosta "sztuczka". PHEX wywołuje dwukrotnie procedurę PXDIG. Po pierwszym wywołaniu przez JSR następuje przesunięcie zawartości bajtu o cztery bity, by starsza połówka znalazła się na miejscu młodszej. Tu powinna nastąpić sekwencja JSR PXDIG, RTS, lecz taki sam skutek da przecież prostsze JMP PXDIG (RTS w PXDIG znajdzie na stosie adres powrotu w miejsce wywołania PHEX). Z kolei jednak rozkaz JMP do następnej instrukcji można bez szkody pominąć. W ten sam sposób procedura PWOR dwakroć wywołuje procedurę PHEX. Liczby nie są wyświetlane na ekranie, lecz przygotowywane w obszarze komunikatów. Wyświetla je następny klocek:
*--- wypisz status

dsp_stat equ *
* wykorzystywane
         lda used 
         ldx used+1
         ldy <use_+3
         jsr pwor
* rozmiar bufora
         sec
         lda memhi
         sbc bufa
         sta size
         lda memhi+1
         sbc bufa+1
         sta size+1
         tax
         lda size
         ldy <siz_+3
         jsr pwor
* wypisz 
         ldx #nul_m pusty
         jsr dsp_msg
         ldx #sta_m status
         jmp dsp_msg
   Wykorzystanie procedury PWOR polega na ustawieniu rejestru indeksowego Y na pozycję ostatniej cyfry, w rejestrach A i X przekazuje się słowo do wypisania.

   W licznych programach, zwykle bardziej skomplikowanych od naszego kopiera, operacje wejścia/wyjścia wywoływane są z wielu pod-programów. Ponieważ po każdym takim wywołaniu trzeba zbadać kod zakończenia operacji (rejestr Y), wygodnie jest stworzyć procedurę inicjującą operację we/wy połączoną z reakcją na błąd.
*--- CIO z ew. komunikatem
mcio     jsr ciov
         bpl ciok
         cpy #136
         beq iook
error    ldx #err_m
derr     jsr dsp_msg
         ldy #255
         rts
ciok     ldx #mem_m
         lda used
         ora used+1
         beq derr
         ldy #1
         rts
   Nie jest to całkiem trywialne, ponieważ tylko w przypadku zapisywania pliku pożądane jest pozytywne zakończenie operacji! W przypadku odczytu bowiem kopier, nie znając z góry wielkości pliku, żąda wypełnienia CAŁEGO bufora. Jeżeli więc ta operacja się powiedzie, można z dużym prawdopodobieństwem przyjąć, że plik jest większy. Dlatego procedura MCIO melduje w takim przypadku niedobór pamięci. Poprawne zakończenie odczytu - to błąd nr 136 (napotkany koniec pliku). Świadczy on o tym, że wszystko zostało przeczytane. Dla uproszczenia kontroli poprawności w procedurach, które wywołują MCIO, modyfikowany jest kod w rejestrze Y: teraz już naprawdę ujemna wartość oznacza niepowodzenie operacji.
*--- zamknij kanal
close    ldx #chn1
         lda #12
         sta io_com,X
         jsr ciov
         lda #3
         sta skct1 cicho!
         tya
         bmi error
         rts
   Procedura zamykająca kanał wycisza przy okazji niemiły pisk, słyszalny wskutek błędu w systemie ATARI.
*--- otworz kanal
open     ldx #chn1
         sta io_mod,x
         lda #3
         sta io_com,x
* szukaj dwukropka
         ldy ':'
         cpy text+1
         beq seti
         cpy text+2
         beq seti
         lda #0
* ustaw iocb 
seti     clc
         adc dnma
         sta io_adr,x
         lda #0
         adc dnma+1
         sta io_adr+1,x
         lda skctl
         and #shift
         asl @
         asl @
         asl @
         asl @
         sta io_aux,x
         jsr ciov
         bmi error
* przygotuj na potem...
         lda io_mod,x
         ora #3
         sta io_com,x
         lda bufa
         sta io_adr,x
         lda bufa+1
         sta io_adr+1,x
         tya
         rts
   Otwarcie kanału to bodaj najbardziej skomplikowany klocek. Ponieważ procedura jest ta sama dla zapisu i odczytu, w akumulatorze przekazuje się żądany tryb pracy kanału: 4 lub 8. Procedura sprawdza obecność znaku ":" w nazwie pliku. Jeżeli go brak, to dołączana jest z przodu domyślna nazwa urządzenia. Jeżeli użytkownik trzyma wciśnięty klawisz SHIFT, to w IO_AUX umieszczane jest 0 (oznacza to długie przerwy dla urządzenia "C:"), w przeciwnym razie - 128. Jeżeli otwarcie powiedzie się, to można przygotować jeszcze kod rozkazu dla przyszłych odczytów lub zapisów (7 lub 11) oraz adres bufora.

   Uwaga: ponieważ OPEN i CLOSE korzystają z części ERROR procedury MCIO, należy te klocki traktować jako niepodzielną grupę. Z uwagi na skoki względne pożądane jest bliskie ich sąsiedztwo.
*--- 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
         lda io_len,x
         sta used
         lda io_len+1,x
         sta used+1
rret     rts
   Wczytanie pliku rozpoczyna się ustawieniem żądanego rozmiaru danych (SIZE - cały bufor), a kończy się zarejestrowaniem faktycznej długości pliku, zwracanej przez system w IO_LEN. Zapis to już czcza formalność - nie wymaga chyba komentarza.
*--- zapisz plik
write    lda #8
         jsr open
         bmi wret
         lda used
         sta io_len,x
         lda used+1
         sta io_len+1,x
         jsr mcio
wret     rts
    Pozostaje jeszcze tylko do napisania procedura INIT, która w sprytny sposób wydedukuje numer stacji dysków, z której wczytujemy nasz kopier (jeśli to nie stacja, nic nie szkodzi), wyzeruje słowo USED i wyświetli nagłówek programu.
*--- ustawienie poczatkowe

init   lda #'0'
       ora driv 
       sta dnam+t
       lda #0
       sta used
       sta used+1
       ldx #tit_m tytul
       jmp dsp_msg

*--- koniec programu

       brk
    Niezbędne dane:
*--- dane adresowe
txta   dta a(text)
dtaa   dta a(data)
bufa   dta a(buff)
dnma   dta a(dnam)
       dta a(0)

*--- dane

data   dta b(eol)
       dta c' TA COPY 1.0 '* 
       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)

dnam     dta c'D0:'
text     org *+120 
buff     equ *
    Leniwi mogą tu zakończyć pracę:
        org runad
        dta a(main)
        end
   Warto tylko zmienić adres w rozkazie ORG, by poszerzyć bufor. Ambitnym proponuję połączyć kopier z RELOCATOR-em z TA 8/91. Trzeba w nim zadeklarować STAR__ jako main, zaś USER__ jako rret.

Janusz B. Wiśniewski



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

Pixel 2001