Czy zastanawialiście się kiedyś, jak użyć polecenia USR w Basic’u? W tym artykule omówię tę przydatną funkcję razem z przykładami dla 8-bitowych komputerów Commodore. Przeanalizujemy także użycie liczb pomiędzy Basic’iem i kodem maszynowym. Zapraszam.
Zarówno polecenia USR, jak i SYS są podobne do instrukcji GOSUB występującej w każdym dialekcie Basica. Jednak zamiast przesyłać sterowanie programu do podprogramu można spowodować, że kontrola przechodzi do podprogramu języka maszynowego. Tak właśnie działają instrukcje USR i SYS. Pierwsza z nich ma dodatkową możliwość przesyłania liczb lub informacji także do podprogramu języka maszynowego.
Ogólny format polecenia USR to:
n=USR(v)
gdzie N oznacza dowolną nazwę zmiennej, a V jest zmienną, której wartość ma zostać przeniesiona do podprogramu Asemblera. Po powrocie do Basic’a, podprogram języka maszynowego umieści nowo obliczoną wartość w zmiennej N. Przekazywanie wartości i informacji pomiędzy jednym i drugim językiem odbywa się za pośrednictwem akumulatora zmiennoprzecinkowego, czyli FAC. Technicznie rzecz biorąc, jest to pięć kolejnych bajtów w pamięci, które są używane do przechowywania liczb zmiennoprzecinkowych. Informacja adresowa dla podprogramu języka maszynowego jest określona w adresach 785 i 786 w Commodore 64 i jest zapisywana w standardowym formacie LBHB (czyli Low Byte, High Byte).
Na przykład, poniższe polecenie:
nv=USR(ov)
w programie Basic’a najpierw przesyła wartość OV do FAC. Następnie program rozgałęzia się do podprogramu języka maszynowego, którego adres początkowy jest przechowywany w adresach 785 i 786. Przed opuszczeniem podprogramu programista przechowuje nowo obliczoną wartość w FAC i wydaje polecenie RTS powodujące powrót z podprogramu. Po przywróceniu Basic’a, wartość w FAC jest utrzymywana przez zmienną NV, a działanie programu w Basic’u jest kontynuowane od miejsca, w którym został wcześniej przerwany.
Jedyną rzeczą uniemożliwiającą pełne wykorzystanie funkcji USR jest konwersja danych zmiennoprzecinkowych w FAC. Liczba musi najpierw być w formacie używanym przez program języka maszynowego, a następnie obliczona wartość musi zostać ponownie sformatowana do FAC, aby możliwy był powrót do programu w Basic’u.
Cała sztuka polega na znajomości położenia dwóch ważnych podprogramów w pamięci. Jeden z nich konwertuje zawartość FAC na liczbę całkowitą o podwójnej precyzji, przechowywaną w adresach $61 i $62 w komputerze PET oraz odpowiednio – $64 i $65 on w VIC-20 oraz Commodore 64.
Poniższe podprogramy są bardzo łatwe w użyciu. Na początek zmień zawartość Basic’a według listingów przedstawionych jako Program numer 1 oraz Program numer 2. Pierwszy podprogram pozwala dokonać konwersji zawartości FAC na liczbę o podwójnej precyzji:
JSR $DBA7
LDA $61
LDY $62
Rejestry A i Y zawierają skonwertowaną wartość całkowitą T w równaniu zapisanym w Basic’u jako:
S=USR(T)
Kiedy musisz przenieść wartość z programu języka maszynowego z powrotem do Basic’a, liczba całkowita musi zostać umieszczona w FAC w odpowiednim formacie. Poniższy kod spełni to zadanie bardzo dobrze:
LDA $61
LDY $62
JSR $D26D
RTS
Zmienna S będzie zawierać przeliczoną wartość dostarczoną przez program języka maszynowego. Te dwa podprogramy powinny wystarczyć w przypadku większości programów.
Aby zilustrować użycie tych programów, użyjmy funkcji USR do symulacji działania instrukcji PEEK. Dzięki temu możemy ocenić podprogramy, porównując wyniki testu z wynikami ogólnie znanej instrukcji. Wybrany przykład jest co prawda przydatny główne jako narzędzie do nauki, ale w końcu każdy kiedyś zaczynał programować.
Właściwy program dla VIC-20 to Program numer 3, natomiast wersja dla C64 – Program numer 4. Oba znajdziecie w ramkach obok. Dalej trzeba wpisać Program numer 5, zwracając szczególną uwagę na linię 120. Liczby w liniach zawierających instrukcje DATA zawierają program języka maszynowego i muszą być wpisane bardzo dokładnie. Uwaga: przed uruchomieniem programu koniecznie go zapisz na taśmie lub dyskietce.
W języku maszynowym będzie to wyglądało tak:
$033C 20 A7 DB JSR $DBA7
IN $61, $62
$033F A5 61 LDA $61
$0341 85 FC STA $FC
$0343 A5 62 LDA $62
$0345 85 FB STA $FB
$0347 A0 00 LDY #$00
$0349 B1 FB LDA ($FB),Y
ADDR
$034B A8 TAY
$034C A9 00 LDA #$00
$034E 20 6D D2 JSR $D26D
$0351 60 RTS
Teraz spójrzmy na wszystkie siedem etapów, które są niezbędne do funkcjonowania funkcji USR. Pierwszym krokiem jest umieszczenie adresu początkowego języka maszynowego w adresach $01 i $02. Najmniej znaczący bajt adresu (LSB) jest przechowywany w pierwszym adresie, a najbardziej znaczący bajt adresu (MSB) jest przechowywany w adresie drugim. Adres startowy to $033C, dlatego adres $00 powinien zawierać następujący kod:
$0000 4C 3C 03 JMP $033C
W języku BASIC wartości adresowe muszą być podane w systemie dziesiętnym. Nie ma potrzeby wywoływania instrukcji POKE z adresem $310 (wersja dla C64), ponieważ została zainicjowana do właściwej wartości po włączeniu komputera.
W swoim programie w Basic’u ustal wartość T w równaniu S=USR(T) Aby korzystać z podprogramów podanych w tym artykule, T musi być liczbą całkowitą o wartości od 0 do 65535. Jest to pełny zakres 2-bajtowych liczb całkowitych, a w systemie szesnastkowym jest równy od $0000 do $FFFF. W tym przykładzie linie od 130 do 150 są używane do wprowadzania i testowania liczby całkowitej z tego samego zakresu.
Gdy wykonywane jest S=USR(T), jest to równoważne instrukcji SYS (828). W obu przypadkach kontrola jest przekazywana z Basic’a do programu języka maszynowego. Funkcja USR różni się od polecenia SYS tym, że FAC może służyć do przekazywania rzeczywistych danych do i z programu w Asemblerze.
W programie języka maszynowego konwertujemy wartość z FAC na dwubajtową liczbę całkowitą. Instrukcje zapisane w adresach od $033F do $034A są używane do pobierania wartości, którą chcemy przekazać do Basic’a.
Instrukcje od adresów $034B do $0351 wpisują wartość całkowitą w rejestrach A i Y. Ponieważ symulowana wartość PEEK jest tak naprawdę tylko jedną liczbą całkowitą, MSB jest ustawione na zero. JSR w $D26D zamieni wartości w rejestrach A i Y na zmiennoprzecinkowe i umieści je w FAC. Wreszcie, RTS zwróci kontrolę do programu w Basic’u.
Prawdziwa zmienna S zostanie teraz przypisana wartości umieszczonej w FAC przez program języka maszynowego. Linie 170 i 180 w Programie numer 5 wyświetlają zarówno wartość S, jak i rzeczywistą wartość PEEK w celu sprawdzenia, czy symulacja jest prawidłowa.
To wszystko, co trzeba wiedzieć. Na koniec dodam kilka słów na temat przechowywania danych w niskich (Low Byte) i wysokich (High Byte) bajtach. Wcześniej wspomniałem o metodzie LBHB, która właśnie używa tych pojęć. Jest to sposób zapisywania dużych liczb stosowany przez wiele mikrokomputerów.
Bajt może zmieścić liczbę nie większą niż 255, dlatego do reprezentowania liczb większych niż 255 potrzebujemy dwóch lub więcej kolejnych bajtów. Format LBHB obejmuje metodę, w której liczby są dzielone, a następnie zapisywane w pamięci jako najmniej znaczącym bajt (LSB) najpierw, a następnie jako najbardziej znaczący bajt (MSB).
Liczba od 256 do 65535 jest przechowywana w pamięci RAM za pomocą dwóch kolejnych bajtów. Drugi bajt (czyli najbardziej znaczący) otrzymuje się poprzez podzielenie oryginalnej liczby przez 256, a następnie zapisanie wartości liczby całkowitej (bez ułamków) w MSB. Pozostała część tego podziału jest przechowywana w pierwszym (czyli najmniej znaczącym) bajcie. Można powiedzieć, że korzystamy z poniższego wzoru:
liczba = LSB + (MSB * 256)
Na przykład, powiedzmy, że chcemy skierować działanie do adresu 828, czyli bufora magnetofonu. W tym celu należy umieścić wartość 848 a adresach $01 i $02. Musimy cały czas korzystać z formatu LBHB. Jak to zrobić? Dzielimy 828 przez 256 i zapisujemy wynik w drugim bajcie. Pozostałą część z podziału zapisujemy w bajcie pierwszym. Dalej używamy wzoru:
N = bajt pierwszy + (256 * ba-
jt drugi)
Zmienna N oznacza oczywiście naszą liczbę, a całość pozwala odczytać liczby w formacie LBHB. Drugi sposób to wzór w innej postaci:
NN = INT (N / 256): POKE ba-
jt pierwszy, N – (NN *
256): POKE bajt drugi, NN
Technika ta wymaga kilku prób, aby w pełni zrozumieć działanie oraz zrozumienia wzorów matematycznych, ale nie ominiemy tego, jeśli chcemy programować w języku maszynowym. W zamian uzyskujemy dużo większe możliwości używania podprogramów.
Program nr 1: Wersja dla PET
033C 20 A7 DB A5 61 85 PC A5
0344 62 85 FB A0 00 Bl FB A8
034C A9 00 20 6aD D2 60 00 FF
Program nr 2: Wersja dla BASIC 4.0
033C 20 Dl CD A5 61 85 FC A5
0344 62 85 FB A0 00 Bl FB A8
034C A9 00 20 BC C4 60 00 FF
Program nr 3: Wersja dla VIC-20
10 FOR A = 828 TO 849 : READ D : POKE A, D : NEXT
20 DATA 32, 155, 220, 165, 100, 133, 252, 165
30 DATA 101, 133, 251, 160, 0, 177, 251, 168
40 DATA 169, 0, 32, 145, 211, 96
Program nr 4: Wersja dla Commodore 64
10 FOR A = 828 TO 849 : READ D : POKE A, D : NEXT
20 DATA 32, 155, 188, 165, 100, 133, 252, 165
30 DATA 101, 133, 251, 160, 0, 177, 251, 168
40 DATA 169, 0, 32, 145, 179, 96
Program numer 5
100 REM SAVE BEFORE RUNNING
110 POKE 1, 60 : POKE 2, 3 : REM JMP $033C
120 REM FOR C-64, USE POKE 785, 60 : POKE 786, 3
130 PRINT „SIMULATED PEEK” : PRINT
140 PRINT „INPUT AN ADDRESS BETWEEN 0 AND ˜ 65535”
150 INPUT T : IF T < 0 OR T > 65535 OR INT(T) <> T THEN 140
160 S = USR(T) : REM SYS 828 ($003C)
170 PRINT S” = PEEK(„T”)” , PEEK(T) : PRINT
180 GOTO 140
Opracował: Mariusz Wasilewski