Już po raz czwarty, mam przyjemność zaprosić czytelników do lektury kursu
programowania procesora 6502, poprowadzonego już dosyć dawno temu, przez jednego z lepszych programistów Atari Janusza
Bohdana Wiśniewskiego. Wszystkich czytelników, którzy z kursem stykają się po raz pierwszy, chcę poinformować, że kurs
ten był już raz publikowany na łamach czasopisma "Tajemnice ATARI". Korzystając z tego, że jestem w telewizji, chciałbym
dzisiejszy odcinek kursu, zwłaszcza w części dotyczącej programu Eol Eater zadedykować koledze Pigule z grupy Shapoon.
Na koniec zostawiłem sobie przykrą wiadomość. Dzisiejszy odcinek jest już odcinkiem przedostatnim....
Chciałbym jednak zwrócić uwagę wszystkich zawiedzionych na fakt, że redakcja
"Tajemnic ATARI" cały ten materiał publikowała w piętnastu wydaniach swojego magazynu. My doceniając Waszą
niecierpliwość, a także mniejszą częstotliwość ukazywania się naszego maga robimy to rozkładając cały materiał na pięć
odcinków.
Nie zwlekając już dłużej, zapraszam wszystkich do lektury tego naprawdę ciekawego
materiału.
Zbycho Jabol/DIAL
TA Copy
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 poprzednim odcinku.
* 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
runadr 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, itp).
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
byta equ $CD
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. ESED będzie zawierać rozmiar aktualnie wczytanego pliku,zaś SIZE wielkość dostępnej
pamięci.
*---numery komunikatów
nul_m equ 0
tit_m equ 1
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 Pokera).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 pętli
głównej, która rozdziela zadania pomiędzy poszczególne procedury.
org $8000
main jsr init
* główna pętla
loop jsr close
* wypisz status
jsr dsp_stat
* pobierz nazwę 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
* powrót do DOS-u
quit jmp (dosrun)
Pętla zaczyna się od wywołania procedury close, zamykającej kanał nr1. Jest to wygodny sposób zabezpieczenia się przed
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. Przełączenie z trybu odczytu na zapis odbywa się
samoczynnie po poprawnym odczytaniu pliku. Ten "pomysł" został wymuszony przez specyfikę standartowego 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 predkość nie gra większej roli więc kto woli ten może użyć procedur z ROM 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 cyfry ).
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ż prostrze JMP PXDIG (RTS w PXDIG znajdzie na stosie
adres powrotu w miejsce wywołania PHEX). Z koleji jednak rozkaz JMP do następnej instrukcji można bez szkody pominąć. W
ten sposób procedura PWOR dwakroć wywoła 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 podprogramó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
iook 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 kanał
close ldx #chn1
lda #12
sta io_com,x
jsr ciov
lda #3
sta skctl cicho!
tya
bmi error
rts
Procedura zamykająca kanał wycisza przy okazji niemiły pisk słyszalny wskutek błędu w systemie ATARI.
*--- otwórz kanał
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śniety klawisz SHIFT to
w IO_AUX umieszczone jest 0 (długie przerwy dla urządzenia C:) w przeciwnym razie 128. Jeśli 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ąsiectwo.
*--- 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ść i 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 to nic nie szkodzi) wyzeruje słowo USED i wyświetli nagłówek programu.
*--- ustawienie początkowe
init lda #'0'
ora driv
sta dnam+1
lda #0
sta used
sta used+1
ldx tit_m tytul
jmp dsp_msg
*--- koniec programu
brk
Niezbedne 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 runadr
dta a(main)
end
Warto tylko zmienić adres w rozkazie org by poszerzyć bufor. Ambitnym proponuję połączyć kopier z RELOCATOR'em. Trzeba w
nim zadeklarowac STAR_ jako main zas USER_ jako rret.
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.
Oba programy należą do grupy tzw. transkoderów, czyli programów przekształcających
dane z jakieją 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ż w tym przypadku pamięć nie jest z gumy stosujemy w przypadku
transkoderów zasadę oszczędności miejsca: jeżeli komputer 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 żeby 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 to robi poniższy program.
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 zadaniem Eatera jest kompresja danych do gry The
Jet. 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 The Jet przez Eol Eatera 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 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 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 słowo 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 przepisywac 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 we właściwym 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 wineta programu:
*---dane
data dta b(eol)
dta c' TA Eol Eater '*
dta b(eol)
zamiast tytułu "TA Copy" (reszta bez zmian).
Autorem procedury wycinającej Eole jest Dariusz Żołna.
Dygresja: posiadacze "Panthera" mogą rzecz całą wykonać dużo prościej. Wystarczy zdefiniować tabelę konwersji z
jednym wpisem: #155= co się czyta "zmień wszystkie EOL na nic" aktywizować ją, a następnie "wydrukować"
ułożoną planszę na plik dyskowy lub kasetowy opcją File/Print.
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 szesnaskowych. 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 objetoś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_l
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 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 swojej pierwotnej wartości.
* ustaw numer poczatkowy
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 (wypisuje 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. Operacje przerywa się
(ważne!) gdy BOUT zamelduje błąd.
* petla 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śli 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 przesł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 zewnetrzne wyposażone są w swe własne bufory, mamy tu więc do czynienia z buforowaniem
wielopoziomowym. Dopóki zatem wypisywanie 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 0 jest tylko atrapą i nigdy nie ulegnie 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 12kB. 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
sbc <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 pojedyńczego 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_l equ *-d1
rem_ equ *-stat
d0 dta c'1000 REM',b(eol)
d0_l 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 relokatorem,
który zapewnia optymalne wykorzystanie pamięci komputera. Poniżej jest ulepszona wersja relokatora umożliwiająca
powtórne uruchomienie programu po powrocie do DOS'u rozkazem RUN. Wykorzystane zostało słowo INITAD ($2e2) dzięki czemu
działalność relokatora ma miejsce przed faktycznym uruchomieniem programu. Relokator oblicza adres pod którym znajdzie
się program i wpisuje go do RUNAD ($2e0).To sprawia, że DOS na ogół samoczynnie uruchomi nasz program od tego adresu.
Również ponowne wskoczenie do programu przez RUN 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__
* 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 Relocatora może obniżyć adres podany w początkowym org by uzyskać nieco więcej pamięci dla
programu.

W tym odcinku kursu, jego autor JBW pozwolił sobie na pewnien "luz". Ja aby
zachować wierność autorowi opublikuję i ten odcinek, jednak i ja wzorem JBW pozwolę sobie na uwypuklenie pewnych faktów.
Otóż materiały publikowane w magazynach dyskowych są raczej czytane dosyć pobierznie i na niektórych rzeczach dosyć
trudno nam się skupić. Szczególnie dotyczy to materiałów wydawałoby się powszechnie znanych. Po opublikowaniu instrukcji
użytkowania edytora Panther spotkałem się z zarzutami, że było to "przegięcie". O niesłuszności tego twierdzenia
przekonał mnie ostatnio jeden z moich przyjaciół. Otóż osobnik ten dosyć szeroko znany na naszej scenie oświadczył mi,
że w pewnej sytuacji musiał zrezygnować z wykorzystywania Panthera na korzyść Speed Script'a, gdyż w żaden inny sposób
nie mógł się pozbyć znaku końca wiersza (tzw. EOL), który to znak jest przez Panther'a automatycznie wprowadzany do
tekstu. Taka sytuacja jest dowodem na to, iż wspomniany osobnik nie przestudiował instrukcji Panther'a, ani orginalnej
(być może jej nie miał....), ani tej publikowanej w SERIOUS'ie #1. Nasuwa się pytanie - dlaczego nie przestudiował? Bo
on przecież już to wszystko wiedział! Dlaczego o tym wspominam? Ano dlatego, żeby się podbudować, żeby utwierdzić się w
przekonaniu, że to ja jednak miałem rację. Wiele osób stawia mi ten sam zarzut odnośnie publikacji cyklu o
programowaniu, lecz mnie samemu bardzo przydało się przypomnienie zawartych w nim wiadomości, a wypowiedź Krógera
oceniająca cykl jako "kultowy" i twierdzenie, że on się na nim wychował, utwierdziło mnie w przekonaniu o słuszności
decyzji publikacji cyklu z nadzieją, że jeszcze ktoś sie na nim "wychowa"...
W tym miejscu chcę zwrócić się do mojego kolegi, którego przykładem posłużyłem się
tam wyżej. Mam wielką nadzieję, że tylko MY dwaj wiemy kto jest kto! Nie chciałem tutaj wyciągać twojej POSTACI na
światło dzienne, chciałem tylko użyć "życiowego", prawdziwego przykładu.
Teraz już zapraszam wszystkich do poczytania z jakimi problemami ze strony swoich
czytelników borykał się JBW.
Zbycho Jabol/DIAL

Jeden z czytelników pyta : "Jaka instrukcja lub instrukcje asemblera odpowiadają
rozkazowi basicowemu "GRAPHICS 0"? Oczywiście nie ma gotowej recepty. Sposób postępowania będzie zależał od kontekstu,
czyli tego co się działo dotąd w komputerze oraz tego co zamierzamy dalej robić. Najprostrzą znaną mi metodą byłby skok:
JMP GR0
do systemowej procedury, która wykonuje wspomnianą instrukcję Basica. Niestety, należałoby ją poprzedzić deklaracją GR0
EQU... ale nie znam stosownego adresu. W tej sytuacji stosuję więc trick, który polega na otwarciu kanału dla
urządzenia "E:". System sam wykona akcję "GRAPHICS 0".
org $480
gr0 ldx #chn1
lda #open
sta io_com,x
lda e
sta io_adr+1,x
lda #read+write
sta io_mod,x
jsr ciov
W tym momencie na ekranie jest już tryb 0. Oczywiście nie można tak po prostu pozostawić aktywnego kanału. Ponieważ nie
będzie więcej używany, należy go zamknąć.
lda #close
sta io_com,x
jsr ciov
Można to zrobić bez obawy, że "close" "zabierze" tryb z ekranu. Ta sztuczka została zastosowana między innymi w
programie Panther. Powyższy program może służyć do przywrócenia standardowego ekranu z poziomu DOS-u. Wiele DOS'ów nie
regeneruje ekranu po zmianie trybu np. na 8 w Basic lub jakimś programie o ile program sam tego nie zrobi. Należy dopisać
zakończenie:
jmp (dosvec)
e dta c'E:',b(eol)
org runadr
dta a(gr0)
end
Taki program, wygenerowany po dołączeniu z przodu
opt list_err+code_dsk
i zasemblowaniu na dysk pod nazwą CLS. COM nadaje się do uruchomienia z poziomu DOS.
Czasem jednak zdarza się, że sekretny zamysł zmusi nas do tworzenia trybu 0 na
własną rękę. Do tego niezbędna jest wiedza o budowie programu ANTIC'a. Myślę, że wszyscy już ją posiadamy. Umieszczenie
DL w programie pozwala ulokować obszar ekranu praktycznie w dowolnym miejscu pamięci. Spróbujmy napisać taki "program"
bez programu, który objawi się naszym oczom podczas wczytywania:
opt list_err+code_dsk
dl_org equ $9c20
org dlptrs
dta a(dl)
Analogicznie należy zdefiniować dane DL:
org dl_org
dl dta b($70),b($70),b($70)
dta b($42),a(scr)
dta b(2),b(2),b(2),b(2)
dta b(2),b(2),b(2),b(2)
dta b(2),b(2),b(2),b(2)
dta b(2),b(2),b(2),b(2)
dta b(2),b(2),b(2),b(2)
dta b(2),b(2),b(2)
dta b($41),a(dl)
Zwróć uwagę,że nasza DL ma się znaleść pod adresem $9c20 czyli tam, gdzie się zwykle mieści podczas pracy z BASIC'em.
Jeżeli uruchomimy nasz program przy nieobecnym BASIC'u to DL i podążający za nią ekran znajdzie się w nietypowym w tych
warunkach miejscu. Po zakończeniu programu cała ta struktura pozostanie oczywiście widoczna na monitorze podczas gdy
inne adresy, jak SAVMSC ($58) wskazują całkiem co innego. To powoduje nieco zamieszania, gdyż komunikaty systemu będą
się "pokazywać" w niewidocznym dla nas miejscu. Jedynym rozsądnym dla nas wyjściem z tarapatów będzie użycie przycisku
RESET. Można, rzecz jasna, ustalić dl_org na $bc20, tak jak w systemie bez BASIC'a, ale wówczas program uruchomiony przy
BASIC zachowa się jeszcze gorzej: pokaże "sieczkę", gdyż obszar ten znajdzie się w obrębie ROM. Jest to ważna, choć nie
jedyna wada prezentowanego programu.
scr dta d'****** EKRAN TESTOWY'
dta d' WIERSZ #01 ********'
dta d'****** EKRAN TESTOWY'
dta d' WIERSZ #02 ********'
.....
dta d'*******EKRAN TESTOWY'
dta d' WIERSZ #24 ********'
Z brakującymi wierszami w miejscu kropek. Oczywiście zamiast przepisywać bezmyślnie "EKRAN TESTOWY" możesz pofolgować
swojej wyobraźni i wypełnić ekran według własnego uznania, na przykład obrazkiem ze znaczków semigraficznych. W
przykładzie każdy 40 znakowy wiersz został podzielony na dwie części po 20 znaków z powodu ograniczonej szerokości
szpalty w TA. (I ja to zachowałem dla SERIOUS'a - Jabol). Jednak we własnym programie można, a nawet trzeba wpisywać
każdy wiersz ekranu w jednej linijce. Pseudorozkaz DTA typu D służy do definiowania danych przeznaczonych do
wyświetlania na ekranie w formie znaków. Kody tych znaków, generowane przez QA różnią się od kodów ASCII (uzyskiwanych
przez DTA typu C), tak jak wymaga tego ANTIC. Trzeba o tym pamiętać, gdy sami umieszczamy dane w pamięci ekranu. Przy
korzystaniu z CIO napisy podaje się w ASCII, gdyż system sam dokonuje konwersji. Tu dostrzegamy drugą poważną wadę tego
sposobu wyświetlania. Program musi zawierać dane całego ekranu i DL, czyli bez mała 1kB choć czasem spora część ekranu
świeci pustką lub powtarza ten sam wzór. Niestety, choć program jest już niemal gotowy, okazuje się, że niektóre DOS'y
próbują ZAWSZE wykonać wczytany program, nawet gdy nie podamy adresu uruchomienia. Oto więc adres:
org runadr
dta a(dummy)
Jest to systemowa procedura inicjalizacji sterownika dysku, znana z literatury jako DISIV. Jest bardzo krótka i niewiele
robi (czytaj: psuje), nie stwierdziłem jej szkodliwego działania w żadnych warunkach. Wykorzystuję ją zazwyczaj jako
równoważnik rozkazu RTS którym jest zakończona, gdy nie mam pod ręką żadnego RTS-u. Pozostaje problem RESET-u, który
trzeba wykonać dla przywrócenia właściwych parametrów ekranu. Pozostawienie tego zapominalskiemu użytkownikowi byłoby
nieroztropne. Z drugiej strony trzeba zostawić trochę czasu na zapoznanie się z obrazem. 10 sekund powinno wystarczyć:
t equ 500 (10*50)
org cdtmv2
dta a(t)
Wpisanie wartości do licznika cdtmv2 inicjuje odliczanie czasu. Ponieważ jednostką jest tu 1/50 sekundy, więc na sekundę
trzeba ich 50. Po odliczeniu do zera system wykona skok do adresu podanego w cdtma2. Trzeba tam wpisać (szybko zanim
upłynie te 10 sekund).
org cdtma2
dta a(warmst)
end
Proszę zauważyć, że w całym programie nie padł ani jeden rozkaz maszynowy! Pokazana metoda nie nadaje sie do krótkich
programów uruchamianych DOS'em. Doskonała jest natomiast do czołówek gier, w trybie graficznym.

Zapraszam wszystkich cierpliwych i wytrwałych czytelników do piątego wydania
magazynu SERIOUS na ostatni już trzyczęściowy odcinek kursu programowania procesora 6502...
|