|
Serwis Edukacyjny Nauczycieli w I-LO w Tarnowie
Materiały dla uczniów liceum |
Wyjście Spis treści Wstecz Dalej
Autor artykułu: mgr Jerzy Wałaszek |
©2026 mgr Jerzy Wałaszek
|
| SPIS TREŚCI |
Pisanie programów na ZX81 zawsze było męką. ZX81 BASIC jest potwornie wolny, z kolei asembler posiada niezbyt wygodny edytor. Jeśli tworzysz kompilowane programy, to w pamięci musisz przechowywać tekst źródłowy. W efekcie masz mniej pamięci dla kodu wynikowego. Przy standardowych 16KB może to być dosyć bolesne.
Dlatego w tym rozdziale przedstawiam tworzenie systemu programowania w asemblerze dla ZX81 na komputerze IBM-PC. Dostaniesz coś, o czym w latach 80-tych mogłem jedynie pomarzyć. Narzędzia te są przeznaczone dla zaawansowanych użytkowników, którzy potrafią (lub potrafili) programować ZX81 w języku maszynowym.
Zacznijmy od skompletowania niezbędnych narzędzi, które są zupełnie DARMOWE i LEGALNE. Będziesz potrzebował cztery rzeczy:
Poniżej wyjaśniam szczegółowo pracę w tym systemie.
Komputer ZX81 posiada dwa rozkazy współpracujące z pamięcią zewnętrzną:
| LOAD "nazwa programu" – służy do odczytu programu w języku
BASIC z
taśmy SAVE "nazwa programu" – zapisuje bieżący program na taśmie |
Jeśli pracujesz z emulatorem VB81, to rozkazy te powodują odczyt lub zapis plików taśmy o rozszerzeniu P w bieżącym katalogu taśmy, który ustawiamy odpowiednią opcją menu. Plik taśmy P zawiera dokładnie to samo, co plik odczytywany na rzeczywistym ZX81 z magnetofonu kasetowego. Naszym celem będzie utworzenie takiego właśnie pliku, w którym umieścimy nasz program w języku maszynowym.
Plik taśmy P zawiera ciągły obszar pamięci zawierający zmienne systemowe, program w języku ZX81 BASIC, bufor wyświetlania DFILE oraz obszar zmiennych języka BASIC. Zapis do pliku rozpoczyna się od adresu szesnastkowego 4009H (16393), a kończy się na końcu obszaru zmiennych języka BASIC. W miejscu tym jest umieszczany bajt o kodzie 80H (128), a jego adres jest równy (E_LINE)-1. Zatem całe zadanie sprowadza się do wygenerowania pliku binarnego, który zawiera obszar pamięci ZX81 od 4009H do (E_LINE)-1.
Kolejnym problemem jest miejsce umieszczania kodu maszynowego. Konstrukcja ZX81 uniemożliwia wykonywanie kodu maszynowego ponad adresem 7FFFH (32767). Jest to spowodowane ingerencją układu ULA w kody wykonywanych instrukcji. Wyświetlanie obrazu w ZX81 jest już tak skonstruowane, iż dla ULA kod maszynowy wykonywany ponad adresem 7FFFH oznacza tworzenie obrazu i wszystkie instrukcje mikroprocesora Z80, które mają wyzerowany bit nr 6 w swoim kodzie, zostaną przekształcone na instrukcję NOP przez ULA. Zatem kod maszynowy musi się znaleźć w obszarze od 4000H do 7FFFH. Tutaj mamy właściwie dwie możliwości:
Dla naszych celów najlepszym rozwiązaniem jest umieszczenie całego kodu w pierwszym wierszu programu. Pierwszy wiersz posiada zawsze stały adres 407DH (16509). W ZX81 każdy wiersz programu posiada następującą budowę:
| Bajty | Wyjaśnienie |
| 2 | Numer wiersza w kolejności MSB,LSB – odwrotnie niż normalny zapis liczb 2-bajtowych. |
| 2 | Długość wiersza |
| długość-1 | Tekst wiersza programu |
| 1 | NEWLINE |
Zatem strategia jest następująca:
Cały program w języku maszynowym umieszczamy wewnątrz instrukcji REM w wierszu pierwszym (nadamy mu numer 0, aby nie można go było edytować w edytorze ZX81 BASIC). Długość wiersza wyliczy asembler na podstawie długości programu w kodzie maszynowym. Daje nam to zupełną swobodę co do ilości instrukcji i danych (pod warunkiem nieprzekroczenia dopuszczalnego rozmiaru pamięci). 16KB dla ZX81 to wystarczająco dużo. Pierwsza instrukcja programu w języku maszynowym rozpocznie się od adresu:
16509 – początek programu+
2 – numer wiersza+ 2 – długość wiersza+
1 – kod instrukcji REM------- 16514 – adres pierwszej instrukcji w
kodzie maszynowym |
Program maszynowy musi być uruchomiony po załadowaniu się programu do pamięci. Zadanie to wypełni drugi wiersz programu (o numerze 1), który będzie zawierał polecenie:
| RAND USR VAL "16514" |
W zmiennej systemowej NXTLIN umieścimy adres drugiego wiersza programu. Spowoduje to, iż po załadowaniu do pamięci zawartości pliku taśmy P ZX81 automatycznie wykona wiersz drugi i w ten sposób twój kod maszynowy będzie uruchomiony. A co stanie się dalej, zależy już wyłącznie od twojego kodu.
Podsumowując, program w ZX81 BASIC ma następującą postać:
| 0 REM kod maszynowy 1 RAND USR VAL "16514" |
Ściągnięte z naszego serwera pliki szablonowe asm zapisz w swoim katalogu projektowym. Plikiem projektu jest plik o nazwie main.asm. Aby otrzymać plik taśmy P musisz skompilować plik main.asm przy pomocy asemblera tniasm (plik ten i reszta plików są przygotowane specjalnie dla tego asemblera). Na szczęście robi się to bardzo prosto. Jeśli umieścisz tniasm.exe w twoim katalogu projektowym z resztą plików asm (dla PC-ta plik tniasm.exe to pchełka), to wystarczy wydać polecenie np. z konsoli Windows:
| tniasm main.asm |
Jeśli pracujesz w poleconym przez nas edytorze MicroAsm, to wejdź w menu do Options → Program Options i w oknie dialogowym zdefiniuj pola Command (ścieżka dostępu do programu tniasm.exe na twoim dysku) i Arguments (plik main.asm):

Teraz możesz kompilować cały projekt klawiszem F7 – pamiętaj o przejściu do katalogu projektowego!
Pliki szablonowe są następujące:
| main.asm | : | główny plik projektowy |
| chars.asm | : | definiuje kody znaków dla ZX81, które są niestandardowe. Kody te używasz do wyświetlania znaków ZX81. |
| svars.asm | : | definiuje obszar zmiennych systemowych, który jest umieszczany na początku pliku taśmy P. Przy okazji zostają tutaj zdefiniowane nazwy i adresy poszczególnych zmiennych systemowych, z których możesz swobodnie korzystać w swoich własnych programach. |
| line0.asm | : | pierwszy wiersz programu ZX81
BASIC zawierający instrukcję REM, za którą będzie umieszczony program w kodzie maszynowym. |
| myprg.asm | : | w tym pliku umieszczasz
swój kod maszynowy. W pamięci ZX81 będzie się on znajdował począwszy od adresu 16514. Tylko ten plik praktycznie edytujesz w swoim projekcie – pozostałe pliki są opakowaniem twojego programu w kodzie maszynowym i służą do wygenerowania poprawnej zawartości pliku taśmy P. Nie zmieniaj ich treści bez wyraźnej i świadomej potrzeby. |
| line1.asm | : | drugi wiersz programu ZX81
BASIC zawierający polecenie RAND USR VAL "16514", które uruchamia twój kod maszynowy, bufor wyświetlania DFILE oraz pusty obszar zmiennych języka BASIC. |
Poniżej przedstawiam treść poszczególnych plików:
| Program w asemblerze: main.asm |
; Plik projektu dla ZX81 FNAME "TEST.P" ; nazwa pliku taśmy dla ZX81 CPU Z80 ORG $4000,$7FFF INCLUDE "chars.asm" ; definicje znaków ZX81 INCLUDE "svars.asm" ; zmienne systemowe ZX81 BASIC INCLUDE "line0.asm" ; wiersz numer 0 INCLUDE "myprg.asm" ; główny program maszynowy INCLUDE "line1.asm" ; wiersz numer 1, bufor wyświetlania, zmienne ZX81 BASIC |
| Program w asemblerze: chars.asm |
; Znaki ZX81 ; ; ____0___1___2___3___4___5___6___7___8___9___A___B___C___D___E___F____ ; 00 SPC GRA GRA GRA GRA GRA GRA GRA GRA GRA GRA " GBP $ : ? 0F ; 10 ( ) > < = + – * / ; , . 0 1 2 3 1F ; 20 4 5 6 7 8 9 A B C D E F G H I J 2F ; 30 K L M N O P Q R S T U V W X Y Z 3F __: EQU $00 ;spacja _QT: EQU $0B ;" _PD: EQU $0C ;funt _SD: EQU $0D ;$ _CL: EQU $0E ;: _QM: EQU $0F ;? _OP: EQU $10 ;( _CP: EQU $11 ;) _GT: EQU $12 ;> _LT: EQU $13 ;< _EQ: EQU $14 ;= _PL: EQU $15 ;+ _MI: EQU $16 ;- _AS: EQU $17 ;* _SL: EQU $18 ;/ _SC: EQU $19 ;; _CM: EQU $1A ;, _DT: EQU $1B ;. _NL: EQU $76 ;NEWLINE _0: EQU $1C _1: EQU $1D _2: EQU $1E _3: EQU $1F _4: EQU $20 _5: EQU $21 _6: EQU $22 _7: EQU $23 _8: EQU $24 _9: EQU $25 _A: EQU $26 _B: EQU $27 _C: EQU $28 _D: EQU $29 _E: EQU $2A _F: EQU $2B _G: EQU $2C _H: EQU $2D _I: EQU $2E _J: EQU $2F _K: EQU $30 _L: EQU $31 _M: EQU $32 _N: EQU $33 _O: EQU $34 _P: EQU $35 _Q: EQU $36 _R: EQU $37 _S: EQU $38 _T: EQU $39 _U: EQU $3A _V: EQU $3B _W: EQU $3C _X: EQU $3D _Y: EQU $3E _Z: EQU $3F |
| Program w asemblerze: svars.asm |
; Plik definiuje wszystkie zmienne systemowe ZX81 ; Te zmienne nie są zapisywane do pliku z programem ERR_NR: RB 1 FLAGS: RB 1 ERR_SP: RW 1 RAMTOP: RW 1 MODE: RB 1 PPC: RW 1 ; Te zmienne są zapisywane – start pliku z programem VERSN: DB 0 E_PPC: DW 0 DFILE: DW DFILEA DF_CC: DW DFILEA+1 VARS: DW VARSA DEST: DW 0 E_LINE: DW VARSA+1 CH_ADD: DW LAST-1 X_PTR: DW 0 STKBOT: DW LAST STKEND: DW LAST BREG: DB 0 MEM: DW MEMBOT DB 0 DF_SZ: DB 2 S_TOP: DW 1 LAST_K: DB $FF,$FF,$FF MARGIN: DB 55 NXTLIN: DW LINE1 ;adres wiersza, który zostanie wykonany po załadowaniu programu OLDPPC: DW 0 FLAGX: DB 0 STRLEN: DW 0 T_ADDR: DW $0C8D SEED: DW 0 FRAMES: DW $FFFF COORDS: DB 0,0 PR_CC: DB $BC S_POSN: DB 33,24 CDFLAG: DB 01000000B PRTBUF: DS 33 MEMBOT: DS 32 ; adresy kilku procedur w ROM ZX81 RESET: EQU $0000 SLOWFAST: EQU $0207 SETFAST: EQU $02E7 NEXTLINE: EQU $0676 DECODEKEY: EQU $07BD PRINTAT: EQU $08F5 MAKEROOM: EQU $099E CLS: EQU $0A2A STACK2BC: EQU $0BF5 STACK2A: EQU $0C02 CLASS6: EQU $0D92 FINDINT: EQU $0EA7 FAST: EQU $0F23 SLOW: EQU $0F2B DEBOUNCE: EQU $0F4B SETMIN: EQU $14BC |
| Program w asemblerze: line0.asm |
; Wiersz nr 0. Zawiera instrukcję REM, w której zostaje umieszczony ; nasz kod maszynowy. Parametry wiersza dla języka BASIC są automatycznie ; generowane przez asembler. LINE0: DB 0,0 ; numer wiersza DW LINE1-$-2 ; długość wiersza DB $EA ; kod rozkazu REM |
| Program w asemblerze: myprg.asm |
; Tutaj umieszczasz cały swój program LD HL,(DFILE) INC HL LD (HL),_X RET |
| Program w asemblerze: line1.asm |
; Wiersz nr 1. Zawiera instrukcję RAND USR VAL "16514", która ; uruchamia kod maszynowy DB $76 ; koniec wiersza 0 LINE1: DB 0,1 ; numer wiersza DW DFILEA-$-2 ; długość wiersza DB $F9,$D4,$C5 ; RAND USR VAL DB _QT,_1,_6,_5,_1,_4,_QT ; "16514" DB _NL ; NEWLINE ; Bufor wyświetlania DFILEA: DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL DS 32 | DB _NL ; Zmienne ZX81 BASIC – nie potrzebujesz ich w kodzie maszynowym VARSA: DB $80 ; Koniec obszaru zapisywanego do pliku taśmy P LAST: |
| tniASM v0.44 (C)2000-2005 – The New Image Programował Patriek Lesparre http://tniasm.tni.nl/ e-mail: tniasm@tni.nl |
tniASM jest cross-asemblerem dla mikroprocesorów Z80, R800 i GBZ80. Niektóre z jego cech to:
Po co pisać kolejny asembler? Dobre pytanie! Po pierwsze musiałem się nauczyć języka C, a nie ma lepszego sposobu na naukę nowego języka niż napisanie w nim programu, który jest ci potrzebny. Tutaj dochodzę do drugiego powodu napisania tniASM, który skupia się na tym, iż żaden inny znany mi cross-asembler nie posiadał potrzebnych mi cech.
Dlatego, oraz być może z powodu, iż nigdy wcześniej nie napisałem asemblera/kompilatora, tniASM nie przypomina innych asemblerów. Posiada on wiele dziwactw i szczerze mówiąc może on ci się wydawać nieco pokręcony. Niemniej jednak spełnia on moje wymagania i mam nadzieję, że spełni również twoje.
Większość asemblerów działa dwuprzebiegowo, w pierwszym przebiegu zbierają one informacje na temat etykiet a w drugim zajmują się odwołaniami naprzód i tworzą program wynikowy.
tniASM działa tutaj inaczej. Tworzenie programu wynikowego wykonywane jest w osobnym przebiegu, a przed nim tniASM wykona tyle przebiegów, ile będzie mu potrzebne w celu wyliczenia wartości wszystkich wyrażeń.
Oznacza to, iż tniASM wykona 2 przebiegi PLUS dodatkowy przebieg dla każdego poziomu odwołania naprzód, w sumie do 5 przebiegów.
tniASM ma swoją własną składnię asemblera dla obsługiwanych mikroprocesorów. Nie panikuj jednakże, zmiany są minimalne. Zobacz do rozdziału 2.7 i dalszych, jeśli chcesz poznać odstępstwa od standardowych reguł.
tniASM jest bardzo prosty w użyciu. Wystarczy wpisać
| tniasm nazwa_pliku |
tniASM następnie spróbuje dokonać asemblacji podanego pliku i umieścić wygenerowany kod maszynowy w pliku "tniasm.out".
Jeśli podany zostanie plik bez rozszerzenia i powstanie błąd, to tniASM doda do nazwy rozszerzenie ".asm" i spróbuje ponownie.
Jeśli chcesz używać pliku wyjściowego o innej nazwie niż "tniasm.out", a nie chcesz stosować dyrektywy FNAME, to możesz dodać drugą nazwę pliku w wierszu polecenia, a tniASM użyje go jako pliku wyjściowego.
Jak napisano w rozdziale 1.5, tniASM zwykle umieszcza kod maszynowy w pliku o nazwie "tniasm.out". Możesz to zmienić stosując dyrektywę FNAME (zobacz do rozdziału 2.5.5).
tniASM tworzy również plik z tablicą symboli o nazwie "tniasm.sym", który zawiera wartości wszystkich etykiet w twoim programie. Wpisy posiadają postać:
| "etykieta: EQU wartość" |
zatem można ten plik dołączać bezpośrednio do zewnętrznych plików.
Oprócz tych plików, tniASM tworzy również plik tymczasowy o nazwie "tniasm.tmp", który przeznaczony jest ściśle do wewnętrznego użytku.
Krótko, nie istnieje. Dla tniASM "LabelA" i "labela" oznaczają dokładnie to samo, jak również "ldir" i "LdiR".
W tym punkcie tniASM różni się znacznie od większości asemblerów. Zwykle wiersz źródłowy posiada układ jak poniżej:
| [etykieta:] [instrukcja [operandy]] [;komentarz] |
Co oznacza, iż dozwolona jest każda kombinacja tych trzech pól "etykieta", "instrukcja" oraz "komentarz".
W pewnym sensie tniASM stosuje ten sam układ, lecz pozwala na dowolną liczbę definicji etykiet i instrukcji w pojedynczym wierszu. Jedynie pole komentarza może występować tylko jeden raz. Ten układ najlepiej opisać następująco:
| [ [etykieta:] | [instrukcja [operandy]] ]* [;komentarz] |
Oznacza to, iż tniASM bez żadnego problemu przyjmie do asemblacji taki wiersz:
| start: JP begin exit: LD BC,0042h CALL dos begin: LD DE,text |
Dodatkowo do takiej swobody tniASM nie ogranicza stosowania białych znaków wewnątrz wiersza. Na przykład etykiety mogą być poprzedzane spacjami, a instrukcje mogą rozpoczynać się na samym początku wiersza, co oznacza również, iż instrukcja może być umieszczona tuż za definicją etykiety. To samo odnosi się do komentarzy.
Definicja etykiety musi kończyć się dwukropkiem (:). Długość etykiety jest praktycznie nieograniczona. Poprawnymi znakami w etykiecie są litery, cyfry, '_', '.', '?', '!', '~', '@' i '#'. Pierwszym znakiem etykiety nie może być cyfra.
Można jako etykiety zdefiniować zarezerwowane słowa (takie jak 'pop', 'ld' czy 'hl'), lecz przy zastosowaniu muszą one być poprzedzane znakiem '&'. Możesz zatem zakodować:
| call &pop {...} pop: pop hl pop de pop bc jp [hl] |
tniASM również wspiera etykiety lokalne. Etykieta lokalna jest zawsze lokalna dla poprzedniej nie-lokalnej etykiety w kodzie źródłowym. Etykieta staje się lokalną, jeśli jej pierwszym znakiem jest '.'.
Ten przykład to wyjaśni:
| main: ld b,8 .loop: call doSomething djnz .loop sub: ld b,8 .loop: call doSomething djnz .loop |
W powyższym kodzie są zdefiniowane cztery oddzielne etykiety: 'main', 'main.loop', 'sub' i 'sub.loop'. Z uwagi na to zachowanie możesz również uzyskać dostęp do lokalnych etykiet poza zasięgiem bieżącej nie-lokalnej etykiety. Na przykład tak:
| main: {...} .end: ret sub: {...} jp main.end .end: ret |
Albo inny okrężny sposób – utwórz etykiety lokalne leżące poza zasięgiem bieżącej nie-lokalnej etykiety:
| main: ld a,[.value] {...} sub: {...} main.value: db 0 |
Znak "|" jest używany jako separator wierszy. Można go również
zastosować do uzyskania więcej niż jednej instrukcji w wierszu. W
zasadzie separator ten nie jest potrzebny w większości przypadków,
ponieważ tniASM sam rozpoznaje, iż wiersz
Jako znak komentarza stosowany jest jak zwykle średnik (;). Można go umieścić w dowolnym miejscu wiersza, a wszystko za średnikiem jest ignorowane aż do następnego wiersza.
Oprócz zwykłych komentarzy tniASM również wspiera bloki komentarzy. Początek i koniec bloku komentarza oznaczane są odpowiednio znakami "{" i "}". Można je umieścić w dowolnym miejscu pliku, a wszystko pomiędzy nimi zostanie zignorowane. Bloki komentarzy mogą być zagnieżdżane praktycznie w nieskończoność.
W tniASM mamy trzy różne rodzaje stałych: liczbowe, znakowe i łańcuchowe. Ponieważ tniASM jest asemblerem 32-bitowym, stałe są 32-bitowymi liczbami całkowitymi ze znakiem o zakresie od -2147483648 (80000000h) to 2147483647 (7FFFFFFFh).
Stałe liczbowe można zapisywać jako dziesiętne, szesnastkowe, dwójkowe lub ósemkowe. Wspierane formaty są następujące:
| Dziesiętne: 123, 123d |
Szesnastkowe (nie może rozpoczynać literą, stosuj 0ABCDh, itp.): 1234h, $1234, 0x1234 |
|
Dwójkowe (może zawierać białe znaki, tj. 1110 0100 b): 11100100b, %11100100 ( " " " " " '% 1110 0100') |
|
Ósemkowe: 123o, 123q |
Stałą łańcuchową jest wszystko pomiędzy apostrofami lub cudzysłowami, co zawiera więcej niż 4 znaki. Stosuje się je w rozkazach takich jak DB/DW, INCLUDE i FNAME. Stałych łańcuchowych nie można stosować w zwykłych wyrażeniach.
Przykład:
| DB "łańcuch w 'cudzysłowach' może zawierać apostrofy" DB 'a łańcuch w "apostrofach" może zawierać cudzysłowy' |
Stałe znakowe podlegają takim samym regułom jak stałe łańcuchowe,
lecz można je stosować w wyrażeniach. Mogą mieć długość do 4 znaków i są
przechowywane w kolejności od najmłodszego bajtu. Zatem stała 'A' jest
traktowana jako
Przykład:
| DB 'abcd' ; daje 'a','b','c','d'. DB +"abcd",1+'a' ; daje 'a','b'. Ponieważ oba łańcuchy są ; wewnątrz wyrażenia, traktuje się je jako stałe znakowe. DB 'a'+1 ; daje błąd, ponieważ 'a' nie jest uważane za część ; wyrażenia – patrz powyżej DB ('abcd' >> 8) ; daje 'b' |
Do obliczania wartości wyrażeń wykorzystywana jest arytmetyka 32-bitowych liczb całkowitych ze znakiem. Wyrażenie składa się z jednej lub więcej stałych w kombinacji z operatorami. W wyrażeniach można stosować daw specjalne symbole: $ i $$. Odnoszą się one kolejno do pozycji asemblacji (licznika programu) oraz pozycji w pliku na początku bieżącej instrukcji.
Można zakodować:
| nop nop jr $-2 ; skocz do pierwszej instrukcji nop |
Wszystkie wspierane operatory zostały wymienione poniżej, począwszy od operatorów o najniższym priorytecie. Operatory posiadające ten sam priorytet są wyliczane od strony lewej do prawej.
Priorytet operatorów można zmienić stosując nawiasy "(" i ")".
Operatory porównań są następujące:
| x = y | równe | |
| x <> y x >< y x != y |
różne | |
| x < y | mniejsze niż | |
| x > y | większe niż | |
| x <= y x =< y |
mniejsze lub równe | |
| x >= y x => y |
większe lub równe |
W przeciwieństwie do języka C operatory porównań dają 0 dla fałszu i -1 dla prawdy (zamiast 0 i 1). Dzięki temu nie ma potrzeby stosowania logicznych wersji operatorów AND, OR i XOR, ponieważ działają one w identyczny sposób jak operatory bitowe.
Operatory porównań pozwalają tworzyć złożone wyrażenia, takie jak:
| x*(1+2*(x<0)) |
co daje w wyniku wartość bezwzględną z x.
Są same przez się zrozumiałe. Zamiast znaków "|" i "^" (jak w C) tniASM stosuje słowa kluczowe "OR" i "XOR" (jak w języku BASIC).
| x + y | dodawanie |
| x – y | odejmowanie |
| x OR y | bitowa suma logiczna |
| x XOR y | bitowa różnica symetryczna |
| x ^ y | potęgowanie |
| x * y | mnożenie |
| x / y | dzielenie |
| x MOD y | reszta z dzielenia |
| x << y | przesunięcie bitowe w lewo |
| x >> y | przesunięcie bitowe w prawo (bez znaku) |
| x AND y | bitowy iloczyn logiczny |
| + x | plus jednoargumentowy |
| - x | minus jednoargumentowy |
| NOT x | negacja bitów |
Definiuje (ciąg) bajt(ów) / słowo (słów).
| DB 255 DB "bla",13,10 DW 'AB',4000h DW "łańcuch nieparzysty" ; nieparzyste łańcuchy są uzupełniane 0 |
Definiuje łańcuch, w którym ostatni znak ma ustawiony na 1 7-my bit.
| DC "TOKEN" ; to samo co DB "TOKE","N" OR 128 DC "LIST","STOP" ;definiuje 2 łańcuchy, oba zakończone ;z ustawionym 7 bitem |
Definiuje miejsce (w bajtach).
| DS 10 ; definiuje 10 bajtów o wartości 0 DS 10,255 ; definuje10 bajtów o wartości 255 DS 4000h-$ ; wypełnia zerami aż do adresu 4000h DS 0 ; nic nie robi DS -1 ;to samo odnosi się dla wartości ujemnych |
Przypisuje wartość etykiecie. EQU musi występować za etykietą w tym samym wierszu.
| bankstart: EQU 4000h ORG bankstart ; to samo co org 4000h |
Określa plik wyjściowy. Używaj FNAME w celu skierowania wyjścia tniASM do pliku innego niż "tniasm.out". W pliku źródłowym można wielokrotnie używać FNAME, aby umieścić różne części kodu wynikowego w różnych plikach. FNAME również ustawia FORG na 0.
| FNAME "output.bin" ; teraz plikiem wyjściowym jest "output.bin" {...} |
Zamiast tworzyć nowy plik, można również poinstruować tniASM, aby wyprowadzał dane do istniejącego pliku podając drugi parametr dla FNAME. Ten drugi parametr jest pozycją w pliku, gdzie tniASM ma wstawić kod wyjściowy, a FORG zostanie automatycznie ustawione na tę wartość.
| FNAME "output.bin",1024 ; wyjście rozpoczyna się od pozycji 1024 ; w istniejącym pliku "output.bin" FNAME "output.bin",0 ; wyjście rozpoczyna się od pozycji 0, jak ; zwykle, lecz w istniejącym już pliku "output.bin" |
Ustawia pozycję wyprowadzania kodu w pliku wyjściowym. Używaj FORG do ustalenia określonej pozycji wstawiania w pliku przez tniASM. Jeśli pozycja ta jest większa niż rozmiar pliku, to plik zostanie uzupełniony zerami. Jeśli nie poda się żadnego FORG, to startowa pozycja w pliku wynosi 0.
Dołącza plik binarny. Instrukcja INCBIN wstawia zawartość pliku binarnego w bieżącym miejscu tworzonego kodu maszynowego. Jest szczególnie użyteczna przy osadzaniu grafiki lub dużych tablic, których nie chcesz mieć na olbrzymich listach DB.
| music1: INCBIN "music1.bin" |
INCBIN opcjonalnie przyjmuje dodatkowe 1 lub 2 parametry. Pierwszy jest przesunięciem w pliku, który zostanie wstawiony. Drugi określa całkowitą liczbę bajtów do wstawienia z tego pliku.
| INCBIN "basic.bin",7 ; dołączamy od bajtu nr 7 INCBIN "cutout.bin",1024,512 ; dołączamy 512 bajtów od pozycji 1024 |
Instrukcja INCLUDE wstawia inny plik na bieżącej pozycji pliku źródłowego. Liczba zagnieżdżeń ograniczona jedynie pojemnością pamięci.
| {...} INCLUDE "incthis.asm" {...} |
ORG zezwala na jeden lub dwa argumenty. Pierwszy ustawia pozycję asemblacji (licznik programu) na wybrany adres, natomiast drugi argument określa maksymalny adres dla tej "sekcji". Jeśli adres ten zostanie przekroczony, tniASM wygeneruje ostrzeżenie. Ignorowane są przy tym instrukcje PHASE. Jeśli nie poda się żadnego ORG, to asemblacja rozpocznie się od 0.
| ORG 0c000h ; dalszy kod rozpocznie się od 0c000h ORG 0c000h,0 ; tak samo jak powyżej ORG 4000h,7FFFh ; zaczyna się od 4000h, ostrzega przy ; przekroczeniu 7FFFh |
PHASE "przesuwa" pozycję asemblacji do określonego adresu. Jest to szczególnie użyteczne dla kodu, który zostanie później przeniesiony w inne miejsce pamięci. DEPHASE przywraca normalną pozycję asemblacji. Nowe PHASE lub ORG kasuje efekt każdego poprzedniego PHASE.
| ; w tym przykładzie relokujemy procedurę SetS#0 z jej bieżącego ; addresu do 0C000h. Z powodu PHASE/DEPHASE jej etykieta ;"'SetS#0" już wskazuje na 0C000h. ORG 8000h ld hl,start ld de,SetS#0 ld bc,SetS#0.end-SetS#0.start ldir {...} SetS#0.start: PHASE 0C000h SetS#0: xor a ; set V9938 S#0 out [99h],a ld a,15+128 out [99h],a ret DEPHASE |
Rezerwuje (podaną liczbę) bajt(ów)/słowo(słów) jako dane niezainicjowane. W zasadzie jest to to samo co DS, lecz nie zmienia pozycji w pliku, ani nie wyprowadza niczego. Jedynie uaktualnia pozycję asemblacji. RB i RW są użyteczne przy definiowaniu zmiennych w RAM.
| ORG 0C000h Var1: RB 2 ; Var1 = 0C000h Var2: RW 1 ; Var2 = 0C002h Var3: RB 0 ; Var3 = 0C004h Var4: RW -1 ; Var4 = 0C004h ponieważ 0 i wartości ujemne ; są ignorowane |
Czasami dobrze jest mieć pewien fragment kodu, który kompiluje się przy spełnieniu określonych warunków. Na przykład przy pisaniu kodu przeznaczonego jednocześnie dla różnych platform sprzętowych (na przykład Z80 i R800), lub do wstawiania/usuwania kodu uruchomieniowego.
tniASM zapewnia asemblację warunkową poprzez konstrukcję IF. Jej podstawowa postać jest następująca:
| IF {operand} [{...}] [ELSE [{...}]] ENDIF |
Uwaga: z uwagi na wieloprzebiegową naturę tniASM, w konstrukcjach IF wolno stosować odwołania naprzód. Można je również stosować poprzez granice plików źródłowych. Oczywiście polecenia IF można zagnieżdżać praktycznie dowolnie głęboko.
Wyrażenie zostaje obliczone i jest traktowane jako fałszywe, gdy ma wartość zero, natomiast każda wartość różna od zera jest traktowana jako prawda.
| loop: {...} IF $-loop < 128 djnz loop ELSE dec b jp nz,loop ENDIF |
Sprawdza, czy w bieżącym przebiegu etykieta została poprzednio zadeklarowana.
| R800: ; zakomentuj dla wersji Z80 IFDEF R800 mulub a,b ELSE call mulub_a_b ENDIF IFDEF R800 ELSE mulub_a_b: {...} ret ENDIF |
Sprawdza, czy istnieje plik. Zobacz na drugi przykład, gdzie mamy zgrabną sztuczkę, która działa z każą instrukcją IF.
| IFEXIST "test" {...} ENDIF ; wykonaj {...}. jeśli "test" istnieje IFEXIST "test" ELSE {...} ENDIF ; wykonaj {...}, jeśli "test" nie istnieje |
Podobne do IFDEF, lecz sprawdza, czy etykieta istnieje bez względu gdzie i kiedy jest deklarowana. Możesz użyć tej instrukcji do sprawdzenia, czy etykieta jest deklarowana dalej w kodzie źródłowym.
tniASM może tworzyć kod maszynowy dla kilku mikroprocesorów, mianowicie Z80, R800 i procesora powszechnie znanego jako GBZ80 (GB to konsola Game Boy). Standardowo tniASM pracuje w trybie R800/MSX. Używając instrukcji CPU można przełączać się pomiędzy poniższymi trybami. Można to robić w dowolnym miejscu kodu i dowolnie często.
Tryby noszą nazwy "Z80", "R800" (plus alias "MSX") i "GBZ80".
| CPU Z80 ; przełącza na tryb Z80 CPU R800 ; przełącza na tryb R800 CPU MSX ; ekwiwalent trybu R800 CPU GBZ80 ; przełącza na tryb GBZ80 |
Ten tryb nie akceptuje instrukcji MULUB/MULUW dla R800, lecz poza tym jest taki sam jak tryb R800/MSX.
Różnice w stosunku do zasad standardowej składni Z80 są następujące:
| PUSH AF,BC,DE,HL ; umieszcza na stosie kolejno AF, BC, DE i HL POP HL,BC ; pobiera najpierw HL a później BC |
Akceptowane są wszystkie instrukcje Z80 i R800.
Różnice w stosunku do standardowej składni R800 są następujące:
Akceptowane są jedynie instrukcje GBZ80 i ich rozszerzenia.
Różnice ze standardową składnią GBZ80 są następujące:
Przy pracy z tniASM utrzymuj jeden plik, do którego dołączasz wszystkie inne pliki. Coś w stylu makefile, lecz bez współzależności i plików pośrednich.
| ; "example.asm" ; to przykładowe 'makefile' fname "example.com" org 100h include "frontend.asm" include "main.asm" include "backend.asm" fname "example.dat" org 0 incbin "stuff.dat" include "somesubs.asm" |
![]() |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2026 mgr Jerzy Wałaszek |
Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone pod warunkiem podania źródła oraz niepobierania za to pieniędzy.
Pytania proszę przesyłać na adres email:
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.
Informacje dodatkowe.