Tajemnice ATARI

Programowanie 6502


Errare humanum est



   W poprzednim odcinku proponowałem Państwu testowanie "buta" kasetowego przez wykonanie zimnego startu z klawiszami SELECT I START. Oczywiście miałem na myśli klawisze START I OPTION. Za pomyłkę przepraszam. Po prostu jest tych klawiszy tak wiele, że trudno się w tym wszystkim połapać. I jeszcze sprostowanie do sprostowania z TA 6/91: znów pominąłem grupę rozkazów, które modyfikują znacznik C. Są to: CMP, CPX, CPY czyli rozkazy porównania zawartości odpowiednio rejestrów A, X, Y z argumentem w pamięci.

Miała baba koguta...


   ...wsadziła go do boota! Użytkowników ATARI z pamięcią taśmową można poznać z daleka po charakterystycznym skrzywieniu sylwetki. Lewa ręka jest nieco dłuższa, wyciągnięta w kierunku wyłącznika zasilania, palce prawej układają się w rodzaj widełek, które nieomylnie trafiają w klawisze OPTION i START. A przecież jest to postawa unikalna w świecie komputerów. Maszyny PC włącza się rano, a wyłącza wieczorem, są także komputery, które nie usypiają nigdy... Ożywia je system operacyjny, którego zadaniem jest uruchamianie kolejnych programów, zgodnie z żądaniami użytkowników. ATARI XL/XE jest w pełni przystosowany do takiego stylu pracy, lecz niestety, brak jest ogólnie uznanego standardu, któremu wierni byliby programiści. Tymczasem zasada jest prosta. Istnieje tylko jeden "boot": jest nim dyskowy lub kasetowy system operacyjny. Każdy inny program powinien się uruchamiać pod kontrolą tego systemu (z zasady DOS oferuje więcej możliwości, niż komputer sam z siebie) i po zakończeniu działania do niego powrócić. Niedopuszczalny jest brak w programie opcji wyjścia (zmuszanie użytkownika do wyłączania zasilania stanowi jawny zamach na jego czas i portfel, bo zwiększa ryzyko awarii).

Kanały



   Nie te na Marsie i nie w Wenecji, lecz tu, w pamięci naszego ATARI, kanały wejścia/wyjścia są niezwykle ważne dla działania komputera, a na dodatek wcale ich nie ma. Mianem kanału określa się bowiem coś ulotnego: metodę realizacji połączenia pomiędzy uniwersalną procedurą wejścia/wyjścia, zwaną CIO, a sterownikiem konkretnego urządzenia. Dzięki tej metodzie możliwe jest jednakowe traktowanie wszystkich urządzeń zewnętrznych, niezależnie od ich fizycznych właściwości. Prostym przykładem użycia kanału będzie wysłanie tą drogą napisu "Hej, to ja!". Zobaczmy to na żywym programie:

     OPT %10101
CIOV EQU $E456
IOCB EQU $340
CHN1 EQU $10

   CIOV stanowi tzw. wektor, pod adresem $E456 znajduje się rozkaz skoku do właściwej procedury CIO. IOCB określa początek zestawu ośmiu obszarów zwanych blokami sterowania we/wy (Input/Output Contral Block). Każdy z nich ma rozmiar 16 bajtów ($10) i zawiera wszystkie informacje niezbędne do funkcjonowania danego kanału. Istnieje zatem 8 kanałów, o numerach od O do 7, każdy opisany osobnym obszarem IOCB.

     ORG $480

     LDX #CHN1

   Odwołując się do CIO wskazujemy zawsze rejestrem x, o który kanał chodzi. Liczba ta jest numerem kanału pomnożonym przez 16 tak, by zsumowana z adresem początku obszarów IOCB dała adres interesującego nas obszaru. W tym programie posłużymy się kanałem nr 1. Przed rozpoczęciem przesyłania danych należy kanał otworzyć, w wyniku czego zostanie on logicznie związany ze sterownikiem danego urządzenia:

OPEN EQU 3
IN   EQU %0100
OUT  EQU %1000
COMM EQU IOCB+2
ADDR EQU IOCB+4
MODE EQU IOCB+10

     LDA #OPEN
     STA COMM,X
     LDA <NAME
     STA ADDR,X
     LDA >NAME
     STA ADDR+1,X
     LDA #OUT
     STA MODE,X
     JSR CIOV

   COMM oznacza tu jednobajtowy rejestr rozkazu - w nim zawsze podaje się kod operacji żądanej od CIO. ADDR jest dwubajtowym adresem obszaru danych, na którym ma operować dana funkcja CIO. W przypadku OPEN (otwarcia) pod adresem NAME dostarczymy nazwę urządzenia. Bajt MODE wskazuje na tryb zamierzonego przesyłania danych: IN oznacza odczyt, zaś OUT - zapis. Aby umożliwić transmisję w obie strony, podaje się IN+OUT, lecz nie każde urządzenie na to pozwala.

   Przez tak otwarty kanał można teraz przesyłać dane, co też niezwłocznie uczynimy:

PISZ EQU 11
SIZE EQU lOCB+8

     LDA #PISZ
     STA COMM,X
     LDA <TEXT
     STA ADDR,X
     LDA >TEXT
     STA ADDR+1,X
     LDA <LEN
     STA SIZE,X
     LDA >LEN
     STA SIZE+1,X
     JSR CIOV

   Ponieważ CIO pozostawia nienaruszoną zawartość rejestru X, można go bez obawy nadal używać do indeksowania odwołań do IOCB. W słowie ADDR podaje się teraz adres danych przeznaczonych do przesłania. Istnieją dwa rozkazy zapisu: kod 11, zwany binarnym i 9, czyli tekstowy. Ten pierwszy wymaga podana w słowie SIZE dokładnej liczby bajtów, które zostaną przesłane bez wnikania w ich rodzaj. Drugi rozkaz, który stosuje się do danych tekstowych, polega na tym, je w przypadku podana większego rozmiaru niż długość wiersza transmisja przerywa się z chwilą napotkania i wysłania znaku końca wiersza (o kodzie 155). W obu przypadkach po powrocie z CIO słowo SIZE będzie zawierać liczbę faktycznie przesłanych bajtów. Po wysłaniu porcji danych kanał pozostaje otwarty i aktywny, wciąż związany z danym urządzeniem i gotów do dalszych rozkazów. Aby zakończyć całą operację, trzeba kanał zamknąć, by mógł być znów w przyszłości użyty do kontaktów z innym urządzeniem:

CLOSE EQU 12

      LDA #CLOSE
      STA COMM,X
      JSR CIOV
      RTS

   Nie wolno tej czynności lekceważyć, gdyż w przypadku urządzeń, które zapisują dane blokami (jak napęd dysków), dopiero CLOSE spowoduje fizyczny zapis kompletnej informacji. Wyjątek stanowi zawsze otwarty kanał nr 0, używany przez system dla urządzenia "E:" w trybie IN+OUT. Nie należy go nigdy otwierać ani zamykać.

   Dopiszmy do naszego programu jeszcze przykładowy tekst:

EOLN EQU 155

TEXT DTA C'Hej, to ja !'
     DTA B(EOLN)
LEN EQU *-TEXT

   Symbol, który w wyrażeniach oznacza bieżący wskaźnik lokacji programu, pozwala nadać etykiecie LEN wartość równą długości tekstu, bez potrzeby liczenia znaków.

   Pozostaje jeszcze tylko nazwa urządzenia, czy raczej ściślej: nazwa pliku wraz z określeniem urządzenia. Tu mamy szeroki wybór: Jeżeli napiszemy

NAME DTA C'E:'

to tekst przykładowy pojawi się na ekranie. Jeśli podamy

NAME DTA C'P:'

to zostanie on wydrukowany na drukarce. Jeżeli napiszemy

NAME DTA C'D:DOWOL.TXT'

to tekst zostanie zapisany na dyskietce w pliku o nazwie DOWOL.TXT. Myślę, że takie unieza1eżnienie od fizycznych parametrów urządzenia bardzo upraszcza konstruowanie programów. Nie jest to oczywiście przypadek, lecz efekt przemyśleń twórców systemu operacyjnego małego ATARI. A dla tych, którzy jeszcze tego nie zauważyli

     END

i to już koniec przykładowego programu. Celowo pominięto tu badanie, czy dana operacja wykonywana przez CIO zakończyła się pomyślnie. W prawdziwym programie każde odwołanie do CIO należy wzbogacić:

     JSR CIOV
     BMI ERROR

gdyż CIO w przypadku niepowodzenia ustawia znacznik N. Fragment programu od etykiety ERROR powinien reagować w odpowiedni sposób na doznaną porażkę, wnioskując o rodzaju błędu z rejestru Y, który zawiera jego numer. Tę samą liczbę można znaleźć w IOCB+3,X. Spis możliwych błędów we/wy był drukowany w TA.

Nakładki

   Trzecim rodzajem programów, obok omówionych już systemów operacyjnych (boot) i programów użytkowych (w szerokim rozumieniu tego słowa), są rezydentne rozszerzenia systemu, zwane krótko nakładkami. Taki program jest tworem pośrednim, gdyż wczytuje się i uruchamia tak, jak program użytkowy, lecz przyczepia się do systemu, pozostając w pamięci jako jego część.

   W tym miejscu, aby poszerzyć sobie horyzonty, zajrzyjmy do artykułu "Jak powstają DOS-y", który zawiera nieco więcej informacji na ten temat, po czym być moje ogarnie nas chęć wykonania rezydentnego sterownika jakiegoś nowego urządzenia, np. "B:", co można uznać za skrót słowa "border", które oznacza ramkę ekranu. Nic łatwiejszego:

OPT %100101
ORG $9000

   Tym razem nie będziemy asemblować programu do pamięci, lecz wprost na dysk (lub kasetę), ponieważ zainstalowanie go pod adresem określonym w MEMLO spowodowałoby zniszczenie QA. Program w chwili uruchomienia znajdzie się pod adresem $9000, skąd dopiero należy go przenieść w nieznane z góry miejsce, którego adres znajdziemy w MEMLO. Niech nasze nietypowe urządzenie zapisuje wysyłane do niego dane w komórce 712, odpowiadającej za kolor ramki ekranu, zaś przy odczycie niech pobiera bajty z rejestru liczb losowych, w przypadku zera meldując błąd 136, co oznacza koniec danych.

RANDOM EQU $D20A
NEWDEV EQU $EEBC
BORDER EQU 712
MEMLO  EQU $2E7
EOF    EQU 136
DOSINI EQU 12

START  EQU *

   Poniższy fragment stanowi procedurę inicjalizacji, która zastąpi dotychczasową sekwencję wykonywaną po naciśnięciu klawisza RESET.

INIT   JSR 0

   Pierwszą czynnością, którą musi wykonać nowa procedura inicjalizacji (podepniemy ją do wektora DOSINI) jest wywołanie starej procedury inicjalizacji. Podczas wstępnej fazy instalowania naszej nakładki zostanie w miejscu argumentu 0 umieszczony adres dotychczasowej procedury odczytany z DOSINI. Następną czynnością będzie wpisanie do tablicy HATABS informacji o urządzeniu "B:"

LDX #'B'
LDY BHADR
LDA BHADR+1
JSR NEWDEV

Wreszcie - poprawienie MEMLO tak, by adres ten wskazywał obszar powyżej naszego programu:

LDA NEWML
STA MEMLO
LDA NEWML+1
STA MEMLO+1
RTS

Zestaw procedur sterownika: OPEN, CLOSE, GET, PUT, STATUS, SPECIAL. Zwróć uwagę, je mają wiele wspólnych fragmentów kodu (zostały zoptymalizowane pod kątem oszczędności pamięci):

CLOSE  LDA #0
PUT    STA BORDER
OPEN   EQU *
STATUS EQU *
SPEC   EQU *
OK     LDY #1
       RTS
GET    LDA RANDOM
       BNE OK
       LDY #EOF
       RTS
       BRK (koniec programu)

   Pojedynczy rozkaz BRK jest wymagany dla programu przesuwającego jako znacznik końca kodu. Bezpośrednio po nim następuje blok adresów, zastępujących użycie w programie rozkazów w trybie natychmiastowym z argumentami "<" i ">" (takie rozkazy nie mogłyby być przemieszczone).

NEWML DTA A(ENDBH)
BHADR DTA A(BHTAB)

oraz tablica sterownika, której adres zostanie umieszczony w HATABS.

BHTAB DTA A(OPEN-1)
      DTA A(CLOSE-1)
      DTA A(GET-1)
      DTA A(PUT-1)
      DTA A(STATUS-1)
      DTA A(SPEC-l)

   Klasyczna tablica sterownika zawiera jeszcze na końcu rozkaz skoku JMP do bliżej nieokreślonej procedury inicjalizującej, ale z całą pewnością nikt i nic z niego nie korzysta, więc pozwoliłem sobie pominąć go.

ENDBH EQU *
      DTA A(O)

   Pojedynczy adres 0 informuje program przesuwający, że skończył się blok adresów przesuwalnych. Dalej mogą się znaleźć teksty komunikatów, różne bufory, itd., słowem takie dane, których nie trzeba modyfikować podczas przesuwania programu w pamięci. Dziwnym zrządzeniem losu ten program nie zawiera takich danych. Tu kończy się część rezydentna. Dalsza część programu wykona się jednorazowo w chwili uruchomienia, lecz nie wchodzi w skład rezydenta i nie musi być przesuwana.

SETINI LDX #1
SI     LDA DOSINI,X
       STA INIT+1,X
       LDA MEMLO,X
       STA DOSINI,X
       DEX
       BPL SI
       RTS

   Procedura SETINI ustawia nowy adres inicjalizacji systemu, zapamiętawszy przedtem stary. Na tym kończy się program sterownika, lecz nie przerywajmy pisania...

   Bo oto uwaga! Prezent pod choinkę! Następujący program potrafi przemieszczać w pamięci dowolne programy, z góry w dół, na granicę MEMLO, pod warunkiem zachowania kilku prostych zasad (większość z nich zasygnalizowano w tym przykładzie):
  • program zapiszesz w jednym ciągłym obszarze pamięci wraz z relokatorem
  • wykonanie Twego programu musi się rozpocząć od jego pierwszego bajtu
  • brak rozkazów z "<" i ">" (w zamian korzystanie z danych adresowych)
  • BRR na końcu segmentu kodu
  • tuż po BRK tablica adresów przesuwalnych (wszystkie wskazują na obszar objęty przemieszczaniem) zakończona adresem spoza tego zakresu
  • na ostatku segment zwykłych danych, które przemieszcza się bez zmian
       Relokator następuje tuż po segmencie danych i musi mieć zdefiniowane dwie etykiety: STAR__ określa początek programu do przesuwania (znajdzie się on pod adresem zawartym w MEMLO), USER__ wskazuje na procedurę, która będzie wykonana PRZED rozpoczęciem przemieszczania programu. Wszystkie etykiety użyte w relokatorze kończą się dwoma znakami podkreślenia dla uniknięcia konfliktów z nazwami w przesuwanym programie.




  • STAR__ EQU START
    USER__ EQU SETINI
    
    
    *------------------*
    *                  *
    *  Relocator 1.0   *
    *                  *
    *     by JBW       *
    *                  *
    *     may'88       *
    *                  *
    *------------------*
    
    
    *--- 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)
    MELO__ EQU $2E7  (2)
    
    
    *--- move ----------
    
    MOVE__ EQU *
           JSR USER
    * disable RUN comm
           LDA #$60 (RTS)
           STA MOVE__
    * clear data flag
           LDA #0
           STA DATF__
    * destination
           LDA MELO__
           STA DEST__
           LDA MELO__+1
           STA DEST__+1
    * code source, distance
           SEC
           LDA <STAR__
           STA SRCE__
           SBC DEST__
           STA DIST__
           LDA >STAR
           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 !
           JMP (MELO__)
    * 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 #$lF
           CMP #$09
           BEQ MOV1__
    * 3-byte instruction
    TPE3__ EQU *
           LDA (SRCE__),Y
           INY
           CMP <STAR__
           LDA (SRCE__),Y
           DEY
           SBC >STAR__
           BCC MOV2__
           LDA (SRCE__),Y
           INY
           CMP <MOVE__
           LDA (SRCE__),Y
           DEY
           SBC >MOVE__
           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 RUNA__
           DTA A(MOVE__)
    
           END
    
    
       Podczas asemblacji powstanie plik typu COM, który można uruchomić spod DOS-u lub COS-u. Zaprezentowane tu urządzenie "B:" jest pomocne przy testach własnych programów I/O. Pozwala zapisywać dane, mrugając do taktu ramką obrazu i odczytywać losowe pliki nie zawierające zer (oczywiście dane odczytywane nie mają żadnego związku z zapisywanymi).

    Janusz B. Wiśniewski

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

    Pixel 2001