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
|