Serwis Edukacyjny
Nauczycieli

w I-LO w Tarnowie
obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

Autor artykułu: mgr Jerzy Wałaszek
Współpraca: mgr inż. Janusz Wałaszek

©2026 mgr Jerzy Wałaszek

SPIS TREŚCI

Tworzenie programów w kodzie maszynowym dla ZX81 na PC-cie

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:

  1. Dowolny emulator ZX81, który potrafi odczytywać pliki taśmy o rozszerzeniu P. Proponuję VB81, jest wręcz idealny:
    ściągnij plik instalacyjny VB81
    przeczytaj instrukcję instalacji i używania VB81
  2. Asembler mikroprocesora Z80, który działa w środowisku Windows. Proponuję darmowy tniASM (istnieje również dobry asembler tasm dla mikroprocesora Z80, jednakże ten ma licencję na 30 dni, po upływie których powinieneś zapłacić autorom 40$, aby nie stać się piratem, zatem sam rozumiesz...):
    ściągnij plik z asemblerem tniasm
    przeczytaj instrukcję dla asemblera tniasm
  3. Wygodny edytor dla tworzonych programów w asemblerze Z80. Ostatecznie można stosować Notatnik z Windows, ale po co się męczyć:
    ściągnij plik instalacyjny edytora MicroAsm
  4. Zestaw plików szablonowych asm dla asemblera tniASM, które utworzą odpowiedni projekt pliku taśmy P dla ZX81.
    pobierz plik z szablonami asm

Poniżej wyjaśniam szczegółowo pracę w tym systemie.

Pliki taśmy P w ZX81

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.

Wiersze programu w ZX81 BASIC

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:

  1. Wewnątrz instrukcji REM – instrukcje REM są ignorowane przez BASIC i mogą zawierać dowolną treść.
  2. W obszarze zmiennych. Obszar ten jest zapisywany do pliku P. Jednakże zmienne mogą się przemieszczać w pamięci – np. gdy dodasz jakiś wiersz do programu. Rozkaz RUN kasuje wszystkie zmienne i przez nieuwagę może uszkodzić twój kod.

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"

Pliki szablonów asm

Ś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:

Instrukcja dla asemblera tniasm

tniASM v0.44
(C)2000-2005 – The New Image

Programował Patriek Lesparre

http://tniasm.tni.nl/
e-mail: tniasm@tni.nl

Rozdział 1: Wprowadzenie

1.1 Co to jest?

tniASM jest cross-asemblerem dla mikroprocesorów Z80, R800 i GBZ80. Niektóre z jego cech to:

1.2 Po co?

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.

1.3 tniASM i przebiegi

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.

1.4 Składnia asemblera tniASM

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ł.

1.5 Używanie tniASM

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.

1.5.1  Wyjście tniASM

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.

Rozdział 2: Język tniASM

2.1 Rozróżnianie liter dużych i małych

Krótko, nie istnieje. Dla tniASM "LabelA" i "labela" oznaczają dokładnie to samo, jak również "ldir" i "LdiR".

2.2 Układ wiersza źródłowego tniASM

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.

2.2.1 Więcej na temat etykiet

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
2.2.2 Separator wierszowy

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 "add a,a call jump ret" to właściwie trzy różne instrukcje. Rozdzielanie instrukcji za pomocą "|" sprawia, że całość lepiej wygląda i łatwiej ją zrozumieć. Jednakże w wierszu takim jak "add a,34 and 3 xor 255" tniASM zakłada, iż miałeś na myśli "add a,253" a nie trzy oddzielne instrukcje. Aby mieć pewność, że tniASM wygeneruje dokładnie taki kod, jaki chcesz, stosuj separator wierszy.

2.2.3 Więcej na temat komentarzy

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ść.

2.3. Stałe

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).

2.3.1 Stałe liczbowe

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
2.3.2 Stałe łańcuchowe

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'
2.3.3 Stałe znakowe

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 41h, 'AB' jako 4241h (lub 41h, 42h), 'ABC' jako 434241h (41h,42h,43h) i 'ABCD' jako 44434241h. W DB/DW stałe znakowe są zawsze traktowane jako stałe łańcuchowe, chyba że znajdują się wewnątrz wyrażenia.

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'

2.4 Wyrażenia

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 ")".

2.4.1 Operatory porównań o priorytecie zero

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.

2.4.2 Operatory addytywne i logiczne (X)OR o priorytecie 1

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
2.4.3 Operatory multiplikatywne i logiczne AND o priorytecie 2
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
2.4.4 Operatory jednoargumentowe o priorytecie 3
+ x plus jednoargumentowy
- x minus jednoargumentowy
NOT x negacja bitów

2.5 Pseudo Instrukcje

2.5.1 DB/DW

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
2.5.2 DC

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
2.5.3 SD

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
2.5.4. EQU

Przypisuje wartość etykiecie. EQU musi występować za etykietą w tym samym wierszu.

bankstart: EQU 4000h
ORG bankstart ; to samo co org 4000h
2.5.5 FNAME

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"
2.5.6 FORG

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.

2.5.7 INCBIN

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
2.5.8 INCLUDE

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"
{...}
2.5.9 ORG

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
2.5.10 PHASE/DEPHASE

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
2.5.11 RB/RW

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

2.6 Asemblacja Warunkowa

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.

2.6.1 IF {wyrażenie}

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
2.6.2 IFDEF {etykieta}

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
2.6.3 IFEXIST {łańcuch}

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
2.6.4 IFEXIST {etykieta}

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.

2.7 Wspieranie wielu mikroprocesorów

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
2.7.1 Tryb Z80

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
2.7.2 Tryb R800 i MSX

Akceptowane są wszystkie instrukcje Z80 i R800.

Różnice w stosunku do standardowej składni R800 są następujące:

2.7.3 Tryb GBZ80

Akceptowane są jedynie instrukcje GBZ80 i ich rozszerzenia.

Różnice ze standardową składnią GBZ80 są następujące:

Rozdział 3: To co pozostało

3.1 Porady

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"

3.2 Oświadczenie


do podrozdziału  do strony 

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: i-lo@eduinf.waw.pl
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.

Informacje dodatkowe.