ZXSRC
Z80 ZX-Spectrum
Sources Rough Collection
in progress...
Небольшой Блокнотик по программированию
Z80 на компьютере ZX-SPECTRUM,
и не только...
Математические операции
ADDITION
;a+hl unsigned slower
;4 bytes and 22 clock cycles
ld b,0 ;ld d,0 ;
ld c,a ;e,a ;
add hl,bc ;add hl,de ;
;a+hl faster unsigned from plutiedev
;5 bytes and 20 clock cycles
;but no other 16-bit register messed up
add a, l ; A = A+L
ld l, a ; L = A+L
adc a, h ; A = A+L+H+carry
sub l ; A = H+carry
ld h, a ; H = H+carry
;a+hl fastest addAtoHL:
;5 bytes, 19/20 clock cycles
add a,l
ld l,a
jr nc, $+3
inc h
a+hl ADD A, L
LD L, A
RET NC
INC H
RET
;a+hl signed from plutiedev
; If A is negative, we need to substract $100 from HL
; (since A's "upper byte" is $FF00)
or a
jp p, Positive
dec h
Positive: ; Then do addition as usual; (to handle the "lower byte")
add a, l
ld l, a
adc a, h
sub l
ld h, a
;(hl)=(hl)+a
add a,(hl)
ld (hl),a
;signed add
ld e, a ; DE = A
add a, a
sbc a
ld d, a
add hl, de ; HL = HL+DE
ADD 16bits ;andreadrian.de
LD HL,#1234 ; LOAD DECIMAL VALUE 12345 INTO REGISTER HL
LD DE,#5 ; LOAD DECIMAL VALUE 7 INTO REGISTER DE
; ADD ROUTINE 16+16BIT=16BIT
; HL = HL + DE
ADD16 ADD HL,DE ; 16-BIT ADD OF HL AND DE
;RESULT IS IN HL
RET ;RESULT IS IN HL
ADD 32bits ;andreadrian.de
LD HL,#1213 ; LOAD HEXADECIMAL 1213 TO HL (LOWER 16-BIT)
LD DE,#F000 ; LOAD HEXADECIMAL F000 TO DE (LOWER 16-BIT)
EXX
LD HL,#1011 ; LOAD HEXADECIMAL 1011 TO H'L' (UPPER 16-BIT)
LD DE,#0 ; LOAD VALUE 0 TO D'E' (UPPER 16-BIT)
EXX
; H'L'HL = H'L'HL + D'E'DE
ADD32 ADD HL,DE ; 16-BIT ADD OF HL AND DE
EXX
ADC HL,DE ; 16-BIT ADD OF HL AND DE WITH CARRY
EXX
RET
Kot Ltd, Кировск, 1996
Эта процедура приращения регистровой пары HL на величину, заданную в
регистре А, которая находится в пределах #00 - #FF.
LD A, приращение Это быстрее и короче,
ADD A,L чем:
LD L,A PUSH BC
ADC A,H LD BC, #
SUB L ADD HL, BC
LD H,A POP BC
SUBSTRACTION
;a-hl unsigned from plutiedev
; If A=0 do nothing Otherwise flip A's sign.
Since the upper byte becomes -1, also
; substract 1 from H.
neg
jp z, Skip
dec h
; Now add the low byte as usual
; Two's complement takes care of
; ensuring the result is correct
add a, l
ld l, a
adc a, h
sub l
ld h, a
Skip:
unsigned hl-de
ld e, a ; DE = A
ld d, 0
or a ; Clear carry
sbc hl, de ; HL = HL-DE
signed hl-de
ld e, a ; DE = A
add a, a
sbc a
ld d, a
or a ; Clear carry
sbc hl, de ; HL = HL-DE
; Compute (HL)=(DE)-(HL)
SUBHL LD A,E ;compute difference.
SUB L
LD L,A ;store low byte.
LD A,D
SBC A,H
LD H,A ;and then high byte.
RET
;via calc84maniac "Optimized routine for HL=A-HL
(the negate HL optimization can be derived
from this by setting A=0 first)
sub l
ld l,a
sbc a,a
sub h
ld h,a
;written by calc84maniac
;comment from calc84maniac:
; To clarify why I did a cpl/scf/adc instead
of a cpl/inc/add or neg/add,
; is that it handles the case of A=0 properly.
Typically, SUB N and
; ADD A,-N give opposite carry outputs,
but SUB 0 and ADD A,-0 both reset the
; carry flag. On the other hand, SCF \ ADC A,255
will set the carry flag like
; we want it to.
;NOTE: This is an in-line routine-- there is no RET
cpl
scf
adc a,c
ld c,a
jr c,$+3
dec b
; Here is a callable routine:
cpl
scf
adc a,c
ld c,a
ret c
dec b
ret
MULTIPLICATION
BC*2,4,8....
Умножить регистровую пару на 2 (4,8...) можно так:
AND A
RL C
RL B .
DE*2,4,8....
sla e
rl d
А если вам надо перемножить регистр HL,то
это лучше сделать командой сложения ADD :
ADD HL,HL умножить на два
ADD HL,HL на четыре
ADD HL,HL на восемь
........... и т.д.
Например:
HLx32 add hl,hl
HLx16 add hl,hl
HLx8 add hl,hl
HLx4 add hl,hl
HLx2 add hl,hl
ret
;B*2,4,8...
ld b,3 ; Multiply 3 with 4
sla b ; *4
sla b ; result: B = 12
;A*2,4,8...
ld a,15 ; Multiply 15 with 8
add a,a ; *8
add a,a
add a,a ; result: A =120
;A*20
ld a,5 ; Multiply 5 with 20 (= A * 16 + A * 4)
add a,a ; *16
add a,a
ld b,a ; Store value of A * 4 in B
add a,a
add a,a
add a,b ; Add A * 4 to A * 16, result: A = 100
A*15
ld a,3 ; Multiply 3 with 15 (= A * 16 - A * 1)
ld b,a ; Store value of A * 1 in B
add a,a ; *16
add a,a
add a,a
add a,a
sub b ; result: A = 45
HL = HL*A
;5.MHLA - умножение
;INPUT : HL <-- что A <-- на что
;OUTPUT: HL = HL*A;портятся DE,HL,A
;FASTED bY CREATOR
MHLA AND A
JR Z,M1H
EX DE,HL
LD HL,0
M2H SRL A
JP NC,M3H
ADD HL,DE
M3H SLA E
RL D
AND A
JP NZ,M2H
RET
M1H LD H,A
LD L,A
RET
;a*12=hl a*3=hl a*12=hl (fast)
; Although this specific case
could be even better as follows:
ld l,a
add a,a ; a*2
add a,l ; a*3
ld h,0
ld l,a ; hl=a*3
add hl,hl ; hl=a*6
add hl,hl ; hl=a*12
; 8 bytes, 45 clocks
;HL*10
Mult10HL
add hl,hl
push de
push hl
pop de
add hl,hl
add hl,hl
add hl,de
pop de
;HL=DE*A
MULT LD HL,0
EX DE,HL
DUP 8
RRA
JR NC,$+5
EX DE,HL
ADD HL,DE
EX DE,HL
ADD HL,HL
EDUP
EX DE,HL
RET
; Multiply 8-bit values H*E ~Grauw
; In: Multiply H with E
Out: HL = result
Mult8 ld d,0
ld l,d
ld b,8
Mult8_Loop add hl,hl
jr nc,Mult8_NoAdd
add hl,de
Mult8_NoAdd djnz Mult8_Loop
ret
; Multiply 8-bit value with a 16-bit value ~Grauw
; In: Multiply A*DE
Out: HL = result
Mult12:
ld l,0
ld b,8
Mult12_Loop:
add hl,hl
add a,a
jr nc,Mult12_NoAdd
add hl,de
Mult12_NoAdd:
djnz Mult12_Loop
ret
; Multiply 16-bit values (with 16-bit result) ~Grauw
; In: Multiply BC*DE
Out: HL = result
Mult16:
ld a,b
ld b,16
Mult16_Loop:
add hl,hl
sla c
rla
jr nc,Mult16_NoAdd
add hl,de
Mult16_NoAdd:
djnz Mult16_Loop
ret
; Multiply 16-bit values (with 32-bit result)
; ~Grauw
; In: BC*DE
; Out: BCHL = result
Mult32:
ld a,c
ld c,b
ld hl,0
ld b,16
Mult32_Loop:
add hl,hl
rla
rl c
jr nc,Mult32_NoAdd
add hl,de
adc a,0
jp nc,Mult32_NoAdd
inc c
Mult32_NoAdd:
djnz Mult32_Loop
ld b,c
ld c,a
ret
; Multiply 8-bit value with a 16-bit
value (right rotating) ~Grauw
; In: Multiply A*DE. Put lowest value
in A for most efficient calculation
; Out: HL = result
Mult12R:
ld hl,0
Mult12R_Loop:
srl a
jr nc,Mult12R_NoAdd
add hl,de
Mult12R_NoAdd:
sla e
rl d
or a
jp nz,Mult12R_Loop
ret
; Multiply 8-bit value with a
;16-bit value (unrolled) ~Grauw
; In: Multiply A*DE Out: HL = result
Mult12U:
ld l,0
add a,a
jr nc,Mult12U_NoAdd0
add hl,de
Mult12U_NoAdd0:
add hl,hl
add a,a
jr nc,Mult12U_NoAdd1
add hl,de
Mult12U_NoAdd1:
add hl,hl
add a,a
jr nc,Mult12U_NoAdd2
add hl,de
Mult12U_NoAdd2:
add hl,hl
add a,a
jr nc,Mult12U_NoAdd3
add hl,de
Mult12U_NoAdd3:
add hl,hl
add a,a
jr nc,Mult12U_NoAdd4
add hl,de
Mult12U_NoAdd4:
add hl,hl
add a,a
jr nc,Mult12U_NoAdd5
add hl,de
Mult12U_NoAdd5:
add hl,hl
add a,a
jr nc,Mult12U_NoAdd6
add hl,de
Mult12U_NoAdd6:
add hl,hl
add a,a
ret nc
add hl,de
ret
;HL*6 Умножение HL на шесть :
ADD HL,HL
LD B,H
LD C,L
ADD HL,HL
ADD HL,BC
;HL*40 умножение HL на сорок :
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD B,H
LD C,L
ADD HL,HL
ADD HL,HL
ADD HL,BC
;by Axor
;HL=HL*128, 35 тактов
LD A,L
RRC H
RRA
LD H,A
LD L,0
RR L
;MUL: DE=DE*BC
MUL EX DE,HL
LD DE,0
LD A,16
MUL1 RR B
RR C
JR NC,MUL2
EX DE,HL
ADD HL,DE
EX DE,HL
MUL2 ADD HL,HL
DEC A
JR NZ,MUL1
RET
Classic 8-bit * 8-bit Unsigned
;Input:
H = Multiplier,
E = Multiplicand,
L = 0, D = 0
;Output: HL = Product
sla h ; optimised 1st iteration
jr nc,$+3
ld l,e
add hl,hl ; unroll 7 times
jr nc,$+3 ; ...
add hl,de ; ...
Classic 16-bit * 8-bit Unsigned
;Input:
A = Multiplier,
DE = Multiplicand,
HL = 0, C = 0
;Output: A:HL = Product
add a,a ; optimised 1st iteration
jr nc,$+4
ld h,d
ld l,e
add hl,hl ; unroll 7 times
rla ; ...
jr nc,$+4 ; ...
add hl,de ; ...
adc a,c ; ...
Classic 16-bit * 16-bit Unsigned
;Input:
DE = Multiplier,
BC = Multiplicand,
HL = 0
;Output: DE:HL = Product
sla e ; optimised 1st iteration
rl d
jr nc,$+4
ld h,b
ld l,c
add hl,hl ; unroll 15 times
rl e ; ...
rl d ; ...
jr nc,$+6 ; ...
add hl,bc ; ...
jr nc,$+3 ; ...
inc de ; ...
;MULTIPLY D*E=DE;
;0<=result<65535
LD DE,#FEFF
CALL MULT
RET
MULT XOR A
LD B,8
MU1 RRA
RR E
JR NC,MU0
ADD A,D
MU0 DJNZ MU1
RRA
RR E
LD D,A
RET
Предлагаю вариант процедуры,
который имеет большую длину,
но работает быстрее при умножении
больших чисел (B>15) и не имеет
синдрома нулевой ошибки. Числа в
комментариях означают:
числитель - длина (байт),
знаменатель - время (тактов).
LD L,C; 1/4
LD D,B; 1/4
LD H,0; 2/7
LD E,0; 2/7
LD B,8; 2/7
; ---------
; 8/29
;-----------------------------
LOOP LD A,1; 2/7
AND L; 1/4
JR Z,PASS; 2/7
ADD HL,DE; 1/11
PASS RR H; 2/8
RR L; 2/8
DJNZ LOOP; 2/13+8
; ----------
; 12/472
; или (58*8+8)
;------------------------------
RET ; 1/10
; ИТОГО: 21/511
При значении регистра B от 1 до 15
процедура умножения способом многократного
сложения (ZX96/4-5, стр.69) выполняется быстрее.
; Процедура знакового умножения D*E=HL
; На входе: DE=множители, BIT 7,D(E) знаковый
; На выходе: HL=результат, BIT 7,H знаковый
ORG #C000
MULTI LD A,D
XOR E
AND #80
EX AF,AF'
RES 7,D
RES 7,E
LD L,D
LD B,8
XOR A
RR L
L1 JR NC,$+3
ADD A,E
RRA
RRL
DJNZ L1
LD H,A
EX AF,AF'
OR H
LD H,A
RET
;from Neos
;chl=chl*b;length is 21 bytes
;maximal time is about 561 tact
_mul24_8 push ix
ex de,hl
xor a
ld l,a
ld h,a
ld xh,8
mul248_ add hl,hl
rla
rl b
jr nc,$+4
add hl,de
adc a,c
dec xh
jr nz,mul248_
ld c,a
pop ix
ret
;from Neos
; de*a=hl
FMULT ld hl,0
ld b,8
add hl,hl
rlca
jr nc,$+3
add hl,de
djnz FMULT+5
ret
; байт*слово=слово
; Вход - А-число 1, DE-число 2.
; Выход - HL-результат.
LD HL,0
DUP 8
ADD HL,HL
ADD A,A
JR NC,$+3
ADD HL,DE
EDUP
; слово*слово=слово
; Вход - BC-число 1,DE-число 2.
; Выход - HL-результат.
LD HL,0
LD A,B
DUP 8
ADD HL,HL
ADD A,A
JR NC,$+3
ADD HL,DE
EDUP
LD A,C
DUP 8
ADD HL,HL
ADD A,A
JR NC,$+3
ADD HL,DE
EDUP
; слово*слово=двойное слово
; Вход -
BC-число 1,DE-число 2
; Выход -
HL-младшая часть слова,
HL'-старшая часть слова.
LD HL,0
EXX
LD HL,0
LD DE,0
EXX
LD A,B
DUP 8
ADD HL,HL
EXX
ADC HL,HL
EXX
ADD A,A
JR NC,$+6
ADD HL,DE
EXX
ADC HL,DE
EDUP
LD A,C
DUP 8
ADD HL,HL
EXX
ADC HL,HL
EXX
ADD A,A
JR NC,$+6
ADD HL,DE
EXX
ADC HL,DE
EXX
EDUP
; двойное слово*двойное слово=двойное слово
; Вход - DE' DE-число 1, BC' BC-число 2
; Выход - HL' HL-результат
LD HL,0
EXX
LD HL,0
EXX
DUP 16
ADD HL,HL
EXX
ADC HL,HL
EX DE,HL
ADD HL,HL
EX DE,HL
EXX
JR NC,$+6
ADD HL,BC
EXX
ADC HL,BC
EXX
EDUP
DUP 16
ADD HL,HL
EXX
ADC HL,HL
EXX
EX DE,HL
ADD HL,HL
EX DE,HL
JR NC,$+6
ADD HL,BC
EXX
ADC HL,BC
EDUP
;andreadrian.de
LD DE,#01213
LD BC,#3
EXX
LD DE,#1011
LD BC,#0
EXX
; MULTIPLY ROUTINE 32*32BIT=32BIT
; H'L'HL = B'C'BC * D'E'DE
; NEEDS REGISTER A, CHANGES FLAGS
MUL32
AND A ; RESET CARRY FLAG
SBC HL,HL ; LOWER RESULT = 0
EXX
SBC HL,HL ; HIGHER RESULT = 0
LD A,B ; MPR IS AC'BC
LD B,32 ; INITIALIZE LOOP COUNTER
MUL32LOOP
SRA A ; RIGHT SHIFT MPR
RR C
EXX
RR B
RR C ; LOWEST BIT INTO CARRY
JR NC,MUL32NOADD
ADD HL,DE ; RESULT += MPD
EXX
ADC HL,DE
EXX
MUL32NOADD
SLA E ; LEFT SHIFT MPD
RL D
EXX
RL E
RL D
DJNZ MUL32LOOP
EXX
; RESULT IN H'L'HL
RET
ПРОЦЕДУРА УМНОЖЕНИЯ HL=B*C.
(ZX РЕВЮ 96/4-5, стр.69).
Предлагаю вариант процедуры,
который имеет большую длину, но
работает быстрее при умножении
больших чисел (B>15) и не имеет
синдрома нулевой ошибки.
Числа в комментариях озна-
чают: числитель - длина (байт),
знаменатель - время (тактов).
LD L,C; 1/4
LD D,B; 1/4
LD H,0; 2/7
LD E,0; 2/7
LD B,8; 2/7
; ---------
; 8/29
;-----------------------------
LOOP LD A,1; 2/7
AND L; 1/4
JR Z,PASS; 2/7
ADD HL,DE; 1/11
PASS RR H; 2/8
RR L; 2/8
DJNZ LOOP; 2/13+8
; ----------
; 12/472
; или (58*8+8)
;------------------------------
RET ; 1/10
; ==============================
; ИТОГО: 21/511
При значении регистра B от 1
до 15 процедура умножения спосо-
бом многократного сложения (ZX
96/4-5, стр.69) выполняется быс-
трее.
Алгоритм умножения способом
сложений со сдвигами подробно
описан в книге Р.Токхейм "Осно-
вы цифровой электроники", МИР,
1988г., стр. 238-240.
Практическое применение про-
цедры умножения могут найти,
например, при расчете адреса на-
чала строки двумерного массива.
(С) Жильцов Алексей,
Углич, 1995
Хочу предложить несколько своих программ в раздел "Этюды". Это короткие
процедуры вычисления произведения HL=B*C.
Mul_1 LD L,B
LD B,8
XOR A
RR L
L1 JR NC,L2
ADD A,C
L2 RRA
RR L
DJNZ L1
LD H,A
RET
Длина 16 байт, максимальная длительность выполнения Тм=328 м.т.
Mul_2 LD L,B
LD B,9
XOR A
L1 RR L
DEC B
RET Z
JR NC,L2
ADD A,C
L2 RRA
LD H,A
JR L1
Длина 15 байт, Тм=430 м.т. Короче предыдущей процедуры на 1 байт, зато медлен-
нее на 25%.
Mul_3 LD L,0
LD H,B
LD B,L
LD A,8
L1 ADD HL,HL
JR NC,L2
ADD HL,BC
L2 DEC A
JR NZ,L1
RET
Длина 14 байт, Тм=387 м.т. Реализован другой алгоритм по сравнению с Mul_1 и
Mul_2.
И наконец, самая короткая процедура, занимающая всего 11 байт:
Mul_4 XOR A
SBC HL,HL
LD D,A
OR B
RET Z
LD E,C
L1 ADD HL,DE
DJNZ L1
RET
Длина 11 байт, Тм=6161 м.т.
Очевидно, что последнюю процедуру не стоит использовать для активных мате-
матических вычислений вследствие ее низкого быстродействия. В некоторых же
случаях (при гарантии, что B <= 8 - 10) скорость процедуры сравнима с тремя
другими. При пониженных требованиях к быстродействию, эта процедура даже пред-
почтительней других по причине своей крайней примитивной простоты.
(С) Васильев Антон
(EARTH SOFTWARE)
HL= B*C
LD E,C
LD HL,0
LD D,0
L1 ADD HL,DE
DJNZ L1
RET
DIVISION
;деление А на 10 , C - результат by koshi?
Div10 LD BC,#0AFF
Div INC C
SUB B
RET C
JP Div
BC/2,4,8....
Деление регистровoй пары на 2, 4, 8 и т.д.
AND A
RR B
RR C
DE/2,4,8
SRL D
RR E
;div A/8,4,2... ????????????
ld a,153 ; Divide 153 by 8
and a,%11111000 ; Clear bits 0-2 (equals 256 - 8)
rrca ; /8
rrca
rrca ; result: a = 19
;hl/32 sam style ;ограничено max: #1fe0/#20
HLdiv32
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD A,H
RET
;HL/A by Andrew Strikes Code
;Вход: HL - делимое, A - делитель
;Выход: HL - частное, A - остаток
DIV_21 NEG
LD C,A
LD B,#11
XOR A
DIV210 ADC A,A
ADD A,C
JR C,DIV211
SUB C
OR A
DIV211 ADC HL,HL
DJNZ DIV210
RET
BC/10 (in BC=-7fff)
bc_div10 ld a, c
sra b
rra
sra b
rra
sra b
rra
sra b
rra
ld c, a
ret
;by b2m
DIV32 ld a,10h ; DE = HLDE/BC, HL = HLDE%BC
DIV321 ex de,hl
add hl,hl
ex de,hl
adс hl,hl
jr c DIV322
push hl
sbс hl,bc
jr nc, DIV323
pop hl
jr DIV325
DIV322 ccf
sbс hl,bc
jr DIV324
DIV323 inc sp
inc sp
DIV324 inc de
DIV325 dec a
jr nz, DIV321
ret
;L=C/B, A=REST
DIVIS XOR A
DIVIS2 LD L,1
D1 RL C
RLA
CP B
JR C,ZER
SUB B
SLI L
JR NC,D1
RET
ZER SLA L
JR NC,D1
RET
;DIV: BC=DE=DE/HL
;from Perfect Commander
DIV LD BC,0
LD A,1
DIV1 BIT 7,H
JR NZ,DIV2
ADD HL,HL
INC A
JR DIV1
DIV2 EX DE,HL
DIV4 OR A
SBC HL,DE
CCF
JR C,DIV3
ADD HL,DE
OR A
DIV RL C
RL B
RR D
RR E
DEC A
JR NZ,DIV4
LD E,C
LD D,B
RET
;DE/BC=DE,HL
;на входе: de-делимое; bc-делитель
;на выходе: de-частное hl-остаток
;by Virtual/bw
divide ld a,b ;меняем
cpl ;
ld b,a ;знак
ld a,c ;
cpl ;у
ld c,a ;
inc bc ;делителя t=30
ld hl,0 ;обнулили новое делимое
ld a,d ;сначала двигаем
rla ;старший байт делимого ;t=18
rl l ;┐
add hl,bc ;│
jr c,$+4 ;│8 раз
sbc hl,bc ;│
rla ;┘ t=8*45=360
ld d,a ;ст. байт результата
ld a,e ;теперь двигаем
rla ;младший байт t=12
adc hl,hl ;┐
add hl,bc ;│
jr c,$+4 ;│8 раз
sbc hl,bc ;│
rla ;┘ t=8*52=416
ld e,a ;мл. байт результата ;hl-остаток от деления ;t=4
;итого: 30+18+360+12+416+4=840 тактов
;если остаток не нужен,
то конец процедуры должен выглядеть так:
adc hl,hl ;┐
add hl,bc ;│
jr c,$+4 ;│7 раз
sbc hl,bc ;│
rla ;┘ t=7*52=364
adc hl,hl ;
add hl,bc ;
rla ;
ld e,a ; t=34
;итого: 30+18+360+12+364+34=818 тактов
;p.s. если будете делить на ноль, то,
как и следовало ожидать, частное будет
;равно нулю, а остаток - делимому, т.е.
в этом плане у процедуры глюков нет
; Divide 8-bit values ~Grauw
; In: Divide E by divider C
Out: A = result, B = rest
Div8:
xor a
ld b,8
Div8_Loop:
rl e
rla
sub c
jr nc,Div8_NoAdd
add a,c
Div8_NoAdd:
djnz Div8_Loop
ld b,a
ld a,e
rla
cpl
ret
; Divide 16-bit values (with 16-bit result)
;~Grauw
; In: Divide BC by divider DE
;Out: BC = result, HL = rest
Div16:
ld hl,0
ld a,b
ld b,8
Div16_Loop1:
rla
adc hl,hl
sbc hl,de
jr nc,Div16_NoAdd1
add hl,de
Div16_NoAdd1:
djnz Div16_Loop1
rla
cpl
ld b,a
ld a,c
ld c,b
ld b,8
Div16_Loop2:
rla
adc hl,hl
sbc hl,de
jr nc,Div16_NoAdd2
add hl,de
Div16_NoAdd2:
djnz Div16_Loop2
rla
cpl
ld b,c
ld c,a
ret
;Thanks to Flyguille for the Div16 routine,
taken from his MNBIOS source code.
;Classic 8-bit / 8-bit Unsigned
;Input: D = Dividend, E = Divisor, A = 0
;Output: D = Quotient, A = Remainder
sla d ; unroll 8 times
rla ; ...
cp e ; ...
jr c,$+4 ; ...
sub e ; ...
inc d ; ...
;Input: D = Dividend, E = Divisor, A = 0, Carry = 0
;Output: A = Quotient, E = Remainder
rl d ; unroll 8 times
rla ; ...
sub e ; ...
jr nc,$+3 ; ...
add a,e ; ...
ld e,a ; save remainder
ld a,d ; complement the result
cpl
Classic 16-bit / 8-bit Unsigned
;Input: HL = Dividend, C = Divisor, A = 0
;Output: HL = Quotient, A = Remainder (see note)
add hl,hl ; unroll 16 times
rla ; ...
cp c ; ...
jr c,$+4 ; ...
sub c ; ...
inc l ; ...
Classic 16-bit / 16-bit Unsigned
;Input: A:C = Dividend, DE = Divisor, HL = 0
;Output: A:C = Quotient, HL = Remainder
slia c ; unroll 16 times
rla ; ...
adc hl,hl ; ...
sbc hl,de ; ...
jr nc,$+4 ; ...
add hl,de ; ...
dec c ; ...
Classic 24-bit / 8-bit Unsigned
;Input: E:HL = Dividend, D = Divisor, A = 0
;Output: E:HL = Quotient, A = Remainder
add hl,hl ; unroll 24 times
rl e ; ...
rla ; ...
cp d ; ...
jr c,$+4 ; ...
sub d ; ...
inc l ; ...
Classic 24-bit / 16-bit Unsigned
;Input: A:BC = Dividend, DE = Divisor, HL = 0
;Output: A:BC = Quotient, HL = Remainder
slia c ; unroll 24 times
rl b ; ...
rla ; ...
adc hl,hl ; ...
sbc hl,de ; ...
jr nc,$+4 ; ...
add hl,de ; ...
dec c ; ...
Classic 32-bit / 8-bit Unsigned
;Input: DE:HL = Dividend, C = Divisor, A = 0
;Output: DE:HL = Quotient, A = Remainder
add hl,hl ; unroll 32 times
rl e ; ...
rl d ; ...
rla ; ...
cp c ; ...
jr c,$+4 ; ...
sub c ; ...
inc l ; ...
В DE - делимое, в C - делитель,
результат в HL, остаток в A.
;DIVIDE DE/C=HL
LD DE,768
LD C,3
CALL DIV
RET
DIV LD B,16
LD HL,0
LD A,H
DV1 RL E
RL D
RLA
SUB C
JR NC,DV2
ADD A,C
DV2 CCF
RL L
RL H
DJNZ DV1
RET
Деление регистровoй пары
на /2, /4, /8 и т.д.
AND A
RR B
RR C
При повторе данного примера N раз
вы разделите число на 2 в степени N.
Таким методом возможно разделить и
отдельные регистры.
;A/6
деление A на шесть /6:
LD A,делимое
LD B,#00
LABEL SUB #06 (делитель)
RET C
INC B
JR LABEL
на выходе этой процедуры частное
будет находится в регистре B
(не забудте, что таким методом можно
выполнить только целочисленное деление!).
;BC/DE Divides BC by DE.
Gives result in BC, remainder in HL by hll
udiv16 ld a,d
or e
ret z ;Return NC if dividing by 0
ld hl,0
ld a,16
udiv1 scf
rl c
rl b
adc hl,hl
sbc hl,de
jr nc,udiv2
add hl,de
dec c
udiv2 dec a
jr nz,udiv1
scf
ret
C_Div_D:
;Inputs:
C is the numerator
D is the denominator
;Outputs:
A is the remainder
B is 0
C is the result of C/D
D,E,H,L are not changed
ld b,8
xor a
C_Div_D_loop:
sla c
rla
cp d
jr c,+_
inc c
sub d
_:
djnz C_Div_D_loop
ret
BC_Div_DE:
BC/DE ==> BC, remainder in HL
;min: 1166cc
;max: 1310cc
;avg: 1238cc
;22 bytes
ld hl,0
ld a,b
ld b,16
div_loop:
;shift the bits from BC into HL
sla c \ rla
adc hl,hl
sbc hl,de
jr nc,div_inc_acc
add hl,de
.db $FE ;this begins the instruction
;'cp *', so it eats the next byte.
div_inc_acc:
inc c
div_loop_done:
djnz div_loop
ld b,a
ret
;from action by vav
Вход:de-число1 (2 байта)/bc-65536-число2 (2 байта)
Выход:a-остаток от деления:de=число1/число2
DELENIE ld hl,0
ld a,e
add a,a
rl d
adc hl,hl
add hl,bc
jr c,NABA57
sbc hl,bc
NABA57 rla
rl d
NABA58 adc hl,hl
add hl,bc
jr c,NABA60
NABA59 sbc hl,bc
NABA60 rla
rl d
NABA61 adc hl,hl
add hl,bc
jr c,NABA62
sbc hl,bc
NABA62 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA63
sbc hl,bc
NABA63 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA64
sbc hl,bc
NABA64 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA65
sbc hl,bc
NABA65 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA66
sbc hl,bc
NABA66 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA67
sbc hl,bc
NABA67 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA68
sbc hl,bc
NABA68 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA69
sbc hl,bc
NABA69 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA70
sbc hl,bc
NABA70 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA71
sbc hl,bc
NABA71 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA72
sbc hl,bc
NABA72 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA73
sbc hl,bc
NABA73 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA74
sbc hl,bc
NABA74 rla
rl d
adc hl,hl
add hl,bc
jr c,NABA75
sbc hl,bc
NABA75 rla
rl d
ld e,a
ret
;from Neos
;chl=chl/d ;a-remainder ;length is 22 bytes
;maximal time is about 1200 tacts
_div24_8 xor a
sub d
ld d,a
xor a
ld b,24
div248_ adc hl,hl
rl c
rla
add a,d
jr c,$+3
sub d
djnz div248_
adc hl,hl
rl c
ret
;from Neos
; hl/d=a hl\d=l
FDIV xor a
ld e,a
ld b,8
fdiv1 rr d
rr e
sbc hl,de
jr nc,$+3
add hl,de
rla
djnz fdiv1
cpl
ret
;bc/de=bc,hl
;Divides BC by DE.
;Gives result in BC, remainder in HL.
udiv16 ld a,d
or e
ret z ;Return NC if dividing by 0
ld hl,0
ld a,16
udiv1 scf
rl c
rl b
adc hl,hl
sbc hl,de
jr nc,udiv2
add hl,de
dec c
udiv2 dec a
jr nz,udiv1
scf
ret
; A,HL=(A,HL)/(B,DE)
DIVISIO LD C,A
XOR A
EXX
LD HL,1
LD B,H
EXX
DIV1 SLA L
RL H
RL C
RLA
CP B
JR C,DIV2
JR NZ,DIV3
EX AF,AF'
LD A,C
CP D
JR C,DIV22
JR NZ,DIV32
LD A,H
CP E
JR C,DIV22
DIV32 EX AF,AF'
DIV3 EX AF,AF'
LD A,L
LD L,H
LD H,C
AND A
SBC HL,DE
LD C,H
LD H,L
LD L,A
JR NC,DIV33
EX AF,AF'
SUB B
DEC A
DIV34 EXX
SLI L
RL H
RL B
EXX
JP NC,DIV1
JP DIVEXIT
DIV22 EX AF,AF'
DIV2 EXX
SLA L
RL H
RL B
EXX
JP NC,DIV1
DIVEXIT EXX
PUSH HL
LD A,B
EXX
POP HL
RET
DIV33 EX AF,AF'
SUB B
JP DIV34
;HL = HL/DE
;1.DIV - деление
;INPUT : HL <-- что DE <-- на что
;OUTPUT: HL = HL/DE ;портятся DE,HL,A
DIV LD A,D
OR E
RET Z
PUSH DE
PUSH BC
LD A,1
DIV_0 PUSH HL
SBC HL,DE
JP C,HL0
SBC HL,DE
JP C,DIV_1
DIV_01 INC A
SLA E
RL D
POP HL
JP DIV_0
DIV_1 POP HL
LD BC,0
DIV_2 AND A
JP NZ,DIV_3
LD H,B
LD L,C
POP BC
POP DE
RET
DIV_3 SBC HL,DE
JP NC,DIV_4
ADD HL,DE
DIV_4 CCF
RL C
RL B
SRL D
RR E
DEC A
JP DIV_2
HL0 CP 1
JP NZ,DIV_01
POP HL
POP BC
POP DE
LD HL,0
RET
SIN, COS
;by SerzhSoft;
;вычисление синуса HL
;A=sin(HL)*255, CF=sign (NC=[+], C=[-])
SIN_HL
dec h
jr z,GOSIN2
inc h
jr nz,SIN360
ld h,SINTAB/256
ld a,l
cp 180
jr nc,GOSIN1
ld a,(hl)
and a
ret
GOSIN1 sub 180
ld l,a
ld a,(hl)
scf
ret
GOSIN2 ld a,l
cp 104
jr nc,GOSIN3
ld l,a
ld a,104
sub l
ld l,a
ld h,SINTAB/256
ld a,(hl)
scf
ret
GOSIN3 inc h
SIN360 ld bc,360
and a
LPSIN1 sbc hl,bc
jr nc,LPSIN1
add hl,bc
jp SIN_HL
;-------
;вычисление косинуса HL
;A=cos(HL)*255, CF=sign (NC=[+], C=[-])
COS_HL
dec h
jr z,GOCOS3
inc h
jr nz,COS360
ld h,SINTAB/256
ld a,l
cp 180
jr nc,GOCOS2
sub 90
jr c,GOCOS1
ld l,a
ld a,(hl)
scf
ret
GOCOS1 add a,180
ld l,a
ld a,(hl)
and a
ret
GOCOS2 sub 90
ld l,a
ld a,(hl)
scf
ret
GOCOS3 ld a,l
cp 14
jr nc,GOCOS4
add a,166
ld l,a
ld h,SINTAB/256
ld a,(hl)
scf
ret
GOCOS4 cp 104
jr nc,GOCOS5
sub 14
ld l,a
ld h,SINTAB/256
ld a,(hl)
and a
ret
GOCOS5 inc h
COS360 ld bc,360
and a
LPCOS1 sbc hl,bc
jr nc,LPCOS1
add hl,bc
jp COS_HL
;выравнивание на сегмент (граница 256)
ds $/256*256+256-$
SINTAB ;insert "sincos.d"
db #0D,#12,#16,#1B,#1F,#23,#28,#2C,#31,#35,#39,#3E,#42,#46,#4A,#4F
db #53,#57,#5B,#5F,#64,#68,#6C,#70,#74,#78,#7C,#7F,#83,#87,#8B,#8E
db #92,#96,#99,#9D,#A0,#A4,#A7,#AB,#AE,#B1,#B4,#B7,#BA,#BD,#C0,#C3
db #C6,#C9,#CC,#CE,#D1,#D3,#D6,#D8,#DA,#DD,#DF,#E1,#E3,#E5,#E7,#E9
db #EB,#EC,#EE,#F0,#F1,#F2,#F4,#F5,#F6,#F7,#F8,#F9,#FA,#FB,#FC,#FC
db #FD,#FE,#FE,#FE,#FF,#FF,#FF,#FF,#FF,#FF,#FF,#FE,#FE,#FE,#FD,#FC
db #FC,#FB,#FA,#F9,#F8,#F7,#F6,#F5,#F4,#F2,#F1,#F0,#EE,#EC,#EB,#E9
db #E7,#E5,#E3,#E1,#DF,#DD,#DA,#D8,#D6,#D3,#D1,#CE,#CC,#C9,#C6,#C3
db #C0,#BD,#BA,#B7,#B4,#B1,#AE,#AB,#A7,#A4,#A0,#9D,#99,#96,#92,#8E
db #8B,#87,#83,#7F,#7C,#78,#74,#70,#6C,#68,#64,#5F,#5B,#57,#53,#4F
db #4A,#46,#42,#3E,#39,#35,#31,#2C,#28,#23,#1F,#1B,#16,#12,#0D,#09
db #04
;--------------------------------------;
; SIN-TABLE MAKING ALGORYTHMS ;
; conception & code by Kolotov Sergey ;
; (c) SerzhSoft, Shadrinsk, apr, 1998 ;
;--------------------------------------;
ORG #8000
;--------------------------------------;
STACK_A EQU #2D28
FP_TO_A EQU #2DD5
;
SIN_MAX EQU #7F
SIN_ADD EQU 0
;
SINTBL1 EQU #6000
SINTBL2 EQU #6100
;--------------------------------------;
MAINPRG
LD C,SIN_MAX
LD B,SIN_ADD
LD DE,SINTBL1
PUSH BC
CALL SINCALC
POP BC
LD DE,SINTBL2
CALL SINMAKE
RET
;--------------------------------------;
SINCALC
PUSH DE
PUSH BC
PUSH DE
LD A,C
CALL STACK_A
POP DE
LD A,E
CALL STACK_A
RST #28 ;calc
DB #A3 ;stk_pi/2
DB #34,#40,#B0,#00,#40 ;#40
DB #05 ;divide
DB #04 ;multiply
DB #1F ;sin
DB #04 ;multiply
DB #38 ;endcalc
CALL FP_TO_A
JR Z,$+4 ;->
NEG ; |
POP BC ;<-
ADD A,B
POP DE
LD (DE),A
INC E
JR NZ,SINCALC
RET
;--------------------------------------;
SINMAKE
INC C
LD HL,SIN_DAT
PUSH BC
LD B,E
LP_SMK1 PUSH HL
LD H,(HL)
LD L,B
LD A,#08
LP_SMK2 ADD HL,HL
JR NC,$+3
ADD HL,BC
DEC A
JR NZ,LP_SMK2
LD A,H
LD (DE),A
POP HL
INC HL
INC E
BIT 6,E
JR Z,LP_SMK1
LD H,D
LD L,E
DEC L
LD A,(HL)
LD (DE),A
INC E
LP_SMK3 LD A,(HL)
LD (DE),A
INC E
DEC L
JR NZ,LP_SMK3
LP_SMK4 LD A,(HL)
NEG
LD (DE),A
INC L
INC E
JR NZ,LP_SMK4
POP BC
LP_SMK5 LD A,(DE)
ADD A,B
LD (DE),A
INC E
JR NZ,LP_SMK5
RET
;-------;
SIN_DAT
DB #00,#06,#0D,#13,#19,#1F,#25,#2C
DB #32,#38,#3E,#44,#4A,#50,#56,#5C
DB #62,#67,#6D,#73,#78,#7E,#83,#88
DB #8E,#93,#98,#9D,#A2,#A7,#AB,#B0
DB #B4,#B9,#BD,#C1,#C5,#C9,#CD,#D0
DB #D4,#D7,#DB,#DE,#E1,#E4,#E7,#E9
DB #EC,#EE,#F0,#F2,#F4,#F6,#F7,#F9
DB #FA,#FB,#FC,#FD,#FE,#FE,#FF,#FF
;--------------------------------------;
; sincos.z80
; CORDIC for sin() and cos() for the Zilog Z80 and Assembler Z80DT
; Author: Andre Adrian
; Version: 12apr2011
; length is 341 bytes
; LOAD TEST VALUES
LD DE,12AFH ; Angle = -1.57079632679 (-90°)
LD BC,9B78H
CALL SINCOS
; expected sin = B'C'BC = 0C0000000H (-1.0)
; expected cos = D'E'DE = 000000000H (0.0)
; delivered sin = B'C'BC = 0BFFFFFFFH
; delivered cos = D'E'DE = 000000031H
HALT
;==================================================
; Constants
;
CN: EQU 22
CM: EQU 8
;==================================================
; variable storage (RAM)
;
Angle:
DEFW 0,0
Y:
DEFW 0,0
X:
DEFW 0,0
Yold:
DEFW 0,0
pATR:
DEFW 0
Step:
DEFB 0
;==================================================
; constant storage (ROM)
;
ATR:
DEFW 0F6A8H,03243H ; atan(1)
DEFW 06705H,01DACH ; atan(1/2)
DEFW 0BAFCH,00FADH ; atan(1/4)
DEFW 06EA6H,007F5H ; atan(1/8)
DEFW 0AB76H,003FEH ; ...
DEFW 0D55BH,001FFH
DEFW 0FAAAH,000FFH
DEFW 0FF55H,0007FH
DEFW 0FFEAH,0003FH
;==================================================
; N BIT ARITHMETRIC SHIFT RIGHT ROUTINE 32BIT = 32BIT
; BCDE >>= A
; CHANGES FLAGS
;
SRBCDE:
SUB 8 ; SET CARRY FLAG IF A < 8
JR C,SRBEBT ; NO MORE BYTES TO SHIFT
LD E,D ; SHIFT BITS 8..15 TO 7..0
LD D,C ; SHIFT BITS 16..23 TO 8..15
LD C,B ; SHIFT BITS 24..31 TO 16..23
LD B,0 ; ASSUME POSITIVE NUMBER
BIT 7,C ; SET ZERO FLAG IF BIT 7 == 0
JR Z,SRBEPS
DEC B ; CHANGE + TO - SIGN
SRBEPS:
JR SRBCDE
SRBEBT:
ADD A,8 ; UNDO SUB 8, SET ZERO FLAG IF A == 0
SRBELP:
JR Z,SRBERT ; NO MORE BITS TO SHIFT
SRA B
RR C
RR D
RR E
DEC A ; SET ZERO FLAG IF A == 0
JR SRBELP
SRBERT:
RET ; RESULT IS IN BCDE.
;==================================================
; SIN() COS() ROUTINE 2 * 32BIT = 32BIT
; B'C'BC, D'E'DE = f(BCDE)
; NEEDS REGISTERS A BC DE HL, CHANGES FLAGS
;
SINCOS:
LD (Angle),DE ; store argument
LD (Angle+2),BC
SUB A ; A = 0
LD (Step),A ; i = 0
LD L,A
LD H,A
LD (Y),HL ; y = 0;
LD (Y+2),HL
LD HL,03B6AH ; x = FIXED(0.607252935..);
LD (X),HL
LD HL,026DDH
LD (X+2),HL
LD HL,ATR ; pATR = ATR;
LD (pATR),HL
SNCSDO: ; do {
LD HL,(Y) ; yold = y;
LD (Yold),HL
LD HL,(Y+2)
LD (Yold+2),HL
; y += (angle >= 0)? x >> i: -(x >> i);
LD DE,(X) ; x >> i
LD BC,(X+2)
LD A,(Step)
CALL SRBCDE
LD A,(Angle+3) ; (angle >= 0)?
RLA ; Bit 7 to Carry
LD HL,(Y)
JR C,SNCSL1
ADD HL,DE ; y += x >> i
LD (Y),HL
LD HL,(Y+2)
ADC HL,BC
JR SNCSE1
SNCSL1:
AND A ; y -= x >> i
SBC HL,DE
LD (Y),HL
LD HL,(Y+2)
SBC HL,BC
SNCSE1:
LD (Y+2),HL
; x -= (angle >= 0)? yold >> i: -(yold >> i);
LD DE,(Yold) ; yold >> i
LD BC,(Yold+2)
LD A,(Step)
CALL SRBCDE
LD A,(Angle+3) ; (angle >= 0)?
RLA
LD HL,(X)
JR C,SNCSL2
AND A ; x -= yold >> i
SBC HL,DE
LD (X),HL
LD HL,(X+2)
SBC HL,BC
JR SNCSE2
SNCSL2:
ADD HL,DE ; x += yold >> i
LD (X),HL
LD HL,(X+2)
ADC HL,BC
SNCSE2:
LD (X+2),HL
; a = (i < M+1)? *pATR++: atr_fixed[M] >> (i-M);
LD A,(Step) ; (i < M+1)?
SUB CM+1 ; i-(M+1)
JR NC,SNCSL3
LD HL,(pATR) ; *pATR++
LD E,(HL)
INC HL
LD D,(HL)
INC HL
LD C,(HL)
INC HL
LD B,(HL)
INC HL
LD (pATR),HL
JR SNCSE3
SNCSL3:
LD DE,(ATR+32) ; atr[M] >> i-M
LD BC,(ATR+34)
INC A ; i-M
CALL SRBCDE
SNCSE3:
; angle -= (angle >= 0)? a: -a;
LD A,(Angle+3) ; (angle >= 0)?
RLA
LD HL,(Angle)
JR C,SNCSL4
AND A ; angle -= a
SBC HL,DE
LD (Angle),HL
LD HL,(Angle+2)
SBC HL,BC
JR SNCSE4
SNCSL4:
ADD HL,DE ; angle += a
LD (Angle),HL
LD HL,(Angle+2)
ADC HL,BC
SNCSE4:
LD (Angle+2),HL
; } while (++i <= N-1);
LD HL,Step ; ++i
INC (HL)
LD A,CN-1 ; N-1 >= ++i
CP (HL)
JP NC,SNCSDO
; RESULT IS IN MEM(X) AND MEM(Y)
LD DE,(X)
LD BC,(Y)
EXX
LD DE,(X+2)
LD BC,(Y+2)
EXX
RET ; RESULT IS IN D'E'DE AND B'C'BC
SerzhSoft
КАК СИHУС ЗАБАБАХАТЬ
Лучше в pуках синица,
чем под кpоватью утка!
Hастало вpемя поговоpить о наболев-
шем, а конкpетнее - о синусах. Дело в
том, что во многих мегадемных эффектах
используются именно синусные таблички.
Это может быть пpостенькая плазма, ка-
кое-нибудь тело, движущееся по плавной
тpаектоpии, полет над гоpами, тоннельчик
и куча еще всего дpугого, чего только
можно пожелать...
Тpадиционно, да и удобно, of course,
создавать синусную табличку с pазмеpом,
кpатным 256 байт, а еще лучше - pовно
#0100! : -) Пpи этом табличка должна на-
чинаться с адpеса, выpавненного на сег-
мент, то есть младший байт этого адpеса
pавен нолику (#?? 00).
Создать пpостейшую sin-table можно с
помощью обыкновенного бейсика. К пpимеpу,
нам необходима табличка, pасположенная с
адpеса 24576 (#6000), длиной 256 байт,
амплитудой (pазмахом) синуса от -127 до
+127 и pазмеpом в один пеpиод. Получаем:
10 FOR i=0 TO 255
20 POKE 24576+i, SIN (PI/128*i)*127
30 NEXT i
Тепеpь сгенеpиpованную табличку мож-
но сохpанить на диске и встpаивать в свои
ассемблеpные эффекты по INSERT, INCBIN и
т. д.
Hо вот вы pешили немного изменить
амплитуду, скажем от -63 до +63, или вам
вдpуг стукнуло в голову, что "сеpединной
точкой" должна быть не 0, а, скажем, 128.
То есть получить pазбpос значений таблич-
ки от 128-63 до 128+63. Конечно, можно
снова "загpузить" BASIC и набpать немного
дpугую пpогpамму:
10 FOR i=0 TO 255
20 POKE 24576+i, SIN (PI/128*i)*63+128
30 NEXT i
Затем опять сохpанить все на диске,
и пpодолжать кодить в асм'е. Hо, навеp-
ное, даже последнему лaмеpу стало ясно,
что тут что-то не так. Hеэффективно, по-
теpя вpемени, да еще место на диске вся-
кие "левые" файлы занимают. Плюс, к тому
же, тоpмознутость компиляции пpи подгpуз-
ке по INCBIN'у. Да и хpанить целый синус-
ный пеpиод в памяти, когда можно обойтись
и четвеpью... Hет, с этим надо что-то де-
лать!
И выход, конечно же, есть. Hе уж то
нам слабо накодить пpоцедуpу ГЕHЕPАЦИИ
синуса на ассемблеpе. Да pаз плюнуть!
Хлоп, и уже готова подпpогpамма с исполь-
зованием ПЗУ 'шного калькулятоpа. Похожая
вещь пpисутствовала в Deja Vu #03 ,
статья "Плавающие атpибуты" . Здесь же
пpиводится немного улучшенная пpоцедуpа:
STACK_A EQU #2D28
FP_TO_A EQU #2DD5
;
SINCALC
PUSH DE
PUSH BC
PUSH DE
LD A,C
CALL STACK_A
POP DE
LD A,E
CALL STACK_A
RST #28 ;calc
DB #A3 ;stk_pi/2
DB #34,#40,#B0,#00,#40 ;#40
DB #05 ;divide
DB #04 ;multiply
DB #1F ;sin
DB #04 ;multiply
DB #38 ;endcalc
CALL FP_TO_A
JR Z,$+4 ;->
NEG ; |
POP BC ;<-
ADD A,B
POP DE
LD (DE),A
INC E
JR NZ,SINCALC
RET
Hа входе в pегистpе C мы должны ука-
зать "pазмах" синуса (амплитуда: -С...
+С), в pегистpе B - центpальное значение
(сколько пpибавлять), а pегистpовая паpа
DE должна содеpжать адpес буфеpа под
табличку. Пpичем, этот адpес обязательно
должен быть кpатен 256 (выpавнен на сег-
мент). И вот, пpимеpно чеpез 15 секунд,
мы получим те заветные 256 байт!
"Так до-о-олго!!!" - спpаведливо за-
метите Вы и будете пpавы. Hу а что вы еще
хотели всего за 39 байт памяти, занимае-
мых пpоцедуpой. Hо, конечно же, данный
пpимеp был пpиведен лишь для полноты
каpтины, а настоящую пpоцедуpу мы pазбе-
pем именно сейчас. Ведь не будете же вы
в демке писать "Please, wait 15 sec...
Decrunching..." только pади какой-то таб-
лички в 256 байт! И пpавильно! Итак, вот
скоpостной аналог вышепpиведенной пpоце-
дуpы, pаботающий несpавненно быстpее, хо-
тя и занимающий уже 55 байт + 64 байта на
данные. Итого: 119 байт. Hо скоpость!
SINMAKE
INC C
LD HL,SIN_DAT
PUSH BC
LD B,E
LP_SMK1 PUSH HL
LD H,(HL)
LD L,B
LD A,#08
LP_SMK2 ADD HL,HL
JR NC,$+3
ADD HL,BC
DEC A
JR NZ,LP_SMK2
LD A,H
LD (DE),A
POP HL
INC HL
INC E
BIT 6,E
JR Z,LP_SMK1
LD H,D
LD L,E
DEC L
LD A,(HL)
LD (DE),A
INC E
LP_SMK3 LD A,(HL)
LD (DE),A
INC E
DEC L
JR NZ,LP_SMK3
LP_SMK4 LD A,(HL)
NEG
LD (DE),A
INC L
INC E
JR NZ,LP_SMK4
POP BC
LP_SMK5 LD A,(DE)
ADD A,B
LD (DE),A
INC E
JR NZ,LP_SMK5
RET
;
SIN_DAT
DB #00,#06,#0D,#13,#19,#1F,#25,#2C
DB #32,#38,#3E,#44,#4A,#50,#56,#5C
DB #62,#67,#6D,#73,#78,#7E,#83,#88
DB #8E,#93,#98,#9D,#A2,#A7,#AB,#B0
DB #B4,#B9,#BD,#C1,#C5,#C9,#CD,#D0
DB #D4,#D7,#DB,#DE,#E1,#E4,#E7,#E9
DB #EC,#EE,#F0,#F2,#F4,#F6,#F7,#F9
DB #FA,#FB,#FC,#FD,#FE,#FE,#FF,#FF
Тепеpь пpи инсталляции какого-либо
вашего FX'а достаточно вызвать SINMAKE с
нужными паpаметpами, и спустя секунду
табличка уже готова! Можно генеpить нес-
колько pазных табличек... Или одну, но
состоящую из pазных синусных пеpиодов, с
pазными амплитудами... А затем, скажем,
пустить по этой таблице scroller и отpы-
ваться на всю катушку...
В пpиложении вы найдете ассемблеpный
исходник с двумя этими пpоцедуpами. И
помните:
Лучше в пpогpамме синус,
чем в мегадеме глюки!!!
P.S. А ведь можно обойтись и без
таблички данных SIN_DAT! Кто догадается -
как? : -)
SQR
;извлечение квадратного корня
;из регистровой пары.
;на входе HL-число из которого
;необходимо извлечь квадратный корень
;на выходе A-квадратный корень из HL
;by Virtual/BW
sqrhl xor a ;обнулили a;)
ld de,64 ;этого требует алгоритм ; t=14
sla h ;берем два самых левых
adc a,a ;бита аргумента
sla h ;
adc a,a ;
jr z,$+4 ;если они не равны
dec a ;нулю, то увеличиваем
inc d ;результат ; t=39
sla h ;┐далее следуем
adc a,a ;│алгоритму извлечения
sla h ;│квадратного корня
adc a,a ;│"столбиком", только
sla d ;│
ld c,d ;│3 раза
sli c ;│
cp c ;│
jr c,$+4 ;│
sub c ;│
inc d ;┘ t=63*3=189
sla l ;┐проделываем похожую
adc a,a ;│операцию с
sla l ;│младшим байтом
adc a,a ;│аргумента
sla d ;│
ld c,d ;│2 раза
sli c ;│
cp c ;│
jr c,$+4 ;│
sub c ;│
inc d ;┘ t=63*2=126
ld h,a ;
or a ;обнулили флаг переноса ; t=8
sbc hl,de ;теперь нам не хватает
jr nc,$+3 ;одного регистра d для
add hl,de ;выполнения последних
ccf ;двух циклов, придется
rl d ;использовать более
add hl,hl ;медленные операции
add hl,hl ;с регистровыми парами ; t=67
sbc hl,de ;и последний раз
ccf ;выполняем расчеты, не
ld a,d ;восстанавливая hl
adc a,a ;в a-результат ; t=27
;итого: 14+39+189+126+8+67+27=470 тактов
;извлечение квадратного корня
;из трехбайтового числа.
;На входе: dhl=d*65536+hl - само число
;На выходе: hl - тот самый квадратный корень
;by Virtual/BW
sqrdhl xor a ;начало процедуры
ld bc,64 ;практически аналогично ;предыдущей ; t=14
sla d ;
adc a,a ;
sla d ;
adc a,a ; t=39
jr z,$+4 ;
dec a ;
inc b ;
sla d ;┐
adc a,a ;│
sla d ;│
adc a,a ;│
sla b ;│
ld e,b ;│3 раза
sli e ;│
cp e ;│
jr c,$+4 ;│
sub e ;│
inc b ;┘ t=3*63=189
sla h ;┐
adc a,a ;│
sla h ;│
adc a,a ;│
sla b ;│
ld e,b ;│2 раза
sli e ;│
cp e ;│
jr c,$+4 ;│
sub e ;│
inc b ;┘ t=63*2=126
ld d,l ;
ld l,h ;
ld h,a ;меняем регистры
or a ; t=16
sbc hl,bc;┐
jr nc,$+3;│
add hl,bc;│
ccf ;│2 раза
rl b ;│
add hl,hl;│
add hl,hl;┘ t=67*2=134
ld l,h ;есть подозрение, что
ld h,0 ;этот блок и предыдущий
ld c,b ;можно немного ускорить
ld b,h ;
rl h ;
ld a,d ; t=31
rla ;┐да и этот блок мне
adc hl,hl;│не нравится.
rla ;│очень вероятно, что
adc hl,hl;│его можно неплохо
sla c ;│ускорить. Но как?
rl b ;│
ld d,b ;│
ld e c ;│3 раза
sli e ;│
rl d ;│
sbc hl,de;│
jr nc,$+4;│
add hl,de;│
dec c ;│
inc c ;┘ t=3*119=357 (!) ;ужас! Думайте, как ;ускорить!
rla ;этот блок аналогичен
adc hl,hl;предыдущему с
rla ;небольшой разницей
adc hl,hl;в конце. Это сделано
ld d,b ;ради повышения
ld e,c ;скорости вычислений
sla e ;
rl d ;
sli e ;
rl d ;
sbc hl,de;
ccf ; t=97
ld h,b ;
ld l,c ;
adc hl,hl;в hl - результат ; t=23
;итого: 14+39+189+126+16+134+31+357+97+23=1026
Square table generator is based on observation that
differences between consecutive squares
(0, 1, 4, 9, 16, 25, ...)
form a sequence of odd numbers.
(1, 3, 5, 7, 9, ...).
Thus, by adding successive odd numbers
iteratively we generate integer squares.
ld hl,SqrTab; must be a multiple of 256
ld b,l
ld c,l ; BC holds odd numbers
ld d,l
ld e,l ; DE holds squares
SqrGen ld (hl),e
inc h
ld (hl),d ; store x^2
ld a,l
neg
ld l,a
ld (hl),d
dec h
ld (hl),e ; store -x^2
ex de,hl
inc c
add hl,bc ; add next odd number
inc c
ex de,hl
cpl ; one byte replacement for NEG, DEC A
ld l,a
rla
jr c,SqrGen
; Square root of 16-bit value
;by Ricardo Bittencourt
; In: HL = value
; Out: D = result (rounded down)
Sqr16 ld de,#0040
ld a,l
ld l,h
ld h,d
or a
ld b,8
Sqr16_Loop sbc hl,de
jr nc,Sqr16_Skip
add hl,de
Sqr16_Skip ccf
rl d
add a,a
adc hl,hl
add a,a
adc hl,hl
djnz Sqr16_Loop
ret
; Square Root (16 bit) HL=number to find
;square root of Returns result in A
SQR16 LD DE,1
XOR A
DEC A
SQ16 SBC HL,DE
INC DE
INC DE
INC A
JR NC,SQ16
RET
; Square Root (32 bit) BCDE=number to find
;square root of. Returns result in DE
SQR32 LD A,B
PUSH DE
POP IX
LD D,0
LD E,D
LD H,D
LD L,D
LD B,16
SQ32A SUB #40
SBC HL,DE
JR NC,SQ32B
ADD A,#40
ADC HL,DE
SQ32B CCF
RL E
RL D
ADD IX,IX
RL C
RLA
ADC HL,HL
DJNZ SQ32A
RET
sqrt32:
;Input: HLDE
;Output: DE is the sqrt, AHL is the remainder
;speed: 238+{0,1}+{0,44}+sqrtHL+3*sqrt32sub_2+sqrt32_iter15
;min: 1260
;max: 1506
;avg: 1377.75
push de
call sqrtHL
pop bc
add a,a
ld e,a
jr nc,+_
inc d
_:
ld a,b
call sqrt32sub_2
call sqrt32sub_2
;Now we have four more iterations
;The first two are no problem
ld a,c
call sqrt32sub_2
;On the next iteration, HL might temporarily overflow by 1 bit
call sqrt32_iter15
;On the next iteration, HL is allowed to overflow,
DE could overflow with our current routine,
but it needs to be shifted right at the end, anyways
sqrt32_iter16:
add a,a
ld b,a ;either 0x00 or 0x80
adc hl,hl
rla
adc hl,hl
rla
;AHL - (DE+DE+1)
sbc hl,de \ sbc a,b
inc e
or a
sbc hl,de \ sbc a,b
ret p
add hl,de
adc a,b
dec e
add hl,de
adc a,b
ret
sqrt32sub_2:
;min: 185cc
;max: 231cc
;avg: 208cc
call +_
_:
;min: 84cc
;max: 107cc
;avg: 95.5cc
sll e \ rl d
add a,a \ adc hl,hl
add a,a \ adc hl,hl
sbc hl,de
inc e
ret nc
dec e
add hl,de
dec e
ret
sqrt32_iter15:
;91+{8,0+{0,23}}
;min: 91cc
;max: 114cc
;avg: 100.75cc
sll e \ rl d ;sla e \ rl d \ inc e
add a,a
adc hl,hl
add a,a
adc hl,hl ;This might overflow!
jr c,sqrt32_iter15_br0
sbc hl,de
inc e
ret nc
dec e
add hl,de
dec e
ret
sqrt32_iter15_br0:
or a
sbc hl,de
inc e
ret
sqrtA: ;Written by Zeda
;Input: A
;Output: D is the squareroot, A is the remainder (input-D^2)
;Destroys: E
;speed: 118+{0,6}+{0,7}+{0,7}+{0,3}
;min: 118cc
;max: 141cc
;avg: 129.5cc
;38 bytes
ld de,5040h ; 10
sub e ; 4
jr nc,sqrtA_skip1 ;\
add a,e ; | branch 1: 12cc
ld d,10h ; | branch 2: 18cc
sqrtA_skip1: ;/
; ----------
cp d ; 4
jr c,sqrtA_skip2 ;\
sub d ; | branch 1: 12cc
set 5,d ; | branch 2: 19cc
sqrtA_skip2: ;/
; ----------
res 4,d ; 8
srl d ; 8
set 2,d ; 8
cp d ; 4
jr c,sqrtA_skip3 ;\
sub d ; | branch 1: 12cc
set 3,d ; | branch 2: 19cc
sqrtA_skip3: ;/
srl d ; 8
; ----------
inc a ; 4
sub d ; 4
jr nc,sqrtA_skip4 ;\
dec d ; | branch 1: 12cc
add a,d ; | branch 2: 15cc
sqrtA_skip4: ;/
srl d ; 8
ret ; 10
sqrtDE:;returns HL as the sqrt, DE as the remainder
;33 bytes;min: 928cc;max: 1120cc;avg: 1024cc;928+8{24,0}
ld b,$80
xor a
ld h,a
ld l,a
sqrt_loop:
srl b
rra
ld c,a
add hl,bc
ex de,hl
sbc hl,de
jr nc,+_
add hl,de
ex de,hl
or a
sbc hl,bc
.db $DA ;start of jp c,** which is 10cc to skip the next two bytes.
_:
ex de,hl
add hl,bc
srl h
rr l
srl b
rra
jr nc,sqrt_loop
ret
sqrtfixed_88:
;Inputs: A.C
;Output: D.E contains the squareroot
;speed: 1482+12{0,17}
;min: 1482cc
;max: 1686cc
;avg: 1584cc
;Adapted from Axe
;35 bytes
ld b,12
ld de,0
ld h,d
ld l,e
__Sqrt88Loop:
sub $40
sbc hl,de
jr nc,__Sqrt88Skip
add a,$40
adc hl,de
__Sqrt88Skip:
ccf
rl e
rl d
sla c
rla
adc hl,hl
sla c
rla
adc hl,hl
djnz __Sqrt88Loop
ret
sqrtfixed_88:
;Input: A.E ==> D.E
;Output: DE is the sqrt, AHL is the remainder
;Speed: 690+6{0,13}+{0,3+{0,18}}+{0,38}+sqrtA
;min: 855cc
;max: 1003cc
;avg: 924.5cc
;152 bytes
;Written by Zeda
call sqrtA
ld l,a
ld a,e
ld h,0
ld e,d
ld d,h
sla e
rl d
sll e \ rl d
add a,a \ adc hl,hl
add a,a \ adc hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
sll e \ rl d
add a,a \ adc hl,hl
add a,a \ adc hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
sll e \ rl d
add a,a \ adc hl,hl
add a,a \ adc hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
sll e \ rl d
add a,a \ adc hl,hl
add a,a \ adc hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
;Now we have four more iterations
;The first two are no problem
sll e \ rl d
add hl,hl
add hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
sll e \ rl d
add hl,hl
add hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
sqrtfixed_88_iter11:
;On the next iteration, HL might temporarily overflow by 1 bit
sll e \ rl d ;sla e \ rl d \ inc e
add hl,hl
add hl,hl
jr c,sqrtfixed_88_iter11_br0
sbc hl,de
jr nc,+_
add hl,de
dec e
jr sqrtfixed_88_iter12
sqrtfixed_88_iter11_br0:
or a
sbc hl,de
_:
inc e
;On the next iteration, HL is allowed to overflow,
DE could overflow with our current routine,
but it needs to be shifted right at the end, anyways
sqrtfixed_88_iter12:
ld b,a ;A is 0, so B is 0
add hl,hl
add hl,hl
rla
;AHL - (DE+DE+1)
sbc hl,de \ sbc a,b
inc e
or a
sbc hl,de \ sbc a,b
ret p
add hl,de
adc a,b
dec e
add hl,de
adc a,b
ret
;Adapted from Axe
sqrtHL:;Input: HL;Output: D is the square root,
cH is the remainder (c being the c flag),
A is 0, B is 0, L is 0
;speed: 758+8{0,6}
;min: 758cc;max: 806cc;avg: 782cc;26 bytes
p_Sqrt:
ld a,l
ld l,h
ld de,$0040
ld h,d
ld b,8
or a
__SqrtLoop:
sbc hl,de
jr nc,__SqrtSkip
add hl,de
__SqrtSkip:
ccf
rl d
add a,a
adc hl,hl
add a,a
adc hl,hl
djnz __SqrtLoop
ret
;written by Zeda
sqrtHL:;returns A as the sqrt, HL as the remainder, D = 0 fastest
;min: 352cc;max: 391cc;avg: 371.5cc
ld de,05040h ; 10
ld a,h ; 4
sub e ; 4
jr nc,sq7 ;\
add a,e ; | branch 1: 12cc
ld d,16 ; | branch 2: 18cc
sq7: ;/
cp d ; 4
jr c,sq6 ;\
sub d ; | branch 1: 12cc
set 5,d ; | branch 2: 19cc
sq6: ;/
res 4,d ; 8
srl d ; 8
set 2,d ; 8
cp d ; 4
jr c,sq5 ;\
sub d ; | branch 1: 12cc
set 3,d ; | branch 2: 19cc
sq5: ;/
srl d ; 8
inc a ; 4
sub d ; 4
jr nc,sq4 ;\
dec d ; | branch 1: 12cc
add a,d ; | branch 2: 19cc
dec d ; | <-- this resets the low bit of D, so `srl d` resets carry.
sq4: ;/
srl d ; 8
ld h,a ; 4
ld a,e ; 4
sbc hl,de ; 15
jr nc,sq3 ;\
add hl,de ; | 12cc or 18cc
sq3: ;/
ccf ; 4
rra ; 4
srl d ; 8
rra ; 4
ld e,a ; 4
sbc hl,de ; 15
jr c,sq2 ;\
or 20h ; | branch 1: 23cc
db 254 ; | <-- start of `cp *` which is 7cc to skip the next byte.
sq2: ; | branch 2: 21cc
add hl,de ;/
xor 18h ; 7
srl d ; 8
rra ; 4
ld e,a ; 4
sbc hl,de ; 15
jr c,sq1 ;\
or 8 ; | branch 1: 23cc
db 254 ; | <-- start of `cp *` which is 7cc to skip the next byte.
sq1: ; | branch 2: 21cc
add hl,de ;/
xor 6 ; 7
srl d ; 8
rra ; 4
ld e,a ; 4
sbc hl,de ; 15
jr nc,+_ ; \
add hl,de ; 15 |
srl d ; 8 |
rra ; 4 | branch 1: 38cc
ret ; 10 | branch 2: 40cc
_: ; |
inc a ; 4 |
srl d ; 8 |
rra ; 4 |
ret ; 10 /
; fast 16-bit isqrt by Zeda Thomas; Feel free to use for whatever :)
very fastest
sqrtHL:
;Input: HL;Output: A is the integer square root of HL
;Destroys: HL,DE (D is actually 0)
;min: 343cc
;max: 380cc
;avg: 361.5cc
;88 bytes
ld de,05040h ; 10
ld a,h ; 4
sub e ; 4
jr nc,sq7 ;\
add a,e ; | branch 1: 12cc
ld d,16 ; | branch 2: 18cc
sq7: ;/
; ----------
cp d ; 4
jr c,sq6 ;\
sub d ; | branch 1: 12cc
set 5,d ; | branch 2: 19cc
sq6: ;/
; ----------
res 4,d ; 8
srl d ; 8
set 2,d ; 8
cp d ; 4
jr c,sq5 ;\
sub d ; | branch 1: 12cc
set 3,d ; | branch 2: 19cc
sq5: ;/
srl d ; 8
; ----------
inc a ; 4
sub d ; 4
jr nc,sq4 ;\
dec d ; | branch 1: 12cc
add a,d ; | branch 2: 19cc
dec d ; | <-- this resets the low bit of D, so `srl d` resets carry.
sq4: ;/
srl d ; 8
ld h,a ; 4
; ----------
ld a,e ; 4
sbc hl,de ; 15
jr nc,sq3 ;\
add hl,de ; | 12cc or 18cc
sq3: ;/
ccf ; 4
rra ; 4
srl d ; 8
rra ; 4
; ----------
ld e,a ; 4
sbc hl,de ; 15
jr c,sq2 ;\
or 20h ; | branch 1: 23cc
db 254 ; | <-- start of `cp *` which is 7cc to skip the next byte.
sq2: ; | branch 2: 21cc
add hl,de ;/
xor 18h ; 7
srl d ; 8
rra ; 4
; ----------
ld e,a ; 4
sbc hl,de ; 15
jr c,sq1 ;\
or 8 ; | branch 1: 23cc
db 254 ; | <-- start of `cp *` which is 7cc to skip the next byte.
sq1: ; | branch 2: 21cc
add hl,de ;/
xor 6 ; 7
srl d ; 8
rra ; 4
; ----------
ld e,a ; 4
sbc hl,de ; 15
;This code would restore the square root
; jr nc,sq0 ;\
; add hl,de ; | 12cc or 18cc
; sq0: ;/
sbc a,255 ; 7
srl d ; 8
rra ; 4
ret ; 10
sqrtHLIX:
;Input: HLIX;Output: DE is the sqrt, AHL is the remainder
;speed: 751+6{0,6}+{0,3+{0,18}}+{0,38}+sqrtHL
;min: 1103;max: 1237;avg: 1165.5;166 bytes
call sqrtHL ;expects returns A as sqrt, HL as remainder, D = 0
add a,a
ld e,a
rl d
ld a,ixh
sll e \ rl d
add a,a \ adc hl,hl
add a,a \ adc hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
sll e \ rl d
add a,a \ adc hl,hl
add a,a \ adc hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
sll e \ rl d
add a,a \ adc hl,hl
add a,a \ adc hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
sll e \ rl d
add a,a \ adc hl,hl
add a,a \ adc hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
;Now we have four more iterations
;The first two are no problem
ld a,ixl
sll e \ rl d
add a,a \ adc hl,hl
add a,a \ adc hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
sll e \ rl d
add a,a \ adc hl,hl
add a,a \ adc hl,hl
sbc hl,de
jr nc,+_
add hl,de
dec e
.db $FE ;start of `cp *`
_:
inc e
sqrt32_iter15:
;On the next iteration, HL might temporarily overflow by 1 bit
sll e \ rl d ;sla e \ rl d \ inc e
add a,a
adc hl,hl
add a,a
adc hl,hl ;This might overflow!
jr c,sqrt32_iter15_br0
sbc hl,de
jr nc,+_
add hl,de
dec e
jr sqrt32_iter16
sqrt32_iter15_br0:
or a
sbc hl,de
_:
inc e
;On the next iteration, HL is allowed to overflow,
DE could overflow with our current routine,
but it needs to be shifted right at the end, anyways
sqrt32_iter16:
add a,a
ld b,a ;either 0x00 or 0x80
adc hl,hl
rla
adc hl,hl
rla
;AHL - (DE+DE+1)
sbc hl,de \ sbc a,b
inc e
or a
sbc hl,de \ sbc a,b
ret p
add hl,de
adc a,b
dec e
add hl,de
adc a,b
ret
SqrtL:
;Inputs:; L is the value to find the square root of
;Outputs:
; C is the result
; B,L are 0
; DE is not changed
; H is how far away it is from the next smallest perfect square
; L is 0
; z flag set if it was a perfect square;Destroyed: A
ld bc,400h ; 10 10
ld h,c ; 4 4
sqrt8Loop: ;
add hl,hl ;11 44
add hl,hl ;11 44
rl c ; 8 32
ld a,c ; 4 16
rla ; 4 16
sub a,h ; 4 16
jr nc,$+5 ;12|19 48+7x
inc c
cpl
ld h,a
djnz sqrt8Loop ;13|8 47
ret ;10 10
;287+7x, x is the number of bits in the result;min: 287;max: 315;19 bytes
L_sqrd:
;Input: L;Output: L*L->A
;147 t-states;36 bytes
ld b,l
;First iteration, get the lowest 3 bits of -x^2
sla l
rrc b
sbc a,a
or l
ld c,a
;second iteration, get the next 2 bits of -x^2
rrc b
sbc a,a
xor l
and $F8
add a,c
ld c,a
;third iteration, get the next 2 bits of -x^2
sla l
rrc b
sbc a,a
xor l
and $E0
add a,c
ld c,a
;fourth iteration, get the eight bit of x^2
sla l
rrc b
sbc a,a
xor l
and $80
sub c
ret
sqrA:
;A*A->A
;Destroys: HL
;76cc or 79cc or 82cc
;Avg: 79cc;51 bytes
add a,a
add a,a
jr nc,$+4
neg
rrca
rrca
ld l,a
srl l
ld h,sqrLUT/256
jr c,$+4
neg
add a,(hl)
ret
sqrLUT:
;MUST BE ALIGNED to a 256-byte boundary.
;Can use:
; #if 0!=$&255
; .fill 256-($&255),0
; #endif
.db $00,$06,$14,$2A,$48,$6E,$9C,$D2
.db $10,$56,$A4,$FA,$58,$BE,$2C,$A2
.db $20,$A6,$34,$CA,$68,$0E,$BC,$72
.db $30,$F6,$C4,$9A,$78,$5E,$4C,$42
;|HL = SQR (HL)
;4.RAS - квадратный корень
;INPUT : HL <-- из чего
;OUTPUT: HL = SQR (HL)
;портятся DE,HL,BC,A
RAS LD A,H
OR L
JR Z,RAS_4
RAS1 LD A,H
AND A
JP NZ,RAS11
LD A,L
CP 1
JP NZ,RAS11
LD HL,1
RET
RAS11 LD B,H
LD C,L
SRL B
RR C
RAS_1 PUSH HL
LD D,B
LD E,C
CALL DIV
ADD HL,BC
SRL H
RR L
PUSH HL
LD D,B
LD E,C
SBC HL,DE
JP NC,RES_10
ADD HL,DE
EX DE,HL
SBC HL,DE
RES_10 LD A,H
AND A
JP NZ,RAS_0
LD A,L
CP 2
JP NC,RAS_0
POP HL
POP BC
RET
RAS_0 POP BC
POP HL
JP RAS_1
RAS_4 LD HL,0
RET
SQR OR A
LD A,L
LD L,H
LD DE,64
LD H,D
LD B,7
SQR0 SBC HL,DE
JR NC,$+3
ADD HL,DE
CCF
RL D
RLA
ADC HL,HL
JP C,0
RLA
ADC HL,HL
JP C,0
DJNZ SQR0
SBC HL,DE
;JR NC,$+3
;ADD HL,DE
CCF
RL D
;RLA
;ADC HL,HL
;RLA
;ADC HL,HL
RET
;sqr hl (from Inferno 4)
;HL=аргумент N
OR A
LD A,L
LD L,H
LD DE,64
LD H,D
LD B,8
sqr0 SBC HL,DE
JR NC,$+3
ADD HL,DE
CCF
RL D
ADD A,A
ADC HL,HL
ADD A,A
ADC HL,HL
DJNZ sqr0
;D=результат
NEG
neghl xor a
sub l
ld l,a
sbc a,h;a,a
sub l
ld h,a
ret
negde xor a
sub e
ld e,a
sbc a,d ;a,a
sub e
ld d,a
ret
RANDOM
;by DDp
;in: HL', DE' out: A, HL', DE'
RANDOM
EXX
LD C,L
LD A,H
RL C
RLA
RL C
RLA
LD B,A
RL C
RLA
RL C
RLA
RL C
RLA
XOR B
LD H,L
LD L,D
LD D,E
LD E,A
EXX
RET
Процедура jtn’а для двухбайтного рандома, с сидом.
Для однобайтного можно брать любой байт из двух:
rnd16
ld de,seed
ld a,d,h,e,l,253
or a:sbc hl,de,a,0,hl,de
ld d,0:sbc a,d:ld e,a:sbc hl,de
jr nc,rnd:inc hl
rnd ld (rnd16+1),hl
ret
random_color
ld a,r
and 7
or a
jr z,random_color
ret
Xorshift
Xorshift is a class of pseudorandom number generators
discover by George Marsaglia and detailed in his
2003 paper, Xorshift RNGs.
; 16-bit xorshift pseudorandom number generator
by John Metcalf, 20 bytes, 86 cycles (excluding ret)
; returns hl = pseudorandom number; corrupts a
; generates 16-bit pseudorandom numbers
with a period of 65535
; using the xorshift method:
hl ^= hl << 7; hl ^= hl >> 9; hl ^= hl << 8
; some alternative shift triplets which also
perform well are: 6, 7, 13; 7, 9, 13; 9, 7, 13.
xornd ld hl,1 ; seed must not be 0
ld a,h
rra
ld a,l
rra
xor h
ld h,a
ld a,l
rra
ld a,h
rra
xor l
ld l,a
xor h
ld h,a
ld (xornd+1),hl
ret
;You may use this routine, just be sure to
credit John Metcalf! Written by John Metcalf
;http://www.retroprogramming.com/2017/07/xorshift-pseudorandom-numbers-in-z80.html
; Annotated by Zeda Thomas,
fixed typo (86 cycles==> 82 cycles)
;Note: uses SMC 16-bit xorshift pseudorandom
number generator
; 20 bytes, 82 cycles (excluding ret)
; returns hl = pseudorandom number
; corrupts a
xorrnd
ld hl,1 ; Init the seed, must not be 0
ld a,h ;\
rra ; | Get the top bits of xs<<7 and xor with the top byte of HL
ld a,l ; | abcdefgh ijklmnop
rra ; | ^hijklmno 00000000
xor h ; | Note that we still need to xor the 'p' with the top byte of l
ld h,a ;/
ld a,l ;\
rra ; | we get 'p' in the carry flag, now shift that in when we do xs>>9
ld a,h ; | abcdefgh ijklmnop (new value)
rra ; | ^00000000 pabcdefg
xor l ; | the 'p' is leftover from the first step, so now Step 1 and 2 are done
ld l,a ;/
xor h ;\ Finally, xor the bottom byte with the top byte for step 3
ld h,a ;/
ld (xorrnd+1),hl ; write back the new value as the next seed
ret
;Dean Belfield
; 16 bit random number routine I found on the web
; Returns a pseudo-random number in the HL register
RND16_SEED: EQU 12345
RND16: LD DE,RND16_SEED
LD A,D
LD H,E
LD L,253
OR A
SBC HL,DE
SBC A,0
SBC HL,DE
LD D,0
SBC A,D
LD E,A
SBC HL,DE
JR NC,1F
INC HL
1f LD (RND16+1),HL
RET
RND_32 LD HL,(SEED)
CALL RND
RND LD A,H
ADD HL,HL
XOR H
ADD HL,HL
ADD HL,HL
ADD HL,HL
XOR H
ADD HL,HL
ADD HL,HL
XOR H
ADD HL,HL
ADD HL,HL
LD L,A
LD (SEED),HL
RET
SEED DEFW #FFFF ; НЕ НОЛЬ!
Следующая процедура является генератором RND.
На выходе из процедуры в
регистровой паре HL будет число от 0 до 65535.
10 ORG 40000
20 BEGIN LD HL,(TEMP)
30 LD C,16
40 S1 LD A,H
50 ADD HL,HL
60 AND 255
70 JP PE,S2
80 INC HL
90 S2 DEC C
100 JR NZ,S1
110 LD (TEMP),HL
120 TEMP DEFB 1,0
from Pplayers (Saracen)
RANDOM LD DE,(SEED)
LD H,E
LD L,253
LD A,D
OR A
LD B,0
SBC HL,DE
SBC A,B
SBC HL,DE
SBC A,B
LD E,A
LD D,B
SBC HL,DE
JP NC,RANDOM2
INC HL
RANDOM2 LD (SEED),HL
RET
by Baze
RANDOM
ld a,SEED ;SEED is usually 0
ld b,a
add a,a
add a,a
add a,b
inc a ;you may try also ADD A,7
ld (RANDOM+1),a
ret
8-bit Xor-Shift random number generator.
;Created by Patrik Rak in 2008 and revised in 2011/2012.
;http://www.worldofspectrum.org/forums/showthread.php?t=23070
org 40000
call rnd ; BASIC driver
ld c,a
ld b,0
ret
xsrnd ld hl,0xA280 ; yw -> zt
ld de,0xC0DE ; xz -> yw
ld (rnd+4),hl ; x = y, z = w
ld a,l ; w = w ^ ( w << 3 )
add a,a
add a,a
add a,a
xor l
ld l,a
ld a,d ; t = x ^ (x << 1)
add a,a
xor d
ld h,a
rra ; t = t ^ (t >> 1) ^ w
xor h
xor l
ld h,e ; y = z
ld l,a ; w = t
ld (xsrnd+1),hl
ret
pentis by tony raugas (далеко не самый лучший вариант)
; ;---
; ld a, r ;рандом но он раскидан
; dec a ;
; adc a, 1 ;
; ld (random_var), a ;
; ld hl, (random_var);теперь объеденил и рандом заработал как надо
; ld b, 5 ;раньше выдавал первую фигуру в тетрисе Т в петрисе L в пентисе Y (не суть)
; frn1 sla l
; rl h ;ВНИМАНИЯ-ВНИМАНЬЯ! рандом в самый первый раз НАДО ВЫЗВАТЬ ДВАЖДЫ!!!
; ld a, #C0 ;что в принципе и сделано
; and h ;зато после удаления фигур и режимов петриса и пентиса - заглючило опять
; jp pe, frn2
; inc l
; frn2 djnz frn1
; ld (random_var), hl
8-bit Random Number Generator
This is a very simple linear congruential generator.
The formula is x[i + 1] = (5 * x[i] + 1) mod 256.
Its only advantage is small size and simplicity.
Due to nature of such generators only a couple
of higher bits should be considered random.
Input: none ,
Output: A = pseudo-random number, period 256
Rand8 ld a,Seed ; Seed is usually 0
ld b,a
add a,a
add a,a
add a,b
inc a ; another possibility is ADD A,7
ld (Rand8+1),a
ret
;This code snippet is 9 bytes and 43cc
;Inputs:; HL is the input seed and must be non-zero
;Outputs:; A is the 8-bit pseudo-random number
; HL is the new seed value (will be non-zero)
;opcode cc
add hl,hl ; 29 11
sbc a,a ; 9F 4
and %00101101 ; E62D 7
xor l ; AD 4
ld l,a ; 6F 4
ld a,r ; ED5F 9
add a,h ; 84 4
;Technical details:
; The concept behind this routine is to combine an LFSR (poor RNG) with a
; counter. The counter improves the RNG quality, while also extending the period
; length.
; For this routine, I took advantage of the Z80's built-in counter, the `r`
; register. This means that we don't need to store the counter anywhere, and it
; is pretty fast to access!
; Some caveats:
; * r is a 7-bit counter
; * r will increment some number of times between runs of the RNG. In most
; cases, this will be constant, but if it increments an even number each
; time, then the bottom bit is always the same, weakening the effect of
; the counter. In the worst case, it increments a multiple of 128 times,
; effectively making your RNG just as good/bad as the LFSR. Ideally, you
; want `r` to increment an odd number of times between runs.
; * In the best case, the bottom 7 bits have 50/50 chance of being 0 or 1.
; The top bit is 1 with probability 1/2 + 1/(2^17-2) ~ .5000076295
; * In the event that your main loop waits for user input between calls,
; then congatulations, you might have a True RNG :)
Fast quality CMWC random number generator for Z80
I have created to weed out the crap RNGs out there.
32+10 bytes, 146..149 T cycles, period 253*2^59,
which is more than 2^66 or 10^20.
cmwc.asm
; 8-bit Complementary-Multiply-With-Carry (CMWC)
random number generator.
; Created by Patrik Rak in 2012, and revised in 2014/2015,
; with optimization contribution
from Einar Saukas and Alan Albrecht.
;http://www.worldofspectrum.org/forums/showthread.php?t=39632
org 40000
call rnd ; BASIC driver
ld c,a
ld b,0
ret
rnd ld hl,table
ld a,(hl) ; i = ( i & 7 ) + 1
and 7
inc a
ld (hl),a
inc l ; hl = &cy
ld d,h ; de = &q[i]
add a,l
ld e,a
ld a,(de) ; y = q[i]
ld b,a
ld c,a
ld a,(hl) ; ba = 256 * y + cy
sub c ; ba = 255 * y + cy
jr nc,$+3
dec b
sub c ; ba = 254 * y + cy
jr nc,$+3
dec b
sub c ; ba = 253 * y + cy
jr nc,$+3
dec b
ld (hl),b ; cy = ba >> 8, x = ba & 255
cpl ; x = (b-1) - x = -x - 1 = ~x + 1 - 1 = ~x
ld (de),a ; q[i] = x
ret
table db 0,0,82,97,120,111,102,116,20,15
if (table/256)-((table+9)/256)
error "whole table must be within single 256 byte block"
endif
4.2 16-bit Random Number Generator
This generator is based on similar method but
gives much better results.
It was taken from an old ZX Spectrum
game and slightly optimised.
Input: none
Output: HL = pseudo-random number, period 65536
Rand16 ld de,Seed ; Seed is usually 0
ld a,d
ld h,e
ld l,253
or a
sbc hl,de
sbc a,0
sbc hl,de
ld d,0
sbc a,d
ld e,a
sbc hl,de
jr nc,Rand
inc hl
Rand ld (Rand16+1),hl
ret
Z80 Psuedo-random number generator
ZX Spectrum Random Numbers
LD A,R ; Load the A register with the refresh register
LD L,A ; Copy register A into register L
AND %00111111 ; This masking prevents the address we are forming from accessing RAM
LD H,A ; Copy register A into register H
LD A,(HL) ; Load the pseudo-random value into A
; HOW THIS WORKS
; The refresh register in the Z80 is highly
unpredictable since it is incremented every cycle.
; Because it may be at any value when this
routine is called, it is very good for random numbers.
; This routine increases the randomness of the
number since it forms an address based on the
; refresh counter's current status and accesses
the memory at that address.
; It can also be modified to get a sixteen-bit
pseudo-random number by changing line 5 to LD D,(HL)
; and adding these two lines to the end:
; INC L
; LD E,(HL)
; This routine was written for the ZX Spectrum
which has a 16KB ROM. If you plan to use this routine
; on another Z80 system, change the binary value at
the AND instruction. For example, if you had a
; Z80 computer with an 8KB ROM, you would change the
binary value to %00011111.
; Fast RND
; An 8-bit pseudo-random number generator,
; using a similar method to the Spectrum ROM,
; - without the overhead of the Spectrum ROM.
; R = random number seed
; an integer in the range [1, 256]
; R -> (33*R) mod 257
; S = R - 1
; an 8-bit unsigned integer
ld a, (seed)
ld b, a
rrca ; multiply by 32
rrca
rrca
xor #1f
add a, b
sbc a, 255 ; carry
ld (seed), a
ret
This is a short peice of code that generates a maximal length,
8 bit, pseudo random number sequence.
It was originally used to switch off and on all the LED's in a 8x30 matrix
in a good random looking order (the un-needed values being skipped).
It's shorter, but slower, than using a lookup table and a counter.
; returns pseudo random 8 bit number in A. Only affects A.
; (r_seed) is the byte from which the number is generated and MUST be
; initialised to a non zero value or this function will always return
; zero. Also r_seed must be in RAM, you can see why......
rand_8 LD A,(r_seed) ; get seed
AND #B8h ; mask non feedback bits
SCF ; set carry
JP PO,no_clr ; skip clear if odd
CCF ; complement carry (clear it)
no_clr LD A,(r_seed) ; get seed back
RLA ; rotate carry into byte
LD (r_seed),A ; save back for next prn
RET ; done
r_seed DB 1 ; prng seed byte (must not be zero)
Ion Random
This is based off the tried and true pseudorandom
number generator featured in Ion by Joe Wingbermuehle
;-----> Generate a random number
; output a=answer 0<=a<=255
; all registers are preserved except: af
random:
push hl
push de
ld hl,(randData)
ld a,r
ld d,a
ld e,(hl)
add hl,de
add a,l
xor h
ld (randData),hl
pop de
pop hl
ret
randData dw 12345 ;here must be a 2 byte seed located in ram.
While this is a fast generator,
it's generally not considered very good
in terms of randomness.
Linear Feedback Shift Register
This particular prng is based on Linear
feedback shift register.
It uses a 64bit seed and generates 8 new
bits at every call.
LFSRSeed must be an 8 byte seed located in ram.
;------LFSR------
;James Montelongo
;optimized by Spencer Putt
;out: a = 8 bit random number
RandLFSR ld hl,LFSRSeed+4
ld e,(hl)
inc hl
ld d,(hl)
inc hl
ld c,(hl)
inc hl
ld a,(hl)
ld b,a
rl e \ rl d
rl c \ rla
rl e \ rl d
rl c \ rla
rl e \ rl d
rl c \ rla
ld h,a
rl e \ rl d
rl c \ rla
xor b
rl e \ rl d
xor h
xor c
xor d
ld hl,LFSRSeed+6
ld de,LFSRSeed+7
ld bc,7
lddr
ld (de),a
ret
While this may produces better numbers,
it is slower, larger and requires a bigger seed
than ionrandom. Assuming theres is a good seed to start,
it should generate ~2^56 bytes before repeating. However if t
here is not a good seed(0 for example), then the numbers crea
ted will not be adequate. Unlike Ionrandom and its use of the
r register, starting with the same seed the same numbers will
be generated. With Ionrandom
the code running may have an impact on the number generated.
This means this method requires more initialization.
You can initialize with TI-OS's seeds, stored at
seed1 and seed2, both are ti-floats but will serve the purpose.
Combined LFSR/LCG, 16-bit seeds
This is a very fast, quality pseudo-random number generator.
It combines a
16-bit Linear Feedback Shift Register
and a 16-bit LCG.
prng16:
;Inputs:
; (seed1) contains a 16-bit seed value
; (seed2) contains a NON-ZERO 16-bit seed value
;Outputs:
; HL is the result
; BC is the result of the LCG, so not that great of quality
; DE is preserved
;Destroys:
; AF
;cycle: 4,294,901,760 (almost 4.3 billion)
;160cc
;26 bytes
ld hl,(seed1)
ld b,h
ld c,l
add hl,hl
add hl,hl
inc l
add hl,bc
ld (seed1),hl
ld hl,(seed2)
add hl,hl
sbc a,a
and %00101101
xor l
ld l,a
ld (seed2),hl
add hl,bc
ret
On their own, LCGs and LFSRs don't pr
oduce great results and are generally
very cyclical, but they are very fast
to compute. The 16-bit LCG in the above
example will bounce around and reach each
number from 0 to 65535, but the lower bits
are far more predictable than the upper bits. Th
e LFSR mixes up the predictability of a given bit'
s state, but it hits every number except 0, meanin
g there is a slightly higher chance of any given b
it in the result being a 1 instead of a 0. It turn
s out that by adding together the outputs of these
two generators, we can lose the predictability of a
bit's state, while ensuring it has a 50% chance of
being 0 or 1. As well, since the periods,
65536 and 65535 are coprime, then the overall period of
the generator is 65535*65536, which is over 4 billion.
Combined LFSR/LCG, 32-bit seeds
This is similar to the one above, except that it use
s 32-bit seeds (and still returns a 16-bit result).
An advantage here is that they've been tested and
passed randomness tests (all of thee ones offered
by CAcert labs). As well, it is still very fast.
rand32:
;Inputs:
; (seed1_0) holds the lower 16 bits of the first seed
; (seed1_1) holds the upper 16 bits of the first seed
; (seed2_0) holds the lower 16 bits of the second seed
; (seed2_1) holds the upper 16 bits of the second seed
; **NOTE: seed2 must be non-zero
;Outputs:
; HL is the result
; BC,DE can be used as lower quality values,
; but are not independent of HL.
;Destroys:
; AF
;Tested and passes all CAcert tests
;Uses a very simple 32-bit LCG and 32-bit LFSR
;it has a period of 18,446,744,069,414,584,320
;roughly 18.4 quintillion.
;LFSR taps: 0,2,6,7 = 11000101
;291cc
seed1_0=$+1
ld hl,12345
seed1_1=$+1
ld de,6789
ld b,h
ld c,l
add hl,hl \ rl e \ rl d
add hl,hl \ rl e \ rl d
inc l
add hl,bc
ld (seed1_0),hl
ld hl,(seed1_1)
adc hl,de
ld (seed1_1),hl
ex de,hl
seed2_0=$+1
ld hl,9876
seed2_1=$+1
ld bc,54321
add hl,hl \ rl c \ rl b
ld (seed2_1),bc
sbc a,a
and %11000101
xor l
ld l,a
ld (seed2_0),hl
ex de,hl
add hl,bc
ret
rand32:
;Tested and passes all CAcert tests
;Uses a very simple 32-bit LCG and 32-bit LFSR
;it has a period of 18,446,744,069,414,584,320
;roughly 18.4 quintillion.
;LFSR taps: 0,2,6,7 = 11000101
;291cc
;Thanks to Runer112 for his help on optimizing the LCG
and suggesting to try the much simpler LCG. On their own,
the two are terrible, but together they are great.
;58 bytes
seed1_0=$+1
ld hl,12345
seed1_1=$+1
ld de,6789
ld b,h
ld c,l
add hl,hl \ rl e \ rl d
add hl,hl \ rl e \ rl d
inc l
add hl,bc
ld (seed1_0),hl
ld hl,(seed1_1)
adc hl,de
ld (seed1_1),hl
ex de,hl
;;lfsr
seed2_0=$+1
ld hl,9876
seed2_1=$+1
ld bc,54321
add hl,hl \ rl c \ rl b
ld (seed2_1),bc
sbc a,a
and %11000101
xor l
ld l,a
ld (seed2_0),hl
ex de,hl
add hl,bc
ret
rand24:
;;219cc
#ifdef smc
seed1_0=$+1
ld hl,12345
seed1_1=$+1
ld a,67
#else
ld hl,(seed1_0)
ld a,(seed1_1)
#endif
ld b,h
ld c,l
ld d,a
add hl,hl \ rla
add hl,hl \ rla
inc l
add hl,bc \ adc a,0
ld (seed1_0),hl
ld (seed1_1),a
ld c,b
ld b,a
#ifdef smc
seed2_0=$+1
ld hl,65432
seed2_1=$+1
ld a,10
#else
ld hl,(seed2_0)
ld a,(seed2_1)
#endif
add hl,hl
rla
ld (seed2_1),a
sbc a,a
and %10000111
xor l
ld l,a
ld (seed2_0),hl
add hl,bc
ret
;#define smc ;uncomment if you are using SMC
rand16:
;collaboration by Zeda with Runer112
;160cc or 148cc if using SMC
;26 bytes
;cycle: 4,294,901,760 (almost 4.3 billion)
#ifdef smc
seed1=$+1
ld hl,9999
#else
ld hl,(seed1)
#endif
ld b,h
ld c,l
add hl,hl
add hl,hl
inc l
add hl,bc
ld (seed1),hl
#ifdef smc
seed2=$+1
ld hl,9999
#else
ld hl,(seed2)
#endif
add hl,hl
sbc a,a
and %00101101
xor l
ld l,a
ld (seed2),hl
add hl,bc
ret
rand10:
;Returns A as a random integer on [0,9]
;Destroys: All
call rand5
sla h
rla
ret
rand5:
;Returns A on [0,4]
;Destroys: All
;Notes:
; This is a non-standard approach to generating random integers on [0,4].
; If you have a truly random number generator that generates bits (0 or 1)
; with equal probability, then standard approaches will still cause a slight
; bias. ("Standard": "rand mod 5" or int(5*rand)). For example, suppose we
; generate a 4-bit number. Then "rand mod 5" will cause 0 to be chosen
; 4/16 times, while 1, 2, 3, and 4 will be chosen 3/16 times (on average).
; A similar problem exists with int(5*rand). One way to mitigate this issue
; is just generating infintely many bits, but apparently that is impractical,
; so I came up with a compromise.
; My approach basically looks at the binary expansion of 1/5, 2/5, 3/5, and 4/5.
; 1/5 = .0011001100110011...
; 2/5 = .0110011001100110...
; 3/5 = .1001100110011001...
; 4/5 = .1100110011001100...
; So if I generate random bits and I get .001100, then a 0, then I know
; that no matter what all of the rest of the bits are, the number is less than
; 1/5, and so int(5*rand) is 0.
; By applying similar logic to the rest of the values, I can guarantee a uniform
; distribution on [0,4]. But there are four cases where this process might
; continue forever, specifically the cases that are like ...00110011...., but
; lucky for us, this happens 4/inf= 0% of the time. In fact, on average it
; takes 3 to 4 bits before the algorithm can assert which value to return.
; The one caveat is that on the Z80, we generally don't have truly random
; numbers :| On the otherhand, it is easy enough to generate pseudo-random
; bits with equal probability :)
call rand
ld a,h
and $C0
push af ;save the original value
ld c,a
rand5_start:
push bc
call rand
pop bc
ld b,15 ;I set this to 15 because I like to guarantee a bit is available for rand10.
rand5_loop:
ld a,h
xor c
jp p,rand5_end
add hl,hl
sla c
jr c,$+4
set 6,c
djnz rand5_loop
jr rand5_start
rand5_end:
pop af
rlca
rlca
sla h
adc a,0
ret
lfsr64:
;;Output: A is an 8-bit pseudo-random number.
ld hl,seed
sla (hl) \ inc hl
rl (hl) \ inc hl
rl (hl) \ inc hl
rl (hl) \ inc hl
rl (hl) \ inc hl
rl (hl) \ inc hl
rl (hl) \ inc hl
rl (hl)
ret nc
ld a,(seed)
xor %000011011
ld (seed),a
ret
lehmer:
;Input:
; (seed) has the seed value of the RNG
;Output:
; (seed) is updated, HL is the result
;Destroys:
; A,DE,BC
;Timing:
; if seed>0 231cc or 232cc, condition dependent
; if seed=0 91cc
; if SMC defined subtract 6cc
;Size: 44 bytes
;Notes:
; Uses the Lehmer RNG used by the Sinclair ZX81
; 75x mod 65537 -> x
#ifndef SMC
ld hl,(seed)
#else
seed = $+1
ld hl,0
#endif
;multiply by 75
ld c,l
ld b,h
xor a
adc hl,hl \ jr z,special \ ld d,a \ rla
add hl,hl \ rla
add hl,hl \ rla \ add hl,bc \ adc a,d
add hl,hl \ rla
add hl,hl \ rla \ add hl,bc \ adc a,d
add hl,hl \ rla \ add hl,bc
;modulo 65537, see note below on how this works
ld e,a
sbc hl,de ;No need to reset the c flag since it is already
jr nc,$+3
inc hl
ld (seed),hl
ret
special:
;In the case that HL=0,
this should be interpreted
as 65536 = -1 mod 65537,
so return -75 mod 65537 = -74 mod 65536 in HL
ld hl,-74
ld (seed),hl
ret
;mod by 2^16 + 1 (a prime)
;current form is A*2^16+HL
;need:
; (A*2^16+HL) mod (2^16+1)
;add 0 as +1-1
; (A*(2^16+1-1)+HL) mod (2^16+1)
;distribute
; (A*(2^16+1)-A+HL) mod (2^16+1)
;A*(2^16+1) mod 2^16+1 = 0, so remove
; (-A+HL) mod (2^16+1)
;Oh hey, that's easy! :P
;I use this trick everywhere, you should, too.
(из zx-review):
RND LD HL,0
LD A,H
ADD A,#77
LD H,A
RLС L
ADD A,L
LD L,A
LD (RND+1),HL
;rnd from Dave Perry's games
PUSH HL
PUSH HL
LD DE,(SEED)
LD H, E
LD L, #FD
LD A, D
AND A
SBC HL, DE
SBC A,0
SBC HL, DE
SBC A,0
LD E,A
LD D,0
SBC HL, DE
JR NC,$+3
INC HL
LD (SEED),HL
POP DE
POP HL
RET
SEED DW 0
А и B SEED останется RND
;from EXOLON by Rafaelle Cecco
;return A-random number
RND push hl
push de
push bc
ld hl, (rndSEED)
ld de, 7
add hl, de
ld e, l
ld d, h
add hl, hl
add hl, hl
ld c, l
ld b, h
add hl, hl
add hl, bc
add hl, de
ld (rndSEED), hl
xor h
pop bc
pop de
pop hl
ret
rndSEED dw 8EAAh
;from Lop Ears game by Players
RANDOM LD DE,(SEED)
LD H,E
LD L,253
LD A,D
OR A
LD B,0
SBC HL,DE
SBC A,B
SBC HL,DE
SBC A,B
LD E,A
LD D,B
SBC HL,DE
JP NC,RANDOM2
INC HL
RANDOM2 LD (SEED),HL
RET
HEX, DEC, BIN, ASCII...
;a=0-3 bit of hex
or #f0
daa
add a,#a0
adc a,#40
;out A=ascii code of hex digit to print
;a=0-#0f
add a,#90
daa
adc a,#40
daa
;out A=ascii code of hex digit to print
;зажигает нужный бит в 16-разрядном регистре by Destr
;например:
;На входе A = 5
;На выходе HL = %0000000000100000
; SET A,HL
LD HL,0
RLA
RLA
CP #20
RLA
OR #C4
LD ($+4),A
DW #CBCB
;Или чуть медленней, но уже с игнором старшего полубайта А
; SET A,HL
LD HL,0
SLI A
SLA A
CP #20
RLA
OR #C4
LD ($+4),A
DW #CBCB
;from Fast Tracker
Decimal65535max ex de,hl
push hl
ld l,c
ld h,b
ld bc,#2710 ;10000
call decdigg
jr hd4096
Decimal4096max EX DE, HL
PUSH HL
LD L, C
LD H, B
hd4096 LD BC, #3E8 ;1000
CALL decdigg
LD BC, #64 ;100
CALL decdigg
LD BC, #A ;10
CALL decdigg
LD A, L
ADD A, #30
LD (DE), A
INC DE
POP HL
EX DE, HL
RET
decdigg LD A, #2F
OR A
decdigj INC A
SBC HL, BC
JR NC, decdigj
ADD HL, BC
LD (DE), A
INC DE
RET
;hex to Ascii из Fast Tracker
HexDigitToAsciiCode_X0 RRCA
RRCA
RRCA
RRCA
HexDigitToAsciiCode_0X AND #F
ADD A,#90;hex to ascii
DAA
ADC A,#40
DAA
CP #30 ;если ascii код это Ноль
RET NZ ;то
LD A,".";печатаем точки
RET
Библиотечка печати в различных системах счисления.
;------ PDES LIB ------
;Printing in Different Evaluation Systems LIBrary
;(c) SerzhSoft, Shadrinsk, V,3 1997
_NULL EQU 0 ;используется в изменяемых командах
;--- print in DEC system ---
PDEC_B ;Печать десятичного числа в A3 (000..255)
LD L,A ;поместили
LD H,#00 ; число в HL
LD DE,DECTB_W+4 ;адрес в таблице, начиная с сотни
LD B,#03 ;макс. возможное кол-во цифр - три
JR LP_PDW1 ;переход на цикл печати
;
PDEC_W ;Печать десятичного числа в HL3 (00000..65535)
PUSH HL ;закинули печатаемое число на стек
LD HL,DECTB_W ;адрес таблицы степеней десятки
LD B,#05 ;макс. возможное количество цифр
LP_PDW1 LD E,(HL) ;взяли текущую степень
INC HL ; десятки из таблицы
LD D,(HL) ; и поместили в DE
INC HL ;перешли к след. элементу таблицы
EX (SP),HL ;адрес эл-та <-> печатаемое число
XOR A ;обнулили счетчик и флаг C для SBC
LP_PDW2 INC A ;увеличиваем счетчик
SBC HL,DE ;вычитаем текущую степень десятки
JR NC,LP_PDW2 ;повторяем пока HL>=0
ADD HL,DE ;HL=HL mod DE; A=HL div DE
ADD A,"0"-1 ;перевод A в ASCII-код ("0".."9")
RST #10 ;печать десятичной цифры
EX (SP),HL ;HL=адрес эл-та, число -> на стек
DJNZ LP_PDW1 ;цикл по цифрам
POP HL ;убрали оставшийся ноль со стека
RET ;выход из процедуры
DECTB_W DW 10000,1000,100,10,1 ;Таблица степеней десятки
;--- print in HEX system ---
PHEX_W ;Печать шестнадцатеричного числа в HL3 (0000..FFFF)
LD A,H ;печать старшего байта числа
CALL PHEX_B ;вызов процедуры HEX-печати байта
LD A,L ;печать младшего байта числа
PHEX_B ;Печать шестнадцатеричного числа в A3 (00..FF)
CALL PHEX_HB ;печать по полубайтам
PHEX_HB ;Печать старшего полубайта в A3 (0..F)
RRCA ;меняем
RRCA ; местами
RRCA ; старший
RRCA ; и младший полубайты
PHEX_LB ;Печать младшего полубайта в A3 (0..F)
PUSH AF ;сохранили A на стеке
AND #0F ;отбросили лишние старшие биты
CP #0A ;если A<10,
JR C,GO_PHL ; то перепрыгнули
ADD A,"A"-"9"-1 ;корректировка: после "9" идет "A"
GO_PHL ADD A,"0" ;перевод в ASCII-код
RST #10 ;печать HEX-цифры ("0".."F")
POP AF ;восстановили A со стека
RET ;выход из процедуры
;--- print in BIN system ---
PBIN_W ;Печать двоичного числа в HL (16 разрядов 0/1)
LD A,H ;печать старшего байта числа
CALL PBIN_B ;вызов процедуры BIN-печати байта
LD A,L ;печать младшего байта числа
PBIN_B ;Печать двоичного числа в A (8 разрядов 0/1)
LD B,#08 ;в байте - 8 битов
LP_PBB RLCA ;поочередно 'выдвигаем' биты в CF
PUSH AF ;сохранить A на стеке
LD A,#18 ;в зависимости от флага C:
RLA ; A="0" или A="1"
RST #10 ;печать очередного бита
POP AF ;восстановить A со стека
DJNZ LP_PBB ;поразрядный цикл
RET ;выход из процедуры
;
Listing 1. Процедура печати байта в десятичной системе.
PDEC_B ;Печать десятичного числа в аккумуляторе3 (000..255)
LD HL,DECTB_B ;адрес таблицы степеней десятки
LD B,#03 ;макс. возможное количество цифр
LP_PDB1 LD C,"0"-1 ;инициализация счетчика
LP_PDB2 INC C ;увеличиваем счетчик
SUB (HL) ;вычитаем текущую степень десятки
JR NC,LP_PDB2 ;повторять, пока A>=0
ADD A,(HL) ;A=A mod (HL); C=STR$(A div (HL))
PUSH AF ;сохранить A на стеке
LD A,C ;текущая цифра
RST #10 ;печать цифры
POP AF ;восстановление аккумулятора
INC HL ;переход к след. степени десятки
DJNZ LP_PDB1 ;закрутили цикл по цифрам
RET ;выход из процедуры
DECTB_B DB 100,10,1 ;Таблица степеней3 10-ки для byte
;--------------------------------------------------------------;
; Printing in Different Evaluation Systems LIBrary v2.0 ;
; (c) SerzhSoft, Shadrinsk, august, 1997 ;
;--------------------------------------------------------------;
_NULL EQU 0 ;используется в изменяемых командах
;--------------------------------------------------------------;
; print in DEC system: ;
;--------------------------------------------------------------;
PDEC_3B ;Печать десятичного числа в AHL (00000000..16777215)
LD DE,34464 ;DE=100000-65536
LD BC,#FF01 ;B=-1 - счетчик сотен тысяч; C=1
LP_PD31 INC B ;увеличиваем счетчик сотен тысяч
AND A ;сбpасываем флаг пеpеноса для SUB
SBC HL,DE ;уменьшаем тpехбайтное число AHL
SBC A,C ; на одну сотню тысяч
JR NC,LP_PD31 ;повтоpяем пока нет пеpеполнения
ADD HL,DE ;восстанавливаем положительное
ADC A,C ; значение у AHL; AHL<100000
PUSH AF ;сохpаняем на стеке нужные нам
PUSH HL ; впоследствии pегистpы A, HL
LD A,B ;количество сотен тысяч в числе
CALL PDEC_B ;печатаем это значение
POP HL ;восстанавливаем со стека
POP AF ; pегистpы A, HL
DEC A ;если остаток<65536,
JR NZ,PDEC_W ; то пеpеходим на печать 0..65535
LD DE,5536 ;иначе - число 65536..99999
ADD HL,DE ;пpибавляем остаток от десятков
ADC A,6 ; тысяч, и их сами - отдельно
PUSH HL ;---------------------------------
LD HL,DECTB_W ; Обходимый фpагмент пp-pы PDEC_W
LD B,#05 ;---------------------------------
JR LP_PDW1+1 ;пpыжок чеpез обнуление дес. тысяч
;--------------------------------------------------------------;
PDEC_B ;Печать десятичного числа в A (000..255)
LD L,A ;поместили
LD H,#00 ; число в HL
PUSH HL ;закинули печатаемое число на стек
LD HL,DECTB_W+4 ;адрес в таблице, начиная с сотни
LD B,#03 ;макс. возможное кол-во цифр - три
JR LP_PDW1 ;переход на цикл печати
;--------------------------------------------------------------;
PDEC_W ;Печать десятичного числа в HL (00000..65535)
PUSH HL ;закинули печатаемое число на стек
LD HL,DECTB_W ;адрес таблицы степеней десятки
LD B,#05 ;макс. возможное количество цифр
LP_PDW1 XOR A ;обнулили счетчик и флаг C для SBC
LD E,(HL) ;взяли текущую степень
INC HL ; десятки из таблицы
LD D,(HL) ; и поместили в DE
INC HL ;перешли к след. элементу таблицы
EX (SP),HL ;адрес эл-та <-> печатаемое число
LP_PDW2 INC A ;увеличиваем счетчик
SBC HL,DE ;вычитаем текущую степень десятки
JR NC,LP_PDW2 ;повторяем пока HL>=0
ADD HL,DE ;HL=HL mod DE; A=HL div DE
ADD A,"0"-1 ;перевод A в ASCII-код ("0".."9")
RST #10 ;печать десятичной цифры
EX (SP),HL ;HL=адрес эл-та, число -> на стек
DJNZ LP_PDW1 ;цикл по цифрам
POP HL ;убрали оставшийся ноль со стека
RET ;выход из процедуры
;--------------------------------------------------------------;
DECTB_W DW 10000,1000,100,10,1 ;Таблица степеней десятки ;
;--------------------------------------------------------------;
; print in HEX system: ;
;--------------------------------------------------------------;
PHEX_W ;Печать шестнадцатеричного числа в HL (0000..FFFF)
LD A,H ;печать старшего байта числа
CALL PHEX_B ;вызов процедуры HEX-печати байта
LD A,L ;печать младшего байта числа
;--------------------------------------------------------------;
PHEX_B ;Печать шестнадцатеричного числа в A (00..FF)
CALL PHEX_HB ;печать по полубайтам
;--------------------------------------------------------------;
PHEX_HB ;Печать старшего полубайта в A (0..F)
RRCA ;меняем
RRCA ; местами
RRCA ; старший
RRCA ; и младший полубайты
;--------------------------------------------------------------;
PHEX_LB ;Печать младшего полубайта в A (0..F)
PUSH AF ;сохранили A на стеке
AND #0F ;отбросили лишние старшие биты
CP #0A ;если A<10,
JR C,GO_PHL ; то перепрыгнули
ADD A,"A"-"9"-1 ;корректировка: после "9" идет "A"
GO_PHL ADD A,"0" ;перевод в ASCII-код
RST #10 ;печать HEX-цифры ("0".."F")
POP AF ;восстановили A со стека
RET ;выход из процедуры
;--------------------------------------------------------------;
; print in BIN system: ;
;--------------------------------------------------------------;
PBIN_W ;Печать двоичного числа в HL (16 разрядов 0/1)
LD A,H ;печать старшего байта числа
CALL PBIN_B ;вызов процедуры BIN-печати байта
LD A,L ;печать младшего байта числа
;--------------------------------------------------------------;
PBIN_B ;Печать двоичного числа в A (8 разрядов 0/1)
LD B,#08 ;в байте - 8 битов
LP_PBB RLCA ;поочередно 'выдвигаем' биты в CF
PUSH AF ;сохранить A на стеке
LD A,#18 ;в зависимости от флага C:
RLA ; A="0" или A="1"
RST #10 ;печать очередного бита
POP AF ;восстановить A со стека
DJNZ LP_PBB ;поразрядный цикл
RET ;выход из процедуры
;--------------------------------------------------------------;
; print in USER system: ;
;--------------------------------------------------------------;
PUSE_W ;Печать числа в установленной MKUSETB системе сч-я в HL
PUSH HL ;закинули печатаемое число на стек
HL_PUW LD HL,_NULL ;адрес конца таблицы степеней
LP_PUW1 DEC HL ;загружаем в DE очередную
LD D,(HL) ; степень числа
DEC HL ; из таблицы,
LD E,(HL) ; двигаясь 'сверху-вниз'
EX (SP),HL ;адрес таблицы <-> печат-е число
XOR A ;обнуление A и сброс флага C
LP_PUW2 INC A ;вычисляем очередной
SBC HL,DE ; разряд числа и результат
JR NC,LP_PUW2 ; помещаем в A (A=1+(HL div DE))
ADD HL,DE ;восстанавливаем (+) значение
ADD A,"0"-1 ;переводим A в ASCII-код
CP "9"+1 ;если код меньше "9",
JR C,GO_PUW1 ; то переход на печать
ADD A,"A"-"9"-1 ;коррекция (после "9" идет "A")
GO_PUW1 RST #10 ;печать очередной цифры числа
EX (SP),HL ;HL=адрес таблицы, число -> стек
DEC DE ;если степень
LD A,D ; числа не равна единице
OR E ; (самый первый элемент таблицы),
JR NZ,LP_PUW1 ; то продолжаем работу
POP HL ;убираем со стека ненужный 0
RET ;выход из процедуры
;--------------------------------------------------------------;
MKUSETB ;Создание таблицы степеней с основанием в A
LD HL,USE_TBL ;адрес создаваемой таблицы
LD DE,#0001 ;инициализация счетчика степени
LP_MUT1 LD (HL),E ;запись текущего
INC HL ; значения степени
LD (HL),D ; в таблицу и переход
INC HL ; к ее следующей ячейке
PUSH HL ;сохраняем адрес на стеке
LD B,A ;осн-е степени -> в счетчик цикла
LD HL,#0000 ;обнуление результата
LP_MUT2 ADD HL,DE ;подсчитываем результат
JR C,GO_MUT ;если HL>65535, то прерываем счет
DJNZ LP_MUT2 ;повторяем <основание> раз
EX DE,HL ;результат -> в DE (новая степень)
GO_MUT POP HL ;восстанавливаем адрес таблицы
JR NC,LP_MUT1 ;если не прерывались, то повторяем
LD (HL_PUW+1),HL ;адрес конца таблицы -> в PUSE_W
RET ;выход из процедуры
;--------------------------------------------------------------;
USE_TBL DS 32 ;Таблица степеней текущей системы счисления
;--------------------------------------------------------------;
; print in RIM system: ;
;--------------------------------------------------------------;
PRIM_B ;Печать числа в римской записи в A (I..CCLV)
LD L,A ;скопировать
LD H,#00 ; A в HL
;--------------------------------------------------------------;
PRIM_W ;Печать числа в римской записи в HL (I..MMMCMXCIX)
PUSH HL ;закинули печатаемое число на стек
LD HL,RIM_TBL ;адрес таблицы знаков
DB #DD ;работаем с половинкой регистра IX
LD L,#07 ;LD XL,число знаков в р. счислении
LP_PRW1 LD E,(HL) ;считываем из таблицы значение
INC HL ; очередного знака римской системы
LD D,(HL) ; счисления и помещаем в DE
INC HL ;затем в регистр C считываем
LD C,(HL) ; ASCII-код знака
INC HL ;переход к следующему знаку
LD (HL_PRW+1),HL ;сохранили адрес следующего знака
EX (SP),HL ;адрес -> на стек, HL=печат. число
XOR A ;обнуление счетчика и сброс CF
LP_PRW2 INC A ;в цикле производим
SBC HL,DE ; деление HL на DE (вычитаем,
JR NC,LP_PRW2 ; пока нет переполнения)
ADD HL,DE ;восстанавливаем (+) значение
DEC A ;т.к. A на 1 больше HL div DE,
JR Z,GO_PRW1 ; то если A=1 - ничего не печатаем
LD B,A ;количество печатаемых символов
LP_PRW3 LD A,C ;код знака
RST #10 ;печатаем его
DJNZ LP_PRW3 ;сколько надо - столько и печатаем
GO_PRW1 DB #DD ;работаем с половинкой регистра IX
LD A,L ;LD A,XL - номер текущего знака
DEC A ;если это последний знак (I), то
JR Z,GO_PRW4 ; двухбуквенного сочетания нет
EX (SP),HL ;HL=адрес след. знака, число->стек
RRA ;если номер текущего знака четный,
JR C,GO_PRW2 ; то сочетание с следующим знаком
INC HL ;иначе - перепрыгнуть через один
INC HL ; знак. В результате получим адрес
INC HL ; знака, для получения двойного
GO_PRW2 LD A,C ; знакосочетания (IV,CM,XL...)
LD C,(HL) ;взяли из таблицы значение для
INC HL ; этого знака и получили
LD B,(HL) ; разность между значением
INC HL ; основного и этим значением.
EX DE,HL ; Например, для основного знака X
AND A ; дополнительным будет I, а их
SBC HL,BC ; разность соответственно:
EX DE,HL ; 10-1=9, т.е. IX
LD C,A ;код основного знака
LD A,(HL) ;код дополнительного знака
EX (SP),HL ;HL=печатаемое число
SBC HL,DE ;если оно < дополнительного
JR C,GO_PRW3 ; значения, то не печатаем
RST #10 ;печать дополнительного знака
LD A,C ;основной знак
RST #10 ;печать
JR GO_PRW4 ;результат - двухбукв. сочетание
GO_PRW3 ADD HL,DE ;восстановили (+) у числа
GO_PRW4 EX (SP),HL ;число -> на стек
HL_PRW LD HL,_NULL ;адрес следующего знака в таблице
DB #DD ;работаем с половинкой регистра IX
DEC L ;DEC XL - уменьшаем счетчик знаков
JR NZ,LP_PRW1 ;крутим цикл, пока есть еще знаки
POP HL ;сняли ненужный уже ноль со стека
RET ;выход из процедуры
;--------------------------------------------------------------;
RIM_TBL ;Таблица значимости букв в написании римских чисел
DW 1000
DB "M" ;M=1000 ;CM=900
DW 500
DB "D" ;D=500 ;CD=400
DW 100
DB "C" ;C=100 ;XC=90
DW 50
DB "L" ;L=50 ;XL=40
DW 10
DB "X" ;X=10 ;IX=9
DW 5
DB "V" ;V=5 ;IV=4
DW 1
DB "I" ;I=1 ;-nop-
;--------------------------------------------------------------;
; end of PDES LIB 2.0 ;
;--------------------------------------------------------------;
Эта программа распечатывает двухбайтовое
шестнадцатиричное
число (#0000...#FFFF),
которое задается в регистре HL на входе в
процедуру. Печать выполняется при
помощи RST 16, поэтому перед ее
использованием должен быть открыт
канал печати
(для экрана: LD A,2:CALL #1601)
PRH LD C,#04
PH0 LD B,#04
XOR A
PH1 RL L
RL H
RLA
DJNZ PH1
CP #0A
JR C,PH2
ADD A,#07
PH2 ADD A,#30
RST 16
DEC C
JR NZ,PH0
RET
As this is one of the most typical tasks,
why not to do it tricky way?
The code snippet here takes a byte from (HL) and prints it.
Note that it uses another (shorter) DAA trick as we know that
Half Carry is cleared before DAA.
Input: HL = address of data ; Output: memory dump
Note: You'll have to replace the PRINT_CHAR macro by actual
platform-specific code.
Don't forget to preserve the contents of HL!
xor a
rld
call Nibble
Nibble push af
daa
add a,F0h
adc a,40h
PRINT_CHAR ; prints ASCII character in A
pop af
rld
ret
; Convert single byte in A to two Ascii characters in B and C
TO_HEX LD B,A ; Save the byte original value
AND $0F ; Strip off the high order nybble
CP $0A ; 0-9 at this point?
JR C,TOHX01 ; We only need to add $30 then, or
ADD A,$07 ; Seven more if it's in the A-F range
TOHX01 ADD A,$30 ; Take the value on up to ASCII
LD C,A ; Store the converted lo order character to C
LD A,B ; Get the original value back
RRCA ; Rotate it to the right
RRCA
RRCA
RRCA
AND $0F ; Mask off the upper trash
CP $0A ; 0-9 (< $0A)?
JR C,TOHX02 ; Only add $30 then
ADD A,$07 ; And add it 7 if it's in the A-F range
TOHX02 ADD A,$30 ; Finish the conversion to ASCII
LD B,A ; Load the finished hi order character to B
RET
;Number in hl to decimal ASCII
;Thanks to z80 Bits
;inputs: hl = number to ASCII
;example: hl=300 outputs '00300'
;destroys: af, bc, hl, de used
DispHL ld bc,-10000
call Num1
ld bc,-1000
call Num1
ld bc,-100
call Num1
ld c,-10
call Num1
ld c,-1
Num1: ld a,'0'-1
Num2: inc a
add hl,bc
jr c,Num2
sbc hl,bc
call prt
ret
;adaptable for a DispA routine
or any other 8-bit register
DispA ld h,0
ld l,a
jp DispHL
;Display a 16- or 8-bit number in hex.
DispHLhex:
; Input: HL
ld c,h
call OutHex8
ld c,l
OutHex8:
; Input: C
ld a,c
rra
rra
rra
rra
call Conv
ld a,c
Conv:
and $0F
add a,$90
daa
adc a,$40
daa
call prt
ret
;ASCII to DEC
;HL-адрес числа
;Выход:DE-число
LD HL,TXT
CALL MakeNumber
RET
MakeNumber
LD DE,0
MkNum1 LD A,(HL)
CALL isdigit
RET NC
EX DE,HL
ADD HL,HL
LD B,H
LD C,L
ADD HL,HL
ADD HL,HL
ADD HL,BC
LD B,0
SUB "0"
LD C,A
ADD HL,BC
EX DE,HL
INC HL
JR MkNum1
isdigit CP "0"
CCF
RET NC
CP "9"+1
RET
TXT DB "768QWEIUSO"
Hexadecimal conversion operates directly
on nibbles and takes advantage of nifty DAA trick.
Input:
HL = number to convert,
DE = location of ASCII string
Output:
ASCII string at (DE)
Num2Hex ld a,h
call Num1
ld a,h
call Num2
ld a,l
call Num1
ld a,l
jr Num2
Num1 rra
rra
rra
rra
Num2 or F0h
daa
add a,A0h
adc a,40h
ld (de),a
inc de
ret
NUMBUFF DEFB "00000"
A2DEC8 ld hl,NUMBUFF
ld (hl),#2f
inc (hl)
sub 100
jr nc,$-3
add a,100
inc hl
ld (hl),#2f
inc (hl)
sub 10
jr nc,$-3
add a,10
inc hl
add a,"0"
ld (hl),a
ret
;Write a Hex number in BCHL, CHL, HL or A
;to memory at DE.
sphex8: ld a,b;Big multidecker fall-through...
call sphex2
sphex6: ld a,c
call sphex2
sphex4: ld a,h
call sphex2
ld a,l
sphex2: push af
rrca
rrca
rrca
rrca
call sphex1
pop af
sphex1: and 0fh
cp 0ah
jr c,sph1a
add a,7
sph1a: add a,'0'
ld (de),a
inc de
ret
;Write decimal numbers in HL to memory at DE
cphlbc: push hl
and a
sbc hl,bc
pop hl
ret
spdec: ld bc,10000
call cphlbc
jr nc,spdec5
ld bc,1000
call cphlbc
jr nc,spdec4
ld bc,100
call cphlbc
jr nc,spdec3
ld a,l
cp 10
jr nc,spdec2 ;>=10
jr spdec1
spdec5: ld bc,10000
call dodec
spdec4: ld bc,1000
call dodec
spdec3: ld bc,100
call dodec
spdec2: ld bc,10
call dodec
spdec1: ld a,l
add a,'0'
ld (de),a
inc de
ret
dodec: ld a,'0'
ddlp: inc a
and a
sbc hl,bc
jr nc,ddlp
dec a
add hl,bc
ld (de),a
inc de
ret
;
;by GRAND, 1999
;Печ.числа (0...65535)
;с незн нулями
;Вх.: HL - число.
PR16D XOR A
LD DE,10000
SBC HL,DE
INC A
JR NC,$-3
ADD A,"0"-1
CALL PRINTA
ADD HL,DE
XOR A
LD DE,1000
SBC HL,DE
INC A
JR NC,$-3
ADD A,"0"-1
CALL PRINTA
ADD HL,DE
;Печ.числа (0...255) с незн нулями
;Вх.: HL - число.
PR8D XOR A
LD DE,100
SBC HL,DE
INC A
JR NC,$-3
ADD A,"0"-1
CALL PRINTA
ADD HL,DE
;Печ.числа (0...99) с незн нулями
;Вх.: HL - число.
LD A,L
LD B,"0"-1
INC B
SUB 10
JR NC,$-3
ADD A,"0"+10
LD C,A
LD A,B
CALL PRINTA
LD A,C
PRINTA.....print proc here
; Routine for converting a 24-bit binary number to decimal
; In: E:HL = 24-bit binary number (0-16777215)
; Out: DE:HL = 8 digit decimal form (packed BCD)
; Changes: AF, BC, DE, HL & IX
; by Alwin Henseler
LD C,E
PUSH HL
POP IX ; input value in C:IX
LD HL,1
LD D,H
LD E,H ; start value corresponding with 1st 1-bit
LD B,24 ; bitnr. being processed + 1
FIND1: ADD IX,IX
RL C ; shift bit 23-0 from C:IX into carry
JR C,NEXTBIT
DJNZ FIND1 ; find highest 1-bit
; All bits 0:
RES 0,L ; least significant bit not 1 after all ..
RET
DBLLOOP: LD A,L
ADD A,A
DAA
LD L,A
LD A,H
ADC A,A
DAA
LD H,A
LD A,E
ADC A,A
DAA
LD E,A
LD A,D
ADC A,A
DAA
LD D,A ; double the value found so far
ADD IX,IX
RL C ; shift next bit from C:IX into carry
JR NC,NEXTBIT ; bit = 0 -> don't add 1 to the number
SET 0,L ; bit = 1 -> add 1 to the number
NEXTBIT: DJNZ DBLLOOP
RET
ПРЕОБРАЗОВАНИЕ ДВОИЧНОГО ЧИСЛА В ДВОИЧНО-ДЕСЯТИЧНОЕ.
Для отображения результатов вычислений необходимо числа
привести в удобный для вывода вид, т.е. преобразовать число в
десятичный код. Например, двоичное число 00001111 (#0F) удобно
представить в виде 0001 0101(#15), т.е. преобразовать его в
двоично-десятичную форму. Этим занимается следующая подпрограмма.
; BCD2B- подпрограмма перевода 2-х байтного числа.
; Двоичное число должно быть в регистре 'HL'.
; Результат :
; в 'A' - десятки тысяч;
; в 'B' - тысячи и сотни;
; в 'C' - десятки и единицы.
; Во время работы вызывается процедура CONV из
; подпрограммы BCD1B - перевод однобайтного числа,
; в которой;
; в 'H' - число для перевода , 'L'=0
; Результат работы: в 'A' - разряды сотен;
; в 'B' - десятки и единицы
; Вход в процедуру преобразования 2-х байтного числа:
BCD2B LD E,17 ; счетчик 1-го цикла
CALL CONV ; вычислить младший BCD байт
LD C,A ; сохранить в 'C'
LD E,17 ; счетчик 2-го цикла
JR PRODOL ; переход на вычисление
; процедура для преобразования 1-байтного числа
BCD1B LD E,9 ; цикл для 1-байтного числа
PRODOL CALL CONV ; вычислить два старших байта
LD B,A ; сохранить средний байт
LD A,L ; старший байт
RET ; выйти вообще
;-----
CONV XOR A ; очистить
SBIT DEC E ; уменьшим счетчик цикла
RET Z ; цикл весь - выйти
ADD HL,HL ; сдвинем старшие разряды в перенос
ADC A,A
DAA ; скорректируем
JR NC,SBIT ; результат больше 99 ?
INC HL ; да - увеличим на 1
JR SBIT ; вернемся в вычисления
from PENTIS by Tony Raugas print DEC
print_digits exx
ld hl, txt_00000
ld b, 5
prd1 ld (hl), #30
inc hl
djnz prd1
exx
ld b, #10
prd2 exx
ld hl, txt_00000+4
ld bc, #500
prd3 ld a, (hl)
add a, a
add a, c
sub #30
cp #3A
jr c, prd4
sub #A
ld c, 1
jr prd5
prd4 ld c, 0
prd5 ld (hl), a
dec hl
djnz prd3
exx
add hl, hl
exx
jr nc, prd7
ld hl, txt_00000+4
ld b, 5
prd6 ld a, (hl)
inc a
ld (hl), a
cp #3A
jr c, prd7
ld (hl), #30
dec hl
djnz prd6
prd7 exx
djnz prd2
ld hl, txt_00000
jp prtpz
txt_00000 db "00000",0
Печать десятичных чисел без нуля. 59 байт. print DEC w/o zeros
LD IX,Буфер 5 байт
LD HL,Число
LD BC,0
LD DE,0-10000
CALL num
LD DE,0-1000
CALL num
LD DE,0-100
CALL num
LD DE,0-10
CALL num
LD A,L
JR num2
num ADD HL,DE
INC B
JR C,$-2
DEC B
SBC HL,DE
LD A,B
OR C
RET Z
LD C,A
LD A,B
num2 ADD A,48
LD (IX),A
INC IX
LD B,0
RET
CISLO ld de,-10000 ;rad desetitisicu
call CIFRA
ld de,-1000 ;rad tisicu
call CIFRA
ld de,-100 ;rad stovek
call CIFRA
ld de,-10 ;rad desitek
call CIFRA
ld de,-1 ;rad jednicek
CIFRA ld a,'0'-1 ;do A znak 0 zmenseny o 1
CIFRA1 add hl,de ;pricti zaporny rad
inc a ;zvys pocitadlo
jr c,CIFRA1 ;jeste jsme jej neprekrocili
sbc hl,de ;pokud ano, oprav cislo
; call ZNAK ;a vytiskni pocitadlo
ret
NUM2DEC: ld e,' '
ld bc,-10000
call NUM1
ld bc,-1000
call NUM1
ld bc,-100
call NUM1
ld c,-10
call NUM1
ld e,'0'
ld c,b
NUM1: ld a,'0'-1
NUM2: inc a
add hl,bc
jr c,NUM2
sbc hl,bc
cp '0'
jr nz,NUM4
ld a,e
NUM3: jp ZNAK2
NUM4: ld e,'0'
jr NUM3
Input: HL = number to convert, DE = location of ASCII string
Output: ASCII string at (DE) ; Num2Dec ld bc,-10000
call Num1
ld bc,-1000
call Num1
ld bc,-100
call Num1
ld c,-10
call Num1
ld c,b
Num1 ld a,'0'-1
Num2 inc a
add hl,bc
jr c,Num2
sbc hl,bc
ld (de),a
inc de
ret
;in: HL
DECWORD LD IY,DECWTAB
XOR A
LD B,4
DECW3 LD E,(IY)
INC IY
LD D,(IY)
INC IY
DECW2 OR A
SBC HL,DE
JR C,DECW1
INC A
JR DECW2
DECW1 ADD HL,DE
CALL DECWPC
DJNZ DECW3
LD A,L
OR A,#30
JR PRCHAR
DECWPC OR A
RET Z
PUSH HL
PUSH BC
OR #30
CALL PRCHAR
LD A,#30
POP BC
POP HL
RET
DECWTAB DEFW 10000,1000,100,10
;in: HL
HEXWORD PUSH HL
LD A,H
CALL HEXBYTE
POP HL
LD A,L
;in: A
HEXBYTE PUSH AF
RRCA
RRCA
RRCA
RRCA
CALL HEXHALF
POP AF
HEXHALF AND #0F
CP #0A
JR C,HEXH1
ADD A,#07
HEXH1 ADD A,#30
; CALL PRCHAR
; Convert HEX ASCII characters in B C registers to a byte value in A
HEX2BIN LD A,B ; Move the hi order byte to A
SUB $30 ; Take it down from Ascii
CP $0A ; Are we in the 0-9 range here?
JR C,HX2B01 ; If so, get the next nybble
SUB $07 ; But if A-F, take it down some more
HX2B01 RLCA ; Rotate the nybble from low to high
RLCA ; One bit at a time
RLCA ; Until we
RLCA ; Get there with it
LD B,A ; Save the converted high nybble
LD A,C ; Now get the low order byte
SUB $30 ; Convert it down from Ascii
CP $0A ; 0-9 at this point?
JR C,HX2B02 ; Good enough then, but
SUB $07 ; Take off 7 more if it's A-F
HX2B02 ADD A,B ; Add in the high order nybble
RET
; Convert single byte in A to two Ascii characters in B and C
TO_HEX LD B,A ; Save the byte original value
AND $0F ; Strip off the high order nybble
CP $0A ; 0-9 at this point?
JR C,TOHX01 ; We only need to add $30 then, or
ADD A,$07 ; Seven more if it's in the A-F range
TOHX01 ADD A,$30 ; Take the value on up to ASCII
LD C,A ; Store the converted lo order character to C
LD A,B ; Get the original value back
RRCA ; Rotate it to the right
RRCA
RRCA
RRCA
AND $0F ; Mask off the upper trash
CP $0A ; 0-9 (< $0A)?
JR C,TOHX02 ; Only add $30 then
ADD A,$07 ; And add it 7 if it's in the A-F range
TOHX02 ADD A,$30 ; Finish the conversion to ASCII
LD B,A ; Load the finished hi order character to B
RET ;
;CLCN32 -> Converts 32Bit-Value in ASCII-String (terminated by 0)
;Input DE,IX=32bit value, IY=destination address
;Output IY=last char in destination string
;Destroyed AF,BC,DE,HL,IX
clcn32t dw 1,0, 10,0, 100,0, 1000,0, 10000,0
dw #86a0,1, #4240,#f, #9680,#98, #e100,#5f5, #ca00,#3b9a
clcn32z ds 4
clcn32 ld (clcn32z),ix
ld (clcn32z+2),de
ld ix,clcn32t+36
ld b,9
ld c,0
clcn321 ld a,"0"
or a
clcn322 ld e,(ix+0):ld d,(ix+1):ld hl,(clcn32z): sbc hl,de:ld (clcn32z),hl
ld e,(ix+2):ld d,(ix+3):ld hl,(clcn32z+2):sbc hl,de:ld (clcn32z+2),hl
jr c,clcn325
inc c
inc a
jr clcn322
clcn325 ld e,(ix+0):ld d,(ix+1):ld hl,(clcn32z): add hl,de:ld (clcn32z),hl
ld e,(ix+2):ld d,(ix+3):ld hl,(clcn32z+2):adc hl,de:ld (clcn32z+2),hl
ld de,-4
add ix,de
inc c
dec c
jr z,clcn323
ld (iy+0),a
inc iy
clcn323 djnz clcn321
ld a,(clcn32z)
add "0"
ld (iy+0),a
ld (iy+1),0
ret
; Combined routine for conversion of different sized binary numbers into
; directly printable ASCII(Z)-string
; Input value in registers, number size and -related to that- registers to fill
; is selected by calling the correct entry:
; entry inputregister(s) decimal value 0 to:
; B2D8 A 255 (3 digits)
; B2D16 HL 65535 5 "
; B2D24 E:HL 16777215 8 "
; B2D32 DE:HL 4294967295 10 "
; B2D48 BC:DE:HL 281474976710655 15 "
; B2D64 IX:BC:DE:HL 18446744073709551615 20 "
; The resulting string is placed into a small buffer attached to this routine,
; this buffer needs no initialization and can be modified as desired.
; The number is aligned to the right, and leading 0's are replaced with spaces.
; On exit HL points to the first digit, (B)C = number of decimals
; This way any re-alignment / postprocessing is made easy.
; Changes: AF,BC,DE,HL,IX
; P.S. some examples below
; by Alwin Henseler
B2D8: LD H,0
LD L,A
B2D16: LD E,0
B2D24: LD D,0
B2D32: LD BC,0
B2D48: LD IX,0 ; zero all non-used bits
B2D64: LD (B2DINV),HL
LD (B2DINV+2),DE
LD (B2DINV+4),BC
LD (B2DINV+6),IX ; place full 64-bit input value in buffer
LD HL,B2DBUF
LD DE,B2DBUF+1
LD (HL)," "
B2DFILC: EQU $-1 ; address of fill-character
LD BC,18
LDIR ; fill 1st 19 bytes of buffer with spaces
LD (B2DEND-1),BC ;set BCD value to "0" & place terminating 0
LD E,1 ; no. of bytes in BCD value
LD HL,B2DINV+8 ; (address MSB input)+1
LD BC,#0909
XOR A
B2DSKP0: DEC B
JR Z,B2DSIZ ; all 0: continue with postprocessing
DEC HL
OR (HL) ; find first byte <>0
JR Z,B2DSKP0
B2DFND1: DEC C
RLA
JR NC,B2DFND1 ; determine no. of most significant 1-bit
RRA
LD D,A ; byte from binary input value
B2DLUS2: PUSH HL
PUSH BC
B2DLUS1: LD HL,B2DEND-1 ; address LSB of BCD value
LD B,E ; current length of BCD value in bytes
RL D ; highest bit from input value -> carry
B2DLUS0: LD A,(HL)
ADC A,A
DAA
LD (HL),A ; double 1 BCD byte from intermediate result
DEC HL
DJNZ B2DLUS0 ; and go on to double entire BCD value (+carry!)
JR NC,B2DNXT
INC E ; carry at MSB -> BCD value grew 1 byte larger
LD (HL),1 ; initialize new MSB of BCD value
B2DNXT: DEC C
JR NZ,B2DLUS1 ; repeat for remaining bits from 1 input byte
POP BC ; no. of remaining bytes in input value
LD C,8 ; reset bit-counter
POP HL ; pointer to byte from input value
DEC HL
LD D,(HL) ; get next group of 8 bits
DJNZ B2DLUS2 ; and repeat until last byte from input value
B2DSIZ: LD HL,B2DEND ; address of terminating 0
LD C,E ; size of BCD value in bytes
OR A
SBC HL,BC ; calculate address of MSB BCD
LD D,H
LD E,L
SBC HL,BC
EX DE,HL ; HL=address BCD value, DE=start of decimal value
LD B,C ; no. of bytes BCD
SLA C ; no. of bytes decimal (possibly 1 too high)
LD A,"0"
RLD ; shift bits 4-7 of (HL) into bit 0-3 of A
CP "0" ; (HL) was > 9h?
JR NZ,B2DEXPH ; if yes, start with recording high digit
DEC C ; correct number of decimals
INC DE ; correct start address
JR B2DEXPL ; continue with converting low digit
B2DEXP: RLD ; shift high digit (HL) into low digit of A
B2DEXPH: LD (DE),A ; record resulting ASCII-code
INC DE
B2DEXPL: RLD
LD (DE),A
INC DE
INC HL ; next BCD-byte
DJNZ B2DEXP ; and go on to convert each BCD-byte into 2 ASCII
SBC HL,BC ; return with HL pointing to 1st decimal
RET
B2DINV: DS 8 ; space for 64-bit input value (LSB first)
B2DBUF: DS 20 ; space for 20 decimal digits
B2DEND: DS 1 ; space for terminating 0
END
EXAMPLES
(In these examples, it is assumed there exists a subroutine PRINT, that
prints a string (terminated by a 0-byte) starting at address [HL] )
Print 1 byte, as follows:
20
7
145 etc.
by: LD A,byte
CALL B2D8
LD HL,B2DEND-3
CALL PRINT
Print a 24-bit value, as follows:
9345
76856366
534331
by: LD E,bit23-16
LD HL,bit15-0
CALL B2D24
CALL PRINT
Print a 48-bit value, like
14984366484
49
123456789012345
3155556 etc.
by:
LD BC,bit47-32
LD DE,bit31-16
LD HL,bit15-0
CALL B2D48
LD HL,B2DEND-15
CALL PRINT
....... ETC , ETC , ETC , ......
;Converts a single ASCII hex char to a nybble value
;Pass char in reg A. Letter numerals must be upper case.
;Return nybble value in low-order reg A with zeros in high-order nybble if no error.
;Return 0ffh in reg A if error (char not a valid hex numeral).
;Also uses b, c, and hl registers.
hex_char_to_nybble: ld hl,hex_char_table
ld b,00fh ;no. of valid characters in table - 1.
ld c,000h ;will be nybble value
hex_to_nybble_loop: cp (hl) ;character match here?
jp z,hex_to_nybble_ok ;match found, exit
dec b ;no match, check if at end of table
jp m,hex_to_nybble_err ;table limit exceded, exit with error
inc c ;still inside table, continue search
inc hl
jp hex_to_nybble_loop
hex_to_nybble_ok: ld a,c ;put nybble value in a
ret
hex_to_nybble_err: ld a,0ffh ;error value
ret
;
;Converts a hex character pair to a byte value
;Called with location of high-order char in HL
;If no error carry flag clear, returns with byte value in register A, and
;HL pointing to next mem location after char pair.
;If error (non-hex char) carry flag set, HL pointing to invalid char
hex_to_byte: ld a,(hl) ;location of character pair
push hl ;store hl (hex_char_to_nybble uses it)
call hex_char_to_nybble
pop hl ;returns with nybble value in a reg, or 0ffh if error
cp 0ffh ;non-hex character?
jp z,hex_to_byte_err;yes, exit with error
sla a ;no, move low order nybble to high side
sla a
sla a
sla a
ld d,a ;store high-nybble
inc hl ;get next character of the pair
ld a,(hl)
push hl ;store hl
call hex_char_to_nybble
pop hl
cp 0ffh ;non-hex character?
jp z,hex_to_byte_err;yes, exit with error
or d ;no, combine with high-nybble
inc hl ;point to next memory location after char pair
scf
ccf ;no-error exit (carry = 0)
ret
hex_to_byte_err: scf ;error, carry flag set
ret
hex_char_table: defm "0123456789ABCDEF";ASCII hex table
CRC
;crc16 ccitt на Z80
;hl - from
;b - how many
;de - crc value
;by LVD'2005
crc16:
ld de,#ffff
loop:
ld a,d
ld d,e
xor (hl)
inc hl
ld e,a
rrca
rrca
rrca
rrca
and #0f
xor e
ld e,a
rrca
rrca
rrca
ld c,a
rrca
and #f0
xor d
ld d,a
ld a,c
and #1f
xor d
ld d,a
ld a,c
and #e0
xor e
ld e,a
djnz loop
ret
16-bit CRC-CCITT
The following routine calculates standard CRC-CCITT bit-by-bit using polynomial 1021h.
Another common scheme CRC-16 uses polynomial A001h and starts with value 0
(so it's likely that you misinterpret bunch of zeros as valid data).
It might be useful to extend the code to use 16-bit byte counter.
Input: DE = address of input data, C = number of bytes to process ; Output: HL = CRC
Crc16 ld hl,FFFFh
Read ld a,(de)
inc de
xor h
ld h,a
ld b,8
CrcByte add hl,hl
jr nc,Next
ld a,h
xor 10h
ld h,a
ld a,l
xor 21h
ld l,a
Next djnz CrcByte
dec c
jr nz,Read
ret
from tomdalby
Small project to create Cyclic Redundancy Check for the ZX Spectrum in Z80 machine code.
Started with a simple CRC-8 (9bit) routine using the CCITT
(Comité Consultatif International Téléphonique et Télégraphique)
polynominal x8+x2+x+1. For binary data x=2,
so the polynominal translates to 28+22+2+1 = 256+4+2+1 = 263 which in binary is 00000001 00000111.
For this implementation the high-order bit (9th bit), which is always 1,
is omitted so it becomes 00000111, 7 in decimal or 0x07 in hex.
;; =====================================================================
;; input - hl=start of memory to check, de=length of memory to check
;; returns - a=result crc
;; 20b
;; =====================================================================
_crc8b:
xor a ; 4t - initial value of crc=0 so first byte can be XORed in (CCITT)
ld c,$07 ; 7t - c=polyonimal used in loop (small speed up)
_byteloop8b:
xor (hl) ; 7t - xor in next byte, for first pass a=(hl)
inc hl ; 6t - next mem
ld b,8 ; 7t - loop over 8 bits
_rotate8b:
add a,a ; 4t - shift crc left one
jr nc,_nextbit8b ; 12/7t - only xor polyonimal if msb set (carry=1)
xor c ; 4t - CRC8_CCITT = 0x07
_nextbit8b:
djnz _rotate8b ; 13/8t
ld b,a ; 4t - preserve a in b
dec de ; 6t - counter-1
ld a,d ; 4t - check if de=0
or e ; 4t
ld a,b ; 4t - restore a
jr nz,_byteloop8b; 12/7t
ret ; 10t
from tomdalby
CRC-8 is optimal (unique) for a block length of 29-1 or up to 64bytes and
longer blocks may produce the same CRC value.
Moving to CRC-16 again using CCITT polynominal (as used in Bluetooth)
x16+x12+x5+1 = 00000001 00010000 00100001 in binary.
With high-order bit omitted we get 00010000 00100001 or 4129 decimal, 0x1021 hex.
;; =====================================================================
;; input - de=start of memory to check, bc=length of memory to check
;; returns - hl=result crc
;; =====================================================================
_crc16:
ld hl,$ffff ; 10t - initial crc=$ffff
_byte16:
push bc ; 11t - preserve counter
ld a,(de) ; 7t - get byte
inc de ; 6t - next mem
xor h ; 4t - xor byte into crc high byte
ld h,a ; 4t - back into high byte
ld b,8 ; 7t - rotate 8 bits
_rotate16:
add hl,hl ; 11t - rotate crc left one
jr nc,_nextbit16 ; 12/7t - only xor polyonimal if msb set
ld a,h ; 4t
xor $10 ; 7t - high byte with $10
ld h,a ; 4t
ld a,l ; 4t
xor $21 ; 7t - low byte with $21
ld l,a ; 4t - hl now xor $1021
_nextbit16:
djnz _rotate16 ; 13/8t - loop over 8 bits
pop bc ; 10t - bring back main counter
dec bc ; 6t
ld a,b ; 4t
or c ; 4t
jr nz,_byte16 ; 12/7t
ret ; 10t
CRC-16 is optimal (unique) for block length of 217-1 or up to 16,384bytes.
CRC-32 was a bit more tricky
CRC подсчитывается следующей программой:
;Входные параметры:
;HL - начальный адрес блока
;DE - конечный адрес блока
;Выходные данные:
;BC - контрольная сумма блока
;from Directory System for TR-DOS
;TR-DOS Navigator v0.69b by Юдин С.А.
CRC PUSH HL
INC DE
LD BC,#0000
CRC1 PUSH DE
LD A,C
XOR (HL)
LD E,A
PUSH BC
PUSH HL
LD BC,#0000
LD D,#08
CRC2 PUSH BC
LD A,C
RRA
LD A,B
RRA
LD B,A
LD A,C
RRA
LD C,A
POP HL
LD A,E
XOR L
AND 1
JR Z,CRC3
LD A,B
XOR #A0
LD B,A
LD A,C
XOR #01
LD C,A
CRC3 LD A,E
RRCA
AND #7F
LD E,A
DEC D
JR NZ,CRC2
POP HL
POP DE
LD A,D
XOR C
LD C,A
LD A,E
XOR B
LD B,A
INC HL
POP DE
LD A,H
CP D
JR NZ,CRC1
LD A,L
CP E
JR NZ,CRC1
DEC DE
POP HL
RET
;from deja vue #9
;STORM
;VERY FAST CRC16 WITH TABLE #200.
; ИНСТАЛЛЯЦИЯ ПРОЦЕДУРЫ ВЫЧИСЛЕНИЯ CRC
; (ПОДГОТОВКА CRCTBL)
CRCINI LD HL,CRCTBL
LD C,#10
CRCINI0 LD D,L
LD E,0
LD B,8
EX DE,HL
CRCINI1 ADD HL,HL
JR NC,CRCINI2
LD A,C
XOR H
LD H,A
LD A,#21
XOR L
LD L,A
CRCINI2 DJNZ CRCINI1
EX DE,HL
LD (HL),D
INC H
LD (HL),E
DEC H
DEC L
JR NZ,CRCINI0
RET
;IN: HL-FROM,BC-LEN
;OUT: DE-CRC
CRCCALC LD DE,0
CRCUPD LD A,C
LD C,B
LD B,A
OR A
JR Z,$+3
INC C
LD A,D
XOR (HL)
EXX
LD H,CRCTBL[+1
LD B,CRCTBL[
LD C,A
LD A,(BC)
EXX
XOR E
EXX
JR CRCTO
CRCLOOP XOR (HL)
EXX
LD C,A
LD A,(BC)
XOR (HL)
CRCTO LD L,C
EXX
INC HL
DJNZ CRCLOOP
DEC C
JR NZ,CRCLOOP
EXX
LD E,(HL)
LD D,A
RET
CRCTBL EQU $^;[#200]
;STORM
;CRC16 (SLOW)
;IN: HL-FROM;BC-LEN.
;OUT: DE-CRC
CRCCALC LD DE,0
CRCUPD EX DE,HL
INC C
DEC C
JR Z,$+3
INC B
PUSH BC
POP IX
LD C,#10
CRC16C LD A,(DE)
XOR H
LD H,A
LD B,8
CRC16A ADD HL,HL
JR NC,CRC16B
LD A,L
XOR #21
LD L,A
LD A,H
XOR C
LD H,A
CRC16B DJNZ CRC16A
INC DE
DEC LX
JR NZ,CRC16C
DEC HX
JR NZ,CRC16C
EX DE,HL
RET
Операции с байтами
COMPARE
.---------------------------------.
¦Результат ¦ Состояние ¦ Мнемоника¦
¦сравнения ¦ флагов ¦ условия ¦
+----------+-----------+----------+
¦ A=X ¦ Z=1 ¦ Z ¦
+----------+-----------+----------+
¦ A<>X ¦ Z=0 ¦ NZ ¦
+----------+-----------+----------+
¦ Беззнаковое сравнение (0...255) ¦
+----------T-----------T----------+
¦ A<X ¦ CY=1 ¦ C ¦
+----------+-----------+----------+
¦ A>=X ¦ CY=0 ¦ NC ¦
+----------+-----------+----------+
¦ С учетом знака (-128...+127) ¦
+----------T-----------T----------+
¦ A<X ¦ S=1 ¦ P ¦
+----------+-----------+----------+
¦ A>=X ¦ S=0 ¦ M ¦
'----------+-----------+----------'
Пояснения:
1) Таблица показывает, какие флаги установятся при равенстве или отличии
операндов в любую сторону.
2) В графе "Мнемоника условия" описываются проверяемые флаги при вводе
команд условного типа.
3) Обозначения в таблице:
CY=C=Carry
NZ=(Z=0)
NC=(C=0)
Z80 IF-ELSE ; andreadrian.de
To convert a C language if() statement into Z80 assembler is quite nasty in detail:
First, there is only a 8-bit compare op-code in the Z80. To compare larger numbers a subtraction has to be done.
Second, the conditional jump op-code is different for unsigned and signed numbers.
Third, the jump goes to the "else" part of the statement,
therefore the jump op-code is the logical opposite of the compare operator
Fourth, at the end of the if-part an unconditional jump is needed to the end of the IF-ELSE block.
All these nasty details fit into one table. Never write assembler programs without this table!
.----------------.------------------.-------------------------------.-------------------------snd.
| C test | as subtraction | unsigned numbers "else jump" | signed numbers "else jump" |
|----------------X------------------X-------------------------------X----------------------------|
|if (a >= b) | if (a-b >= 0) | JP C or JR C | JP PO |
|if (a < b) | if (a-b < 0) | JP NC or JR NC | JP PE |
|if (a == b) | if (a-b == 0) | cascaded JP NZ or JR NZ | cascaded JP NZ or JR NZ |
'----------------'------------------'-------------------------------'----------------------------'
Note: JP PO is jump on overflow. The overflow flag is the carry
flag for signed numbers. In the Z80 the overflow and parity
flag are the same. The official name of JP PO is jump on parity odd.
As they say "just to confuse the russians".
As example see the assembler listing for the following C code fragment.
short HL, DE;
char A;
HL = -2000;
DE = -1000;
if (HL >= DE) {
A = 1;
} else {
A = 0;
}
Before you run the assembler code below, what is the value
of A at the end of the C language listing? Even if you are sure,
you may run a C program to confirm your believes.
In the assembler code we use registers A, DE and HL instead of memory locations.
The C source is given as comment.
;==================================================
; IF-ELSE CODE FRAGMENT
LD HL,-2000 ; HL = -2000;
LD DE,-1000 ; DE = -1000;
AND A ; if (HL >= DE) {
SBC HL,DE
JP PO,ELSE
LD A,1 ; A = 1;
JR ENDIF
ELSE: ; } else {
LD A,0 ; A = 0;
ENDIF: ; }
HALT
END
Note: The AND A op-code clears the carry flag.
Because of this, the SBC HL,DE op-code works
like SUB HL,DE - an op-code the Z80 does not have.
The following assembler listing shows the cascaded if
(a == b) translation. Because the state of the zero flag does not
propagate like the carry and overflow flag, after each
subtraction a conditional jump is necessary.
The C listing is:
long HLHL, DEDE;
char A;
HLHL = 0x12345678;
DEDE = 0x12340000;
if (HLHL == DEDE) {
A = 1;
} else {
A = 0;
}
The assembler listing is:
;==================================================
; IF-ELSE CODE FRAGMENT 2
LD HL,05678H ; H'L'HL = 0x12345678;
LD DE,00000H ; D'E'DE = 0x12340000;
EXX
LD HL,01234H
LD DE,01234H
EXX
AND A ; if (HLHL == DEDE) {
SBC HL,DE
JR NZ,ELSE
EXX
SBC HL,DE
EXX
JR NZ,ELSE
LD A,1 ; A = 1;
JR ENDIF
ELSE ; } else {
LD A,0 ; A = 0;
ENDIF ; }
HALT
END
Note: What happens if the first JR NZ,ELSE is deleted?
C (Bit 0)
Carry flag. It is set or cleared depending on the operation is performed.
For ALU operations, it signs carry (e.g., ADD) or borrow (e.g., SUB).
For bit shift and rotate operations, it stores the least/most significant
bit after an operation. For the logical instructions AND, OR, and XOR,
the Carry flag is reset.
N (Bit 1)
Add/Subtract flag. This flag is used by the Decimal Adjust Accumulator
instruction (DAA) to distinguish between the ADD and SUB instructions.
For ADD instructions, N is cleared to 0. For SUB instructions, N is set to 1.
P/V (Bit 2)
Parity/Overflow flag. This flag is set to a specific state depending
on the operation being performed. For arithmetic operations, this flag
indicates an overflow condition when the result in the Accumulator
is greater than the maximum possible number (+127) or is less than
the minimum possible number (–128). This overflow condition is determined
by examining the sign bits of the operands.
H (Bit 4)
Half Carry flag. This flag is set or cleared depending on the carry
and borrow status between bits 3 and 4 of an 8-bit arithmetic operation.
This flag is used by the Decimal Adjust Accumulator (DAA) instruction
to correct the result of a packed BCD add or subtract operation.
Z (Bit 6)
Zero flag. It is set if the result generated by the execution of
certain instructions is 0; otherwise, it is reset.
S (Bit 7)
Sign flag. It stores the state of the most-significant bit of
the Accumulator (bit 7).
COMPARE CP N
Unsigned
If A == N, then Z flag is set.
If A != N, then Z flag is reset.
If A < N, then C flag is set.
If A >= N, then C flag is reset.
Signed
If A == N, then Z flag is set.
If A != N, then Z flag is reset.
If A < N, then S and P/V are different.
A >= N, then S and P/V are the same.
.--------------------------------------------------.
| if Val2>=Val1 then goto label | >= | JP NC,label |
| if Val2<Val1 then goto label | < | JP C,label |
| if Val2=Val1 then goto label | = | JP Z,label |
| if Val2<>Val1 then goto label | <> | JP NZ,label |
'--------------------------------------------------'
; set Carry flag
scf
; reset Carry flag (alters Sign and Zero flags as defined)
or a
; alternate reset Carry flag (alters Sign and Zero flags as defined)
and a
; set Zero flag (resets Carry flag, alters Sign flag as defined)
cp a
; reset Zero flag (alters a, reset Carry flag, alters Sign flag as defined)
or 1
; set Sign flag (negative) (alters a, reset Zero and Carry flags)
or $80
; reset Sign flag (positive) (set a to zero, set Zero flag, reset Carry flag)
xor a
;Set parity/overflow (even):
xor a
;Reset parity/overflow (odd):
sub a
;Set half carry (hardly ever useful but still...)
and a
;Reset half carry (hardly ever useful but still...)
or a
;Set bit 5 of f:
or %00100000
;get the zero flag in carry
scf
jr z,$+3
ccf
;Put carry flag into zero flag.
ccf
sbc a, a
;Signed Compare D to E!
;Returns carry and zero flag as expected
;Destroys A.
ld a,d
sub e
sbc a,a
xor e
xor d
rla
COMPARE lvd
;LD C,4 ;длина
;LD B,0 ;ноль
LD BC,длинна
LD HL,M1;это сравниваем
LD DE,M2;с этим
COMPAR1 LD A,(DE)
INC DE
CPI
JR NZ,NE_SOVPALO ;ret nz
JP PE,COMPAR1
SOVPALO ;ret
;from Fast Tracker
ld b,xx
compar LD A,(DE)
CP (HL)
INC HL
INC DE
RET NZ
DJNZ compar
RET
;вместо этого
; ld bc,42
; or a
; sbc hl,bc
;можно так
; ld bc,-42
; add hl,bc
COMPARE 2 bytes
or a
ld hl,(тут ZX или нет?)
ld de,"ZX"
sbc hl,de
ret nz
jr совпало
CPBCDE PUSH HL
LD H,B
LD L,C
OR A
SBC HL,DE
POP HL
RET
cphlbc push hl
and a
sbc hl,bc
pop hl
ret
cpstr:
;Compare two strings at HL and DE;returns z if they are equal
;returns c if DE points to the smaller string;returns nc if DE is the
bigger (or equal) string.
ld a,(de)
cp (hl)
inc de
inc hl
ret nz
or a
jr nz,cpstr
ret
COMPARESTRING_nocase:
;Compare two strings at HL and DE, with lowercase being equal to uppercase,
returns z if they are equal
;returns c if HL points to the smaller string, returns nc if HL is the
bigger (or equal) string.
;destroys A,C,DE,HL, preserves B
;26 bytes
ld a,(de)
call toUpper
ld c,a
ld a,(hl)
call toUpper
cp c
inc de
inc hl
ret nz
or a
jr nz,cpstr
ret
toUpper:
;A is the char. If A is a lowercase letter,
this sets it to the matching uppercase
;18cc or 30cc or 41cc avg: 26.75cc
cp 'a'
ret c
cp 'z'+1
ret nc
sub 'a'-'A'
ret
;compare strings
;Compare two strings at HL and DE returns, z if they are equal
;returns c if DE points to the smaller string,
returns nc if DE is the bigger (or equal) string.
ld a,(de)
cp (hl)
inc de
inc hl
ret nz
or a
jr nz,cpstr
ret
COMPARESTRINGS fast nocase:
;Compare two strings at HL and DE, with lowercase being equal to
uppercase, returns z if they are equal
;returns c if HL points to the smaller string, returns nc
if HL is the bigger (or equal) string.
;destroys A,C,DE,HL, preserves B
;31 bytes.
ld a,(de)
cp 'a'
jr c,+_
cp 'z'+1
jr nc,+_
sub 'a'-'A'
_:
ld c,a
ld a,(hl)
cp 'a'
jr c,+_
cp 'z'+1
jr nc,+_
sub 'a'-'A'
_:
cp c
inc de
inc hl
ret nz
or a
jr nz,cpstr
ret
compare pol
ld b,lenght
ld de,m1
ld hl,m2
COMPARE: ld a,(de) ;porovnava B znaku
cp (hl) ;z adresy DE a HL
ret nz ;Z = stejne
inc de
inc hl
djnz COMPARE
;These code snippets are for 16-bit comparisons.
; NOTE: these are meant to be in-line, not called!
;
;"I learned these from calc84maniac"
;"These have similar flags to that of the `cp` instruction. At the very least,
; you get the zero and carry flag identical."
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
cpHL_DE:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
;Inputs:
; HL, DE
;Outputs:
; z flag is set if HL=DE, else nz
; c flag is set if HL<DE, else nc
;Destroys:
; none
;size: 4 bytes
;speed: 30cc
;
or a
sbc hl,de
add hl,de
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
cpHL_BC:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
;Inputs:
; HL, BC
;Outputs:
; z flag is set if HL=BC, else nz
; c flag is set if HL<BC, else nc
;Destroys:
; none
;size: 4 bytes
;speed: 30cc
;
or a
sbc hl,bc
add hl,bc
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
cpHL_DE_faster:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
;Inputs:
; HL, DE
;Outputs:
; z flag is set if HL=DE, else nz
; c flag is set if HL<DE, else nc
;Destroys:
; A
;size: 6 bytes
;speed: 20cc or 23cc
;
ld a,h
cp d
jr nz,cpHL_DE_result
ld a,l
cp e
cpHL_DE_result:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
cpHL_BC_faster:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
;Inputs:
; HL, BC
;Outputs:
; z flag is set if HL=BC, else nz
; c flag is set if HL<BC, else nc
;Destroys:
; A
;size: 6 bytes
;speed: 20cc or 23cc
;
ld a,h
cp b
jr nz,cpHL_BC_result
ld a,l
cp c
cpHL_BC_result:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
cpDE_HL_faster:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
;Inputs:
; DE, HL
;Outputs:
; z flag is set if DE=HL, else nz
; c flag is set if DE<HL, else nc
;Destroys:
; A
;size: 6 bytes
;speed: 20cc or 23cc
;
ld a,d
cp h
jr nz,cpDE_HL_result
ld a,e
cp l
cpDE_BC_result:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
cpDE_BC_faster:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
;Inputs:
; DE, BC
;Outputs:
; z flag is set if DE=BC, else nz
; c flag is set if DE<BC, else nc
;Destroys:
; A
;size: 6 bytes
;speed: 20cc or 23cc
;
ld a,d
cp b
jr nz,cpDE_BC_result
ld a,e
cp c
cpDE_BC_result:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
cpBC_HL_faster:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
;Inputs:
; BC, HL
;Outputs:
; z flag is set if BC=HL, else nz
; c flag is set if BC<HL, else nc
;Destroys:
; A
;size: 6 bytes
;speed: 20cc or 23cc
;
ld a,b
cp h
jr nz,cpBC_HL_result
ld a,c
cp l
cpBC_HL_result:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
cpBC_DE_faster:
;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;:;;
;Inputs:
; BC, DE
;Outputs:
; z flag is set if BC=DE, else nz
; c flag is set if BC<DE, else nc
;Destroys:
; A
;size: 6 bytes
;speed: 20cc or 23cc
;
ld a,b
cp d
jr nz,cpBC_DE_result
ld a,c
cp e
cpBC_DE_result:
if #20 to #5f - then continue, else - out
cp #20
jp c, out
cp #60
jp nc, out
if Bit 7 set - then out, else - continue
bit 7,a
jp nz,out
if bit 7 set - then continue, else - out
bit 7,a
jp z,out
FIND
FIND LENGHT
stringlength:
;Inputs: HL points to the null-terminated string
;Outputs: BC is the length of the string HL points to the byte after the null-byte
;Destroys: A ;speed: 41+21n ;12 bytes
xor a
ld c,a
ld b,a
cpir
scf ;comment this if the NULL byte should be counted
sbc a,c
ld c,a
sbc a,a
sub b
ld b,a
ret
FIND LENGHT less 256
stringlength_8bit:
; Input: HL points to the null-terminated string (less than 256 bytes long)
; Output: A is the length of the string
xor a
ld c,a
cpir
sub c
; dec a ; comment if the null byte is supposed to be included in the length
ret
;IN
LD HL,0
LD BC,#FFFF ;сколько байт проверяем
LD DE,BUFF ;что ищем
LD IX,#04 ;длина искомого
CALL FIND
RET
;OUT - адрес найденного в BC ... в HL место остановки поиска
;если в BC ноль - ничего не найдено
;очень мне она не нравится!! хотя и ра6отает вроде
FIND LD (FINL),IX
LD A,(FINL)
LD (FADR),DE
FN1 LD DE,(FADR)
PUSH BC
LD A,(FINL)
LD B,A
PUSH BC
FN3 LD A,(DE)
CP (HL)
INC HL
JR NZ,NOF
INC DE
DJNZ FN3
POP BC
FN0 DEC HL
DJNZ FN0
POP BC
LD B,H
LD C,L
RET
NOF POP BC
POP BC
DEC BC
LD A,B
OR C
JR NZ,FN1
RET
FADR DW #0000
FINL DW #0000
BUFF DB "1982"
SWAP
SWAP
;HL - откуда ;DE - куда ;BC - сколько
LD HL,SOURCE1
LD DE,SOURCE2
LD BC,LEN
SWA1 LD A,(DE)
LDI
DEC HL
LD (HL),A
INC HL
JP PE,SWA1 ;JP PE,$-6
RET
;from rom 1982 #343c
ld b,xx
swaper ld a,(de)
ld c,(hl)
ex de,hl
ld (de),a
ld (hl),c
inc hl
inc de
djnz swaper
ex de,hl
ret
swap 2 bottom bits
ld b,a
and 3
ld a,b
jp pe,done
xor 3
COPY
ld hl,otkuda ; ld hl, otkuda+dlina-1 (last byte)
ld de,kuda ; ld de, kuda+dlina-1 (last byte)
ld bc,dlina ; ld bc, dlina
ldir ; lddr
;быстрая переброска нескольких байт
;(снизу вверх!)
ld sp,otkuda
pop hl ;кидаем в стак два байта
pop de ;кидаем в стак два байта
pop bc ;кидаем в стак два байта
pop af ;кидаем в стак два байта
ld sp,kuda
push af ;кладем пару байт куда нам нужно
push bc ;кладем пару байт куда нам нужно
push de ;кладем пару байт куда нам нужно
push hl ;кладем пару байт куда нам нужно
;author: calcmaniac84, I think ;Copy zero terminated string at HL to DE.
strcopy xor a
docopystr cp (hl)
ldi
jr nz,docopystr
ret
Move 3 bytes from (HL) to (DE).
MOVE3 LD B,3
; Move (B) bytes from (HL) to (DE).
HL2DE LD A,(HL)
LD (DE),A
INC HL
INC DE
DEC B
JP NZ,HL2DE
RET
Block move. (DE) to (HL), (C) bytes total.
DE2HL INC C ;is count down to zero?
DE2HL1 DEC C
RET Z ;yes, we are done.
LD A,(DE) ;no, move one more byte.
LD (HL),A
INC DE
INC HL
JP DE2HL1 ;and repeat.
move block from IX to IY
BLKMOV LD IX, FROM
LD IY, TO
LD BC. COUNT
NEXT LD A, (IX) GET WORD
LD (IY), A
INC IX
INC IY
DEC C
JR NZ, NEXT
Переворот строки байтов (de=начало, bc=длина):
ld h,b
ld l,c
add hl,de ;hl=конец+1
srl b
rr c ;bc=wid/2
mirrorbytes0
dec hl
ld a,(de)
ldi
dec hl
ld (hl),a
jp pe,mirrorbytes0
;- Reverse a ;input: byte in A ;output: Reversed byte in A
;destroys B ;17 bytes and 66 clock cycles ;author: calcmaniac84
ReverseA:
ld b,a ;b=ABCDEFGH
rrca ;a=HABCDEFG
rrca ;a=GHABCDEF
xor b \ and %10101010 \ xor b ;a=GBADCFEH
ld b,a ;b=GBADCFEH
rrca ;a=HGBADCFE
rrca ;a=EHGBADCF
rrca ;a=FEHGBADC
rrca ;a=CFEHGBAD
xor b \ and %01100110 \ xor b ;a=GFEDCBAH
rrca ;a=HGFEDCBA
;input: B to reverse ;output: A reversed ;25 bytes, 96 clock cycles
typicalreversea:
rr b
rla
rr b
rla
rr b
rla
rr b
rla
rr b
rla
rr b
rla
rr b
rla
rr b
rla
ret
LDIR 4,8,16,32,64....
;This is a general-purpose faster-than-LDIR code to replace LDIR in speed-critical situations.
fastLDIR:
;copy BC bytes from HL to DE
;Cost:; 27cc for having to call; 110cc for setting up the loop, worst case
; 10cc * ceiling(BC/n) ;n=2^k for some k, see the line below "ldirloop:"; 16cc * BC
;costs roughly 152-BC*(5-10/n) more than a simple LDIR (worst case)
;for n=4, BC>=61 saves;for n=8, BC>=41 saves;for n=16, BC>=35 saves * default, see the "ldirloop" to change
;for n=32, BC>=33 saves;for n=64, BC>=32 saves
push hl
push af
xor a
sub c
and 15 ;change to n-1
add a,a
ld hl,ldirloop
add a,l
ld l,a
jr nc,$+3 ;these aren't needed if the ldirloop doesn't cross a 256 byte boundary.
;Can save 12cc on the above timings and 3 bytes.
inc h ;
pop af
ex (sp),hl
ret
ldirloop:
;n=16, (number of LDI instructions, use qty of 4,8,16,32,64)
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
_ldirloop_end:
ldi
jp pe,ldirloop
ret
ldir 4,8,16,32.....
LD HL,SOURCE ; 10
LD DE,DESTINATION ; 10
LD BC,bytes ; 10
LOOP LDI ; 16
LDI ; 16
LDI ; 16
LDI ; 16
JP PE,LOOP ; 10 Loop until bc = zero
; Up to 19% faster alternative for large LDIRs (break-even at 21 loops)
; hl = source ; de = destination; bc = byte count
FastLDIR
xor a
sub c
and 16 - 1
add a,a
di
ld (FastLDIR_jumpOffset),a
ei
jr nz,$ ; self modifying code
FastLDIR_jumpOffset: equ $ - 1
FastLDIR_Loop:
ldi ; 16x LDI
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
jp pe,FastLDIR_Loop
ret
FILL
ld hl,first
ld de,first+1
ld bc,x
ld (hl),a
ldir
ld d,a
ld e,a
ld hl,fill
ld sp,hl
fill push de
jp (hl)
ld (SAVE_SP),sp
ld sp,FINAL_ADDRESS+1
ld hl,fillBYTE_1*#100 + fillBYTE_2
ld de,LENGTH_TO_FILL/2
ld b,e
dec de
inc d
PUSHLOOP push hl
djnz PUSHLOOP
dec d
jr nz,PUSHLOOP
SAVE_SP equ $+1 ld SP,#0000
;заполняем область памяти какой-то текстурой...ну очень тормозно
;FILLHOWLEN EQU #1
;FFILLBEGIN EQU #4000
;FILLEND EQU #57FF
;DE,FILLBEGIN
;IX,FILLEND
;HL,FILLHOW
;C,FILLHOWLEN
;FILLHOW DB "1234567890"
FILL_BYTES
PUSH HL
LD B,C ; ?
FILL0 LD A,(HL)
LD (DE),A
PUSH HL
PUSH IX
POP HL
AND A
SBC HL,DE
POP HL
JR Z,POPR
JR C,POPR
INC HL
INC DE
DJNZ FILL0
POP HL
JR FILL_BYTES
POPR POP HL
RET
FILLBLOCK with A, bc=len
LD (HL),A
INC HL
DJNZ FILLBLOCK
RET
FILLBLOCK WITH reg D, bc=len
LD A,D
LD (HL),A
INC HL
DEC BC
LD A,B
OR C
JP NZ,FILLBLOCK2
RET
COUNTERS, LOOPS, etc...
Первый вариант быстрее, и чуть короче, но использует регистр А.
Третий вариант медленнее второго, но зато не привязан к B, можно использовать два любых регистра.
Четвёртый не привязан к А, но имеет ограничение на 32765 значений.
Что лучше - выбирать нужно по конкретной задаче.
такты подсчитаны НЕ КОРРЕКТНО!
ld bc, 1000
loop dec bc ; 6t
ld a,b ; 4t
or c ; 4t
jr nz, loop ; 12t
; итого 26 тактов
ld bc, 1000
loop djnz loop ; 13/8t
dec c ; 4t
jr nz, loop ; 12/7t
; итого 29 тактов (чуть меньше, т.к. последний виток быстрее переход)
ld bc, 1000
loop dec c ; 4t
jr nz, loop ; 12/7t
dec b ; 4t
jr nz, loop ; 12/7t
; итого 32 такта (чуть меньше, т.к. последний виток быстрее переход)
ld de, 1000
loop dec de ; 6t
bit 7,d ; 8t
jr z, loop ; 12t
; итого 26 тактов
ld hl,0
ld bc,1000
loop [код цикла]
cpi
jp pe,loop
На выходе HL=1000
;16bit loop (v1)
ld b, e
dec de
inc d
loop2
...
djnz loop2
dec d
jp nz,loop2
;16bit loop (v0)
ld bc, howmuch
loop:
...
dec bc
ld a, b
or c
jp nz,loop
ld b,10 ; The LSB of the loop is 10
ld d,3 ; The MSB of the loop + the first loop is 3
Loop:
.....
djnz Loop
dec d
jp nz,Loop
This will loop 522 times
;how much BITS are SET in A ;population counter
;input: A - byte, out: A - number of set bits
;by jacobly
PopCountA
LD C,A
AND %10101010
CPL
RRCA
ADC A,C
LD B,A
AND %00110011
LD C,A
XOR B
RRCA
RRCA
ADD A,C
LD C,A
RRCA
RRCA
RRCA
RRCA
ADD A,C
AND %00001111
RET
;а это более длинный и медленный вариант
;input: b ;output: a
TypicalPopCountA:
xor a
ld c, a
rrc b
adc a, c
rrc b
adc a, c
rrc b
adc a, c
rrc b
adc a, c
rrc b
adc a, c
rrc b
adc a, c
rrc b
adc a, c
rrc b
adc a, c
JUMP TO PROCEDURES
JPHL
;ld a,num:call jphl (jump to NUMBER proc)
;портит только AF и HL'
EXX
PUSH DE
LD H,0
LD L,A
ADD HL,HL
LD DE,FUNTBL
ADD HL,DE
LD A,(HL)
INC HL
LD H,(HL)
LD L,A
POP DE
PUSH HL
EXX
RET
LD SP,#6000 ;set stack
LD A,2 ;number of procedure
CALL JPHL
RET
JPHL
;PUSH DE
LD L,A
LD H,0
ADD HL,HL
LD DE,TABLE
ADD HL,DE
LD A,(HL)
INC HL
LD H,(HL)
LD L,A
;POP DE
JP (HL)
TABLE DW proca,procb,procc,procd,proce.......
;ld a,num: call jphl
jphl LD HL,JUMP_TBL ;10
ADD A,A ;4
ADD A,L ;4
LD L,A ;4
ADC A,H ;4
SUB L ;4
LD H,A ;4
LD A,(HL) ;7
INC HL ;6
LD H,(HL) ;7
LD L,A ;4
JP (HL) ;4
JUMP_TBL DW proca,procb,procc,procd,proce.......
;в AYплейерах часто используется таблица с однобайтным смещением.
;a=x
ld hl,table
ld c,a
ld b,0
add hl,bc
ld c,(hl)
add hl,bc
jp (hl)
table
; пример из ASM 1.12
commands_jphl
ld hl, com_tbl
ld b, 0
ld c, a
add hl, bc
ld c, (hl)
add hl, bc
ld c, b
jp (hl)
com_tbl db com_SHOCK1976-$
db com_holdsmpl-$
db com_holdimg-$
db com_holdinstr-$
db com_quant-$
db com_glisup-$
db com_glisdn-$
db com_portdn-$
db com_ZXSOSMBM95-$
db com_portup-$
db com_ZXSOSMBM95-$
db com_sldvol-$
db com_ZXSOSMBM95-$
db iio1-$
db com_ZXSOSMBM95-$
from alonecoder
Ещё один способ вызова функции по номеру (номера заняты не подряд):
;a=команда
ld hl,tcmds ;адрес списка команд
ld bc,ncmds ;число команд в списке
cpir
jp nz,ошибка
add hl,bc
add hl,bc
add hl,bc
ld a,(hl)
inc hl
ld h,(hl)
ld l,a
jp (hl)
Список команд:
tcmds
db 'a'
db 's'
db 'd'
...
db 'x'
ncmds=$-tcmds
;дальше в обратном порядке лежат адреса обработчиков:
dw PROC_x
...
dw PROC_d
dw PROC_s
dw PROC_a
Используется в NedoOS - из-за поддержки нумерации команд от CP/M и MSX-DOS.
Самые частые команды лежат в начале списка CPIR.
Когда номера команд идут подряд, CPIR уже не нужен, достаточно перехода по таблице.
Это значительно быстрее!
Вот вариант от NEO SPECTRUMAN'а:
ld h,tab ;7
ld h,(hl) ;7
jp (hl) ;4
А вот из Супер Марио под NedoOS:
;ld l,a
or 0xc0 ;7
ld h,a ;4
jp (hl) ;4
LD HL,(HL):
Often we want to use indirection when using a lookup table of addresses.
For example, say you have a look-up table for strings:
LUT
dw String1
dw String2
dw String3
dw String4
String1: db "String1",0
String2: db "String2",0
String3: db "String3",0
String4: db "String4",0
And say you wanted to store the location of the string in HL.
Assuming HL already points to the address located in the LUT:
ld e,(hl)
inc hl
ld d,(hl)
ex de,hl
That is 4 bytes, 24 t-states, but it destroys DE.
The following is the same size and speed, destroying A:
ld a,(hl)
inc hl
ld h,(hl)
ld l,a
In the case that you need extreme speed or size optimisations,
the following also does the trick, but has a few drawbacks:
ld sp,hl
pop hl
At just 2 bytes, 16-tstates that is pretty optimised, but it destroys
the stack pointer which is a crucial element to most routines.
In general, you would need to save the stack pointer somewhere and
later restore it at a total cost of 40 t-states and 8 bytes and your
routine wouldn't be able to use the stack. You would then need to use
this version of indirection at least 6 times to get a speed saving
and 5 times for a size saving, at the cost of 2 bytes of RAM.
hl,(HL)
;input: HL = pointer to memory, ouput: HL = (HL) ;destroy A only
ldHLind:
ld a,(hl)
inc hl
ld h,(hl)
ld l,a
ret
;same as above but destroy DE
ldHLind2:
ld e,(hl)
inc hl
ld d,(hl)
ex de,hl
ret
Вывод текста
PRINT 8x8
ORG #8000
LD A,#7F;char
LD DE,#1F17;XX,YY
;prtchar8x8
;A=char ;de=xy; font in BC
printchar
LD H,0
LD L,A
LD A,E ;convert XY to SCR
AND 7
RRCA
RRCA
RRCA
ADD A,D
LD D,E
LD E,A
LD A,D
AND #18
OR #40
LD D,A
LD BC,#3C00;font
ADD HL,HL
ADD HL,HL
ADD HL,HL
ADD HL,BC
LD A,(HL)
LD (DE),A
INC HL
INC D
LD A,(HL)
LD (DE),A
INC HL
INC D
LD A,(HL)
LD (DE),A
INC HL
INC D
LD A,(HL)
LD (DE),A
INC HL
INC D
LD A,(HL)
LD (DE),A
INC HL
INC D
LD A,(HL)
LD (DE),A
INC HL
INC D
LD A,(HL)
LD (DE),A
INC HL
INC D
LD A,(HL)
LD (DE),A
RET
;from ddp
;in: [HL] - ASCIIZ-string (incl.ctrl.codes!)
PRINT LD A,(HL)
INC HL
OR A
RET Z
CP #0D ;(13) CR+LF
JR Z,PRN1
CP #16 ;(22) set pos. X, Y
JR Z,PRN2
PUSH HL
CALL PRCHAR
POP HL
JR PRINT
PRN1 PUSH HL
LD HL,(POSADR)
LD A,L
OR #1F
LD L,A
CALL PRCH3 ;Їа®ўҐаЄ "Є®Ґж бва®ЄЁ"
POP HL
JR PRINT
PRN2 LD E,(HL)
INC HL
LD D,(HL)
INC HL
LD A,D
AND #07
RRCA
RRCA
RRCA
OR E
LD E,A
LD A,D
AND #08
ADD #48
LD D,A
LD (POSADR),DE
JR PRINT
;=====
;in: A - char. (all printable - no ctrl.codes!)
; [POSADR] - address in screen
PRCHAR LD L,A
LD H,#00
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD DE,#3C00
ADD HL,DE
EX HL,DE
LD HL,(POSADR)
LD C,H
LD B,8
PRCH1 LD A,(DE)
LD (HL),A
INC DE
INC H
DJNZ PRCH1
LD H,C
PRCH3 INC L
JR NZ,PRCH2
LD A,H
ADD A,#08
LD H,A
CP #58
JR C,PRCH2
LD H,#48
PRCH2 LD (POSADR),HL
RET
DEFB " A.U.MEMORY TEST (131116) ",13,0
M_TEST DEFB 22,1,6,"( pass / fail )"
DEFB 13,13,"Test: ",0
M_LOOP DEFB 13,13,"Loop: ",0
M_SLASH DEFB " / ",0
This code copies the Spectrum ROM font to RAM making it "bold" as it goes,
then sets the systemvariable to point to it:
ld hl,15616 ; ROM font.
ld de,60000 ; address of our font.
ld bc,768 ; 96 chars * 8 rows to alter.
fontb ld a,(hl) ; get bitmap.
rlca ; rotate it left.
or (hl) ; combine 2 images.
ld (de),a ; write to new font.
inc hl ; next byte of old.
inc de ; next byte of new.
dec bc ; decrement counter.
ld a,b ; high byte.
or c ; combine with low byte.
jr nz,fontb ; repeat until bc=zero.
ld hl,60000-256 ; font minus 32*8.
ld (23606),hl ; point to new font.
ret
;from SAVC
pr_str ld de,0
call xy2scr
ld ix,num_str
call pr_str8x8
ret
pr_str8x8 ld a,(ix)
or a
ret z
call pr_smb8x8
call nxt_pr8x8
inc ix
jr pr_str8x8
pr_smb8x8 ld h,0
ld l,a
add hl,hl
add hl,hl
add hl,hl
ld bc,0 ;graph8x8
cp 32
jr c,m2
ld bc,font8x8
m2 add hl,bc
ld b,7
m1 ld a,(hl)
ld (de),a
inc hl
inc d
djnz m1
ld a,(hl)
ld (de),a
ret
nxt_pr8x8 inc de
ld a,d
and %11111000
ld d,a
ret
xy2scr ld a,d
and 7
rrca
rrca
rrca
or e
ld e,a
ld a,d
and 24
or 64
ld d,a
ret
num_str db "123456",0
PRINT VARIABLE STRING OF TEXT!
VARIABLE
;LD (X),BC
;LD (XL),DE
;LD (DATA),HL
DI
LD (BLOCK+1),SP
LD A,(Y)
LD L,A
LD H,0
ADD HL,HL
LD DE,BASE
ADD HL,DE
LD SP,HL
LD DE,(DATA)
LD A,(X)
LD C,A
LD A,(YL)
LD B,A
GOBB1 POP HL
LD A,B
LD (SAME+1),A
LD A,(XL)
LD B,A
LD A,L
OR C
LD L,A
GOBB2 LD A,(DE)
LD (HL),A
INC DE
INC HL
DJNZ GOBB2
SAME LD B,0
DJNZ GOBB1
BLOCK LD SP,0
EI
RET
DATA FOR VARIABLE SPRITE ROUTINE
X DB 0 ;x coordinate variable
Y DB 0 ;y coordinate variable
XL DB 0 ;x length variable
YL DB 0 ;y length variable
DATA DW 0 ;data for variable
PRINT 6x8
print42_str
;BC=coord HL=TEXT,0 ;!!! ПЕРПЕПИСАТЬ!!!!
LD A,(HL)
OR A
RET Z; JR Z,RET_AND_SCROLL
CP #B0
JR C,prst1
CP #E0
JR C,prst1
SUB #30
prst1 CALL print42_sym
INC HL
LD A, C
CP #FC
JR NZ,print42_str
LD C, 0
INC B
JR print42_str
print42_sym
PUSH AF
PUSH DE
PUSH HL
PUSH BC
LD H,0
LD L,A
ADD HL,HL ;ADD HL,HL;x2
LD D,H ;ADD HL,HL;x4
LD E,L ;ADD HL,HL;x8
ADD HL,HL;
ADD HL,DE;
LD DE,font42-192;256
ADD HL,DE
PUSH BC
PUSH HL
LD A,B
AND #18
OR #40
LD D,A
LD A,B
AND 7;если печатать в 1 третьей то моwно удалить
RRCA
RRCA
RRCA
LD E,A
LD A,C
RRA
RRA
RRA
AND #1F
LD H,0
LD L,A
ADD HL,DE
;LD DE,#3FF;пока не удалять иначе иногда
LD A,C
AND 7
LD C,A
JR Z,prs2
prs1 SCF
RR D
;RR E ;пока не удалять срет в символы
DEC A
JR NZ,prs1
prs2 LD B,6 ;сколько строк рисуем
prs3 EX (SP),HL
LD A, (HL)
INC HL
EX (SP),HL
PUSH BC
LD B,A
LD A,C
LD C,0
OR A
JR Z,prs5
prs4 SRL B
RR C
DEC A
JR NZ,prs4
prs5 LD A,(HL)
AND D
OR B
LD (HL),A
INC L
LD A,(HL)
;AND E;пока не удалять
OR C
LD (HL),A
DEC L
INC H
POP BC
DJNZ prs3
POP HL
POP BC
prs6 POP BC
LD A,C
ADD A,6
LD C,A
POP HL
POP DE
POP AF
RET
Экран и Аттрибуты
SCREEN & ATTRS
#ATTR = FLASH*128+BRIGHT*64+PAPER*8+INK
Процедуры, связанные с адресацией атрибутов.
Atribute file: Length=768 bytes. Address: #5800..#5AFF
(22528..23295)
Цвета задаются
Адрес атрибута вычисляется так: следующими битами:
adr(C,L) = 22528+L*32+C 7 6 543 210
---------------
где: L - строчка (0..23) f b p i
C - столбец (0..31) l r a n
a i p k
Схематически адрес атрибута выглядит так: s g e
h h r
0101 10LL LLLC CCCC t
Пересчет адресов в экранной области
Входные HL - адрес верхнего байта нужного знакоместа.
Выходные HL - соответственный адрес в атрибутах.
SRL H
SRL H
SRL H
SET 4,H
SET 6,H
И обратная процедурка... Входные: HL - адрес в атрибитах.
Выходные: HL - адрес верхнего байта соответствующего знакоместа.
SLA H
SLA H
SLA H
RES 7,H
12) Как определить адрес атрибута для заданного символа с
координатами XY.
Input: BC = YX
Output: HL = attribute address
AdrAttr LD A,B
RRCA
RRCA
RRCA
LD H,A
AND #E0
OR C
LD L,A
LD A,H
AND 3
OR #58
LD H,A
RET
13) Переход к следующей строке атрибутов.
Input: HL = attribute address
Output: HL = upper attribute address
DownAttr LD A,L LD DE,32
ADD A,32 или ADD HL,DE
LD L,A
RET NC
INC H
RET
14) Переход к предыдущей строке атрибутов.
Input: HL = attribute address
Output: HL = lower attribute address
UpAttr LD A,L LD DE,32
SUB 32 или AND A
LD L,A SBC HL,DE
RET NC
DEC H
RET
процедурка down_hl_8 для пересчета координат на экране на 8 ниже?
down_hl8
ld a, l
add a, #20
ld l, a
sbc a, a
and #08
add a, h
ld h, a
процедура перехода на 1 тайл (на 16 пикселей) выше:
ld a, l
sub #40
ld l, a
sbc a, a
and #f8 ; A = 0 or -8
add a, h
ld h, a
есть процедура, которая из координат 32х24 высчитывает экранный адрес:
;in: D - Y координата, E - X координата
;out:DE - screen adress
LD A,D
AND 7
RRCA
RRCA
RRCA
OR E
LD E,A
LD A,D
AND 24
OR 64
LD D,A
;Пересчёт из атрибутной области в адресную.
;Там фактически надо переместить биты из младших 0-1 в более старшие 3-4,
;плюс сохранить адрес.
;Универсальность процедуры в применимости и по отношению к теневому (#c000-#daff) экрану.
;by GriV
ld a,h ; 4
; and %00000011 ; 7 0 - так и хочется замаскировать эти биты, но эта операция не нужна,
add a,a ; 4 8 - т.к. старшие биты уйдут по дальнейшей маске %00011111, а
add a,a ; 4 12 - младшие биты забиты нулями по add a,a
add a,a ; 4 16
xor h ; 4 20
and %00011111 ; 7 27 - сохраним нужные биты и обнулим ненужные 0-2
xor h ; 4 31 - вытащили исходный адрес расположения
ld h,a ; 4 35
;Суть следующая:
;есть DE - адрес в экране
;C - количество пиксельных линий которые надо пропустить вниз
;Надо сделать п/п, которая получит в DE адрес новой пиксельной линии
;by GriV
ld a,d 4 берём уже имеющееся смещение по пикселям вниз
and %00000111 7 маскируем
add a,c 4 добавляем к смещению, т.к. можем перейти через треть или з/м
ld c,a 4 сохранили полученное значение
and %00111000 7 выделили часть, отвечающую за положение внутри трети экрана
rlca 4 смещаем её в старшие биты
rlca 4
add a,e 4 добавляем к младшему адресу
ld e,a 4 сохраняем
sbc a,a 4 если у нас появился CY, значит надо будет делать переход в другую треть
and %01000000 7 заполнили флагом переноса шестой бит аккумулятора
add a,c 4 добавили значения по третям из исходных данных
and %11000000 7 выделили
rrca 4 смещаем в 4-5 бит для адреса экрана
rrca 4 обращаю внимание что полученные трети никуда не сохраняем!
rrca 4
xor c 4 нам надо ещё младшие 3 бита, адрес пиксельной линии
and %11111000 7 по хитрому без промежуточного регистра выделяем её
xor c 4 получили в аккумуляторе
ld b,a 4 спрятали
ld a,d 4 из регистра D забираем треть и сам адрес
and %11111000 4 маскируем их
add a,b 4 добавляем спрятанное значение, младшие 3 бита учтены вначале
ld d,a 4 готово!
Проверка наличие порта #FF
Данная программа НЕ ОПРЕДЕЛЯТ неверносделанный порт #FF!
LD HL,#5800 ; заносим в атрибуты
LD DE,#5801 ; число #FF
LD BC,#02FF
LD (HL),#40
LDIR
EI
LD BC,#0800 ; В BC счетчик
HALT
LD HL,FF_YES
LOOP_0 IN A,(#FF)
CP #40
JR Z,LAB_X
DEC BC
LD A,B
OR C
JR NZ,LOOP_0
LD HL,ABSENT
LAB_X CALL PRINT
; A = Attr byte to set
; B = Line index
ClearLine:
push af ; Save A
ld a,b ; Multiply the line count with 32
sla a
sla a
sla a
sla a
sla a
ld hl,#5800 ; Calculate the attribute address
ld d,0
ld e,a
add hl,de
pop af ; Restore A
ld b,#20 ; Set all the 32 attribute bytes
SetAttr:
ld (hl),a
inc hl
djnz SetAttr
ret
find ATTRS from BC
LD A,B
SRA A
SRA A
SRA A
ADD A,88
LD H,A
LD A,B
AND 7
RRCA
RRCA
RRCA
ADD A,C
LD L,A
RET
clear_attrs
;ei
ld hl,#5800
ld de,#5801
ld bc,#02ff
ld (hl),l
;halt ;дабы плавно))
out (#FE),a
ldir
D_HL INC H
LD A,H
AND 7
RET NZ
LD A,L
ADD A,32
LD L,A
RET C
LD A,H
SUB 8
LD H,A
RET
D_DE INC D
LD A,D
AND 7
RET NZ
LD A,E
ADD A,32
LD E,A
RET C
LD A,D
SUB 8
LD D,A
RET
U_HL: LD A,H
DEC H
AND 7
RET NZ
LD A,L
SUB 32
LD L,A
RET C
LD A,H
ADD A,8
LD H,A
RET
U_DE LD A,D
DEC D
AND 7
RET NZ
LD A,E
SUB 32
LD E,A
RET C
LD A,D
ADD A,8
LD D,A
RET
;расчет адреса по знакоместам L-Y H-X
COOR_Z LD A,L
AND 7
RRCA
RRCA
RRCA
ADD A,H
LD H,L
LD L,A
LD A,H
AND #18
OR 64
LD H,A
RET
ATR_CONVERT
LD A,H
AND #18
RRCA
RRCA
RRCA
ADD A,#58
LD H,A
RET
COOR_ATR
;расчет адреса атрибутов L-Y H-X
LD A,H
LD H,L
LD L,0
AND A
DUP 3
RR H
RR L
EDUP
ADD A,L
LD L,A
LD A,H
OR #58
LD H,A
RET
Переход к предыдущему символу по экранному адресу.
Input: HL = screen address
Output: HL = previous char screen address
PrevChr RR H
RR H
RR H
DEC HL
RL H
RL H
RL H
RET
Как определить адрес атрибута для заданного символа с координатами XY.
Input: BC = YX
Output: HL = attribute address
AdrAttr LD A,B
RRCA
RRCA
RRCA
LD H,A
AND #E0
OR C
LD L,A
LD A,H
AND 3
OR #58
LD H,A
RET
Переход к следующей строке атрибутов.
Input: HL = attribute address
Output: HL = upper attribute address
DownAttr LD A,L LD DE,32
ADD A,32 или ADD HL,DE
LD L,A
RET NC
INC H
RET
Переход к предыдущей строке атрибутов.
Input: HL = attribute address
Output: HL = lower attribute address
UpAttr LD A,L LD DE,32
SUB 32 или AND A
LD L,A SBC HL,DE
RET NC
DEC H
RET
На этот раз мы сделали подборку различных подпрограммок по работе с адресами в экранной области.
Конечно же асам программирования все это давно известно, но вот начинающим наверняка будет полезно.
Процедура расчета адреса в файле атрибутов из координат:
На входе: D-X, E-Y
ATR_1 LD A,E
ADD A,A
ADD A,A
ADD A,A
LD L,A
LD H,#16
ADD HL,HL
ADD HL,HL
LD A,L
OR A
LD L,A
RET
Второй вариант:
На входе: H-X, L-Y
ATR_2 LD A,H
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD H,#16
ADD HL,HL
ADD HL,HL
ADD A,L
LD L,A
RET
На выходе обеих процедур в HL будет содержаться адрес файла атрибутов.
Решить противоположную задачу можно так:
На входе: HL-адрес
CRDS LD A,L
AND #1F
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD L,H
LD H,A
LD A,L
AND #1F
LD L,A
RET
На выходе: H-X, L-Y.
И еще одна простенькая процедура для расчета адреса в экранном файле из адреса в файле атрибутов:
На входе: HL-адрес в файле атрибутов.
ATR_SCR LD A,H
ADD A,A
ADD A,A
ADD A,A
AND #7F
LD H,A
RET
На выходе: HL-адрес экранного файла.
Процедура для расчета адреса в экранном файле из координат.faster
На входе: HL-координаты в знакоместах.
На выходе: HL-адрес в экранном файле.
SCR_ADR LD A,L
AND 7
RRCA
RRCA
RRCA
ADD A,H
LD H,L
LD L,A
LD A,H
AND #18
OR #40
LD H,A
RET
D-X (в знакоместах), E-Y (в знакоместах).slower
На выходе: HL-адрес в экранном файле.
coordde2scrhl LD A, E
AND #18
OR #40
LD H, A
LD A, E
AND 7
AND A
RRA
RRA
RRA
RRA
ADD A, D
LD L, A
RET
Процедура для расчета адреса в экранном файле из координат. Часто применяется при выводе спрайтв.
На входе: D-X (в знакоместах), E-Y (в пикселях).
SCRadr2 LD A,E
AND 7
LD C,A
LD A,E
AND 192
RRCA
RRCA
RRCA
OR #40
OR C
LD H,A
LD A,E
AND 56
RLCA
RLCA
OR D
LD L,A
RET
На выходе: HL-адрес в экранном файле.
Процедура для перехода на одну пиксельную линию в экране ниже. На входе: HL-адрес в экранном файле.
LINE_HL INC H
LD A,H
AND 7
RET NZ
LD A,L
ADD A,32
LD L,A
RET C
LD A,H
SUB 8
LD H,A
RET
На выходе: HL-адрес в экранном файле ниже на пиксель от исходного.
(c) Д.Анисимов, г.Киров, 1996.
Хочу предложить в раздел "ЭТЮДЫ" несколько разработанных мною процедур.
Первая - процедура расчета адреса в файле атрибутов из координат:
Первый вариант:
X-координата в D
Y-координата в E
LD A,E
ADD A,A
ADD A,A
ADD A,A
LD L,A
LD H,#16
ADD HL,HL
ADD HL,HL
LD A,L
OR A
LD L,A
Второй вариант:
X-координата в H
Y-координата в L
LD A,H
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD H,#16
ADD HL,HL
ADD HL,HL
ADD A,L
LD L,A
На выходе обеих процедур в HL - адрес файла атрибутов.
Решить противоположную задачу можно так:
В HL - адрес.
Первый вариант:
LD A,L
AND #1F
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD L,A
LD A,H
AND #1F
LD H,A
на выходе:
в H-y координата
в L-x координата
Второй вариант:
LD A,L
AND #1F
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD L,H
LD H,A
LD A,L
AND #1F
LD L,A
на выходе:
в H-x координата
в L-y координата.
И еще одна простенькая процедура для расчета адреса в экранном файле из
адреса в файле атрибутов:
В HL-адрес в файле атрибутов
LD A,H
ADD A,A
ADD A,A
ADD A,A
AND #7F
LD H,A
В HL-адрес экранного файла
from Sergei Smirnov
// Converts X,Y coords to screen address
// Arguments:
// L - coord Y (0..255)
// H - coord X (0..192)
// Returns:
// DE - screen address
coords_to_address:
ld a, h // 4t
and #c0 // 7t
cp #c0 // 7t
jr z, coords_to_address_rom // 7t
// Sum: 25t
srl l // 8t
srl l // 8t
srl l // 8t
// Sum: 24t
// L - coord Y (0..31)
// H - coord X (0..192)
coords_32x192_to_address:
ld a, h // 4t
srl a // 8t
scf // 4t
rra // 4t
srl a // 8t
xor h // 4t
and #f8 // 7t
xor h // 4t
ld d, a // 4t
ld a, h // 4t
rlca // 4t
rlca // 4t
xor l // 4t
and #e0 // 7t
xor l // 4t
ld e, a // 4t
ret // Sum: 78t (102t w/o check)
// Total: 25 + 24 + 78 = 127t, 33 bytes
// Converts X,Y coords to screen address #2
// Arguments:
// L - coord Y (0..255)
// H - coord X (0..192)
// Returns:
// DE - screen address
coords_to_address:
ld a, h // 4t
and #c0 // 7t
cp #c0 // 7t
jr z, coords_to_address_rom // 7t
// Sum: 25t
// block BB
rrca // 4t
scf // 4t
rra // 4t
rrca // 4t
ld d, a // 4t
// Sum: 20t
// block CCC
ld a, h // 4t
and #07 // 7t
or d // 4t
ld d, a // 4t
// Sum: 19t
// block DDD
ld a, h // 4t
and #38 // 7t
rlca // 4t
rlca // 4t
ld e, a // 4t
// Sum: 23t
// block EEEEE
ld a, l // 4t
and %11111000 // 7t
rrca // 4t
rrca // 4t
rrca // 4t
or e // 4t
ld e, a // 4t
ret // Sum: 31t (93t w/o check)
// Total: 25 + 20 + 19 + 23 + 31 = 118t, 31 bytes
coords_to_address_rom:
ld de, 0
scf
ret
// Converts X,Y coords to address in attributes
// Arguments:
// L - coord X (0..255)
// H - coord Y (0..192)
// Returns:
// DE - address in attributes
coords_to_attrs:
ld a, h
rlca
rlca
ld e, a
and #03
cp #03
jr z, coords_to_address_rom
// block BB
or #58
ld d, a
// block DDD
ld a, e
and #e0
ld e, a
// block EEEEE
ld a, l
rrca
rrca
rrca
and #1f
or e
ld e, a
ret
// Converts screen address to address in attributes
// Arguments:
// HL - screen address
// Returns:
// HL - address in attributes
address_to_attrs_hl:
ld a, h
and a
ret z
and #18
rrca
rrca
rrca
or #58
ld h, a
ret
Кто-то где-то, помнится, просил заливалку экрана сеткой.
Вот, короче не смог придумать. grid
grider ld a,%10101010
ld hl,16384
ld b,24
;grid1 ld c,0
grid2 ld (hl),a
inc l
; dec c
jr nz,grid2
inc h
cpl
djnz grid2
ret
вопрос по теме - ни у кого не сохранилось процедурки down_hl_8 для пересчета координат на экране на 8 ниже?
copy ld a, l
add a, #20
ld l, a
sbc a, a
and #08
add a, h
ld h, a
ret
xy2atr ld a,d
rrca
rrca
rrca
ld d,a
and 224
or e
ld e,a
ld a,d
and 3
or 88
ld d,a
ret
xy2scr ld a,d
and 7
rrca
rrca
rrca
or e
ld e,a
ld a,d
and 24
or 64
ld d,a
ret
scr2atr ld a,d
rrca
rrca
rrca
and %00000011
or %01011000
ld d,a
ret
;Процедура ADR_STR
;Вход: A - номер строки (0..191)
;Выход: HL,DE - адрес в дисплейном файле.
ADR_STR LD E,A
AND A
RRA
SCF
RRA
AND A
RRA
XOR E
AND #F8
XOR E
LD H,A
XOR A
XOR E
AND #C7
XOR E
RLCA
RLCA
LD L,A
LD E,A
LD D,H
RET
;from Lop Ears by Players
;FINDS ATTRIBUTE POSITION
ATLOC LD A,B
SRA A
SRA A
SRA A
ADD A,88
LD H,A
LD A,B
AND 7
RRCA
RRCA
RRCA
ADD A,C
LD L,A
RET
from Lop Ears by Players
****************************************
* SCREEN COORDINATE ROUTINE *
****************************************
PIXEL LD A,H
AND 192
RRCA
RRCA
RRCA
ADD A,64
LD D,A
LD A,H
AND 7
ADD A,D
LD D,A
LD A,H
ADD A,A
ADD A,A
AND 224
LD E,A
LD A,L
AND 248
RRCA
RRCA
RRCA
OR E
LD E,A
EX DE,HL
RET
(c) Д.Анисимов, г.Киров, 1996.
Хочу предложить в раздел "ЭТЮДЫ" несколько разработанных мною процедур.
Первая - процедура расчета адреса в файле атрибутов из координат:
Первый вариант:
X-координата в D
Y-координата в E
LD A,E
ADD A,A
ADD A,A
ADD A,A
LD L,A
LD H,#16
ADD HL,HL
ADD HL,HL
LD A,L
OR A
LD L,A
Второй вариант:
X-координата в H
Y-координата в L
LD A,H
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD H,#16
ADD HL,HL
ADD HL,HL
ADD A,L
LD L,A
На выходе обеих процедур в HL - адрес файла атрибутов.
Решить противоположную задачу можно так:
В HL - адрес.
Первый вариант:
LD A,L
AND #1F
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD L,A
LD A,H
AND #1F
LD H,A
на выходе:
в H-y координата
в L-x координата
Второй вариант:
LD A,L
AND #1F
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD L,H
LD H,A
LD A,L
AND #1F
LD L,A
на выходе:
в H-x координата
в L-y координата.
И еще одна простенькая процедура для расчета адреса в экранном файле из
адреса в файле атрибутов:
В HL-адрес в файле атрибутов
LD A,H
ADD A,A
ADD A,A
ADD A,A
AND #7F
LD H,A
В HL-адрес экранного файла
888
DEP888
LD HL,FILE888TO
CALL LDDE11
LD HL,FILE888FROM
LD C,128
DEP CALL DEP3
CALL NZ,oldcl
JR NZ,COLQQ
EXX
PUSH HL
EXX
POP DE
PUSH BC
LD BC,#820
LD A,D
COL80 ADD A,24
LD D,A
LDI ;R
DEC E
ADD A,24
LD D,A
LDI ;G
DEC E
SUB 48
LD D,A
LDI ;B
DEC E
INC A
DJNZ COL80
POP BC
EXX
JR COLQ
COLQQ
EXX
LD A,H
COLQ INC L
JR Z,$+4
SUB 8
LD H,A
EXX
CP FILE888TO/256+24
JR NZ,DEP
CHLRLC LD C,(HL)
INC HL
RL C
RET
COL4
LD A,#40
CALL DEPCOL0
CALL PUTCOL
DJNZ COL4
RET
COL1
XOR A
CALL PUTCOL
DJNZ COL1
RET
oldcl
;любое число цветов, кроме 8
;A=1 - старое число цветов и старая палитра
;увеличивает H на 8
LD D,T888FOUND/256
DEC A
JR Z,COLOLD
LD LX,A
LD E,A
DEPTAB CALL DEP3
DEC E
LD (DE),A
JR NZ,DEPTAB
COLOLD LD A,LX
LD B,64
CP 4
JR NC,COL45O
DEC A
JR Z,COL1
DEC A
JR Z,COL2
;2=11
;1=10
;0=0
COL3
LD A,#80
CALL DEPCOL0
JR Z,COL3N1
SLA C
CALL Z,CHLRLC
RLA
DEC A
COL3N1 CALL PUTCOL
DJNZ COL3
RET
;1=1
;0=0
COL2
LD A,#80
CALL DEPCOL0
CALL PUTCOL
DJNZ COL2
RET
COL45O
JR Z,COL4
RRA
JR C,COL5
;5=111
;4=110
;3=101
;2=100
;1=01
;0=00
COL6 LD A,#40
CALL DEPCOL0
CP 2
JR C,COL6N1
DEC A
SLA C
CALL Z,CHLRLC
RLA
COL6N1 CALL PUTCOL
DJNZ COL6
RET
;0=00
;1=01
;2=10
;3=110
;4=111
COL5 LD A,#40
CALL DEPCOL0
CP 3
JR C,COL5N1
SLA C
CALL Z,CHLRLC
RLA
SUB 3
COL5N1 CALL PUTCOL
DJNZ COL5
RET
PUTCOL
LD E,A
LD A,(DE)
EXX
RRA
RL E ;B
RRA
RL D ;R
RRA
RL C ;G
JR NC,EXXRET
LD (HL),E ;B
LD A,H
ADD A,24
LD H,A
LD (HL),D ;R
ADD A,24
LD H,A
LD (HL),C ;G
SUB 47 ;INCH
LD H,A
LDDE11 LD C,1
EXXRET EXX
RET
DEP3 LD A,#20
DEPCOL0 SLA C
CALL Z,CHLRLC
ADC A,A
JR NC,DEPCOL0
RET
Звук
AY8910(12) / YM2149
Частота работы АУка
amstrad cpc 1,000000 ;странно, но таблицы в тракерах на Амстрад под 2,0мгц
bk0011m 1,500000 (6mhz/2/2 by RDC soft, Moscow ;
zx-pentagon 1,750000 (cpu/2) ;
zx-spectrum 1,773400 ;
zx-melodik 1,789770 (3,57954/2) ;если брать таблицы тональностей этих трех
datasheet 1,7895000 (1,78977) (3,579/2) ;"девайсов", то все эти три таблички будут фактически
msx 1,7897725 (cpu/2) ;одинаковы, ибо частота почти одинаковая
atari st 2,000000 ;
Functional differences AY vs YM
Input clock speed
The YM has an internal clock divider, enabling it
to be used with an external clock of up to 4Mz vs 2MHz of
the AY. Pin 26 which is designated TEST 2 on the AY-3-8910
and should be left NC, is /SEL on the YM2149F. When the
voltage level of /SEL is high, the input clock is taken as the
master clock. When the voltage level is low, the input clock
is divided by 2. The pin has an internal pullup allowing pin
compatibility with the AY.
Envelope volume
The envelope volume counter on the AY-3-8910 internally uses 4 bit
s, resulting in 16 steps. On the YM2149F it has 5 bits, resulting
in twice the steps, and counts up twice as fast. This allows for
smoother volume ramping, even though the register for setting
its direct value remains 4 bits wide.
Unused bits in registers
On the AY, unused bits in registers always read back as 0 even
if you had earlier written 1 to them. On the YM2149G, the register
values can be read back as written.
Output voltage offset
The YM2149 has a 2V DC offset on all outputs, whereas
the AY-3-8910 has a 0.2V offset on a channel if an envelope is active.
Real world sound differences
If you listen to the same compositions played on both chips
there are differences:
The output from the AY is louder.
Note transitions seem smoother on the AY
The YM output is cleaner
Музыкальный процессор включает в себя 16 ре-
гистров. 14 из них используется для формирова-
ния звука.
Для того, чтобы поместить в регистр музы-
кального сопроцессора какие либо данные, надо:
1) выбрать регистр для записи данных
LD BC,#FFFD
LD A,номер регистра 0 - 13
OUT (C),A
2) записать данные
LD BC,#BFFD
LD A,какoе либо число 0 - 255
OUT (C),A
Для чтения данных из регистра надо:
1) выбрать регистр для записи данных
LD BC,#FFFD
LD A,номер регистра 0 - 13
OUT (C),A
2) считать данные
IN A,(C)
Первые шесть регистров используются для
задания высоты звучания каждого канала из
диапазона 0 - 4095.
Регистры R0,R1 - частота звучания канала A.
Регистры R2,R3 - частота звучания канала B.
Регистры R4,R5 - частота звучания канала C.
Регистр R6 - определяет частоту выводимого
шума для трёх каналов из диапазона 0 - 31.
Регистр R7 - управляет звуковыми каналами
d7d6d5d4d3d2d1d0
x x ¦ ¦ ¦ ¦ ¦ L запрещает звучание канала A.
¦ ¦ ¦ ¦ L-- запрещает звучание канала B.
¦ ¦ ¦ L---- запрещает звучание канала C.
¦ ¦ L------ запрещает шум канала A.
¦ L-------- запрещает шум канала B.
L---------- запрещает шум канала C.
Следующие три регистра используются для
задания громкости звука из диапазона 0 - 15.
4-й бит указывает на то, что громкость будет
изменятся способом указанным в R13 и со
скоростью указаной в R11/R12.
Регистр R8 - определяет громкость канала A.
Регистр R9 - определяет громкость канала B.
Регистр R10 - определяет громкость канала C.
Регистры R11, R12 - определяют скорость
изменения громкости звука от 0 до 65535. (на
практике изменение рег. R11 малоощутимо,
поэтому достаточно задавать только рег. R12)
Регистр R13 - управляет формированием
огибающей выходного сигнала:
#0,#1,#2,#3,#9 - затухание, затем тихо;
#4,#5,#6,#7,#F - нарастание, затем тихо;
#B - затухание, затем громко;
#D - нарастание, затем зромко;
#8 - повторяющееся затухание;
#C - повторяющееся нарастание;
#E - повторяющееся нарастание и
затухание;
#A - повторяющееся затухание и
нарастание;
(C) Денис Паринов, 1997.
Ну, а теперь несколько примеров, демонстрирующих сказаное.
Перед обращением к программе в регистр A
необходимо записать номер эффекта с 1 по 4.
; Вх: A - номер эффекта с 1 - 4.
AY_EFF LD HL,EFFECT1 ;Вычисляем
LD BC,#000E ;адрес начала
NEFF ADD HL,BC ;эффекта следующего
DEC A ;за выбранным.
JP NZ,NEFF ;
DEC HL ;HL на конец выбра-
;нного эффекта.
LD A,#0D ;Начать с рег-ра R13
LD C,#FD ;
NREG LD B,#FF ;Выбрать регистр AY
OUT (C),A ;для записи данных.
LD B,#BF ;Запись в регистр AY
OUTD ;байта из (HL) и
;уменьшение HL на 1.
DEC A ;Уменьшение номера
;регистра AY, если
JP P,NREG ;он Є 0, продолжить.
RET ;Иначе выйти.
; Эффект используется в различных boot'ах при запуске программ.
EFFECT1 DEFW #0080,#0001,#0000 ;частота тона
;для каналов A, B, C
DEFB #00 ;частота шума
DEFB #38 ;00111000 выкл. шум.
DEFB #10,#10,#10;громк. канал. A,B,C
DEFW #1500 ;изменение громкости
DEFB #01 ;затухание
; Эффект из программы Honey Commander.
EFFECT2 DEFW #203C,#2064,#208C
DEFB #00
DEFB #38
DEFB #10,#10,#10
DEFW #0810
DEFB #01
; Эффект 2 из Honey Commander.
EFFECT3 DEFW #2050,#2060,#2070
DEFB #01
DEFB #00
DEFB #10,#10,#10
DEFW #0400
DEFB #01
; Эффект напоминающий вращение лопастей вертолёта.
EFFECT4 DEFW #0000,#0000,#0000
DEFB #00
DEFB #07
DEFB #10,#10,#10
DEFW #0100
DEFB #0E
REGISTERS
---------
The AY-3-8910/8912 contains 16 internal registers as follows:
REGISTER FUNCTION RANGE
0 Channel A fine pitch 8-bit (0-255)
1 Channel A course pitch 4-bit (0-15)
2 Channel B fine pitch 8-bit (0-255)
3 Channel B course pitch 4-bit (0-15)
4 Channel C fine pitch 8-bit (0-255)
5 Channel C course pitch 4-bit (0-15)
6 Noise pitch 5-bit (0-31)
7 Mixer 8-bit (see below)
8 Channel A volume 4-bit (0-15, see below)
9 Channel B volume 4-bit (0-15, see below)
10 Channel C volume 4-bit (0-15, see below)
11 Envelope fine duration 8-bit (0-255)
12 Envelope course duration 8-bit (0-255)
13 Envelope shape 4-bit (0-15)
14 I/O port A 8-bit (0-255)
15 I/O port B 8-bit (0-255)
Notes:
- The AY-3-8912 does not contain register 15.
- The volume registers (8, 9 and 10) contain a 4-bit
setting but if bit 5 is set then that channel uses the envelope
defined by register 13 and ignores its volume setting.
- The mixer (register 7) is made up of the following
bits (low=enabled):
Bit: 7 6 5 4 3 2 1 0
_ _
I/O I/O Noise Noise Noise Tone Tone Tone
B A C B A C B A
The AY-3-8912 ignores bit 7 of this register.
ENVELOPES
---------
The AY-3-8910/8912 contains the following preset
envelopes or waveforms (set using control register 13). Note
that these affect volume only and not the pitch:
0 __________ single decay then off
4 /|_________ single attack then off
8 ||||| repeated decay
9 __________ single decay then off
10 ///// repeated decay-attack
_________
11 | single decay then hold
12 /|/|/|/|/|/ repeated attack
__________
13 / single attack then hold
14 ////// repeated attack-decay
15 /|_________ single attack then off
example:
ayctrl EQU 65533
aydata EQU 49149
start ld d,7 ; select the mixer register
ld e,62 ; enable channel A only
call outer ; send it to PSG
ld d,1 ; channel A course pitch
ld e,50 ; pitch value
call outer ; send it to PSG
ld d,8 ; channel A volume
ld e,15 ; maximum
call outer ; send it to PSG
ret
outer ld bc,ayctrl ; select control port
out (c),d ; send specified value
ld bc,aydata ; select data port
out (c),e ; send specified value
ret
;AY or YM, present or absent
;from Action demo
LD BC,#FFFD
XOR A
OUT (C),A
LD B,#BF
LD A,#40
OUT (C),A
LD B,#FF
IN A,(C)
CP #40
LD HL,ayym_ABSENT ;jr z,present
JR NZ,print ;
LD A,#10
OUT (C),A
IN A,(C)
CP #FF
LD HL,AY8910
JR NZ,print
LD HL,YM2149F
print CALL PRINT
;alonecoder
Определение наличия музыкального сопроцессора
LD BC,#FFFD
XOR A ;A=0/2/4/7/11 (полные 8-битные)
OUT (C),A ;избегаем OUT (C),0
IN L,(C) ;oldRO
INC L
LD B,#BF
OUT (C),L ;newRO != oldRO
;читать R0 и проверить, изменился ли он
LD B,#FF ;#fdfd нельзя на ATM1
IN A,(C) ;R0
CP L ;newRO
JR Z,AYON ;R0 == newRO
AY_OFF
AY_OFF
LD HL,#0D00
LD DE,#FFBF
LD C,#FD
RES_AY1 LD B,D
OUT (C),H
LD B,E
OUT (C),L
DEC H
JP P,RES_AY1
RET
ay_off LD HL,#000D
SHUT1 LD BC,#FFFD
OUT (C),L
LD B,#BF
OUT (C),H
DEC L
JR NZ,SHUT1
RET
;DIGITAL GOOD QUALITY BY RAINBOW SOFT
;1BIT SAMPLE АY-3-8910/12 :
LD ВС,#FFFD ; инсталляция АY-3-8910/12
LD А,7
0UТ (С),А
LD ВС,#ВFFD
LD А,#FF ; вывод звука и шума
0UТ (С),А
LD ВС,#FFFD
LD А,8 ; каналЫ : 8 - А, 9 - В, 10 - С
0UТ (С),А
LD НL,начало инструмента
LD DE,длина инструмента
LD ВС,#ВFFD
L1 PUSН DE
LD А,(НL)
LD D,%00001111 ; маска
EХХ
LD В,задержка
DJNZ $
EХХ
RLА
LD E,А
SВС А,А
АND D
0UТ (#FE),А
0UТ (#FE),А
LD А,E ¬
RLА ¦
LD E,А ¦ повторть 7 раз
SВС А,А ¦
АND D ¦
0UТ (#FE),А -
P0P DE
INС НL
DEС DE
LD А,D
0R E
JP NZ,L1
;BY RAINBOW 8BIT SAMPLE PLAYER
LD ВС,#FFFD ; инсталляция АУ-3-8910/12
LD А,7
0UT (С),А
LD ВС,#BFFD
LD А,#FF ; вывод звука и шума
0UT (С),А
LD ВС,#FFFD
LD А,8 ; каналЫ : 8 - А, 9 - В, 10 - С
0UT (С),А
LD HL,начало инструмента
LD DE,длина инструмента
LD ВС,#BFFD
L1 LD А,(HL)
EXX
LD В,задержка
DJNZ $
EXX
RRA ; выделение 4-х старших бит
RRA
RRA
RRA
0UT (С),А
INC HL
DEC DE
LD А,D
0R E
JP NZ,L1
; WARLOCK SYSTEM3 22.12.96
;PLAYER SAMPLES FROM SAMPLER,INSTRUMENT,DIGITAL STUDIO etc 1BIT SAMPLE
ORG 30000
LD A,#0D
LD HL,OZIW
LD DE,20000
LD (OZIW).A
LD (L1),HL
LD (L2),DE
XOR A
LD C,A
CALL LOOP
INC C
XOR A
CALL LOOP
LD A,#3E
LD C,#07
CALL LOOP
EXX
LD BC,#BFFD
EXX
LD BC,#FFFD
LD HL,OZIW
L1 EQU $-2
LD DE,#0DE8
L2 EQU $-2
M1 PUSH DE
LD D,#08
OUT (C),D
LD A,(HL)
RRCA
RRCA
RRCA
RRCA
AND #0F
EXX
OUT (C),A
EXX
INC D
OUT (C),D
EXX
OUT (C),A
EXX
INC D
OUT (C),D
EXX
OUT (C),A
EXX
LD A,(OZIW)
L3 DEC A
JP NZ,L3
PUSH HL
POP HL
RRCA
RRCA
RRCA
LD A,(HL)
RRCA
RRCA
RRCA
RRCA
AND #0F
EXX
OUT (C),A
EXX
DEC D
OUT (C),D
EXX
OUT (C),A
EXX
DEC D
OUT (C),D
EXX
OUT (C),A
EXX
LD A,(OZIW)
L4 DEC A
JP NZ,L4
INC HL
POP DE
DEC DE
LD A,D
OR E
JR NZ,M1
XOR A
LD C,#08
CALL LOOP
INC C
XOR A
LOOP PUSH BC
PUSH DE
LD D,C
LD BC,#FFFD
OUT (C),D
LD B,#8F
OUT (C),A
POP DE
POP BC
RET
OZIN ; начиная с этого адреса должен располагаться инструмент
;DIGIPLAYER BY A.VOROZHKIN?
DI
LD HL,Адрес
LD DE,Длина
LD BC,#FFFD
LD A,Канал ;8 - A,9 - B,10 - C
OUT (C),A
LD B,#BF ;_OUT1
LD A,(HL)
OUT (C),A
LD B,Задержка ;от 1 до 255
DJNZ $
INC HL
DEC DE
LD A,D
OR E
JR NZ,_OUT1
EI
RET
(c) MOn5+Er^Sa9e
Супер-быстрый player with MCC
Без вступлений и окончаний приведу самый распоследний вариант
плейера для MCC метода! Теперь им можно воспроизводить ВСЕ -
теорема Котельникова играет на нас!
EI ;до запуска в порт AY n7 записать #FF!
HALT
LD SP,START-SAMPLE-ADRESS
LD IX,LOOP
LD DE,#08+256*('MCC_TBL+1)
LD BC,#FFFD
OUT (C),E ;вкл. 8 AY_порт (крайний канал)
LOOP: POP HL
LD E,L
LD A,(DE)
DEC D
EX AF,AF'
LD A,(DE)
LD E,9
OUT (253),A
EX AF,AF'
OUT (C),E
OUT (253),A
LD E,H
LD A,(DE)
INC D
EX AF,AF'
LD A,(DE)
LD E,8
OUT (253),A
EX AF,AF'
OUT (C),E
OUT (253),A
JP (IX)
Обработчик INT'а:
INT: POP IY ;взяли адр. возврата
PUSH HL ;восстановили испорченный SAMPL
LD HL,0
ADD HL,SP
LD BC,END_SAMPLE_ADDRESS
OR A
SBC HL,BC
JR NC,END_INT
LD BC,#FFFD
POP HL
JP (IY)
END_INT: выход из player'а
160 тактов на две выборки сэмпла.
80 тактов на выборку.
Fдискрет.=3,5 МГц/80=43,75 кГц.
Плеер использует только один крайний канал AY, можно и два для
двойной громкости (на качество в принципе почти не влияет кол-во
крайних каналов...)
Единственный недостаток, можно слегка проскочить конец сэмпла,
т.к. проверка раз в INT, но недостаток легко исправляется: в
конце сэмпла (а он обязательно беззнаковый 0..#FF, а не -128 до
+127) заполнен 896 байт "тишиной" - байтом #80 и всё. Почему 896
- т.к. в INT'е 71680 тактов (maximum на нетурбированной машине),
а 71680/80=896!
;ay play sound
w8912 ld hl,snddat ; start of AY-3-8912 register data.
ld e,0 ; start with register 0.
ld d,14 ; 14 to write.
ld c,253 ; low byte of port to write.
w8912a ld b,255 ; 255*256+253 = port 65533 = select soundchip register.
out (c),e ; tell chip which register we're writing.
ld a,(hl) ; value to write.
ld b,191 ; 191*256+253 = port 49149 = write value to register.
out (c),a ; this is what we're putting there.
inc e ; next sound chip register.
inc hl ; next byte to write.
dec d ; decrement loop counter.
jp nz,w8912a ; repeat until done.
ret
snddat defw 0 ; tone registers, channel A.
defw 0 ; channel B tone registers.
defw 0 ; as above, channel C.
sndwnp defb 0 ; white noise period.
sndmix defb 60 ; tone/noise mixer control.
sndv1 defb 0 ; channel A amplitude/envelope generator.
sndv2 defb 0 ; channel B amplitude/envelope.
sndv3 defb 0 ; channel C amplitude/envelope.
sndenv defw 600 ; duration of each note.
defb 0
TRACKER'S TONE TABLES
;----------------------------------------------------------------------------------------------
Итак таблица которую лично я задектил впервые у Дэвида Виттакера
(он писал в ней с 1986 года, например в игре Zub 128k)
Её же испозовали поляки в SOUND TRACKER, и наши ребята в Pro Tracker 1 и 2
(так же используется в Fast Tracker 1.хх, Global Tracker, Vortex Tracker, ST Pro и где-то еще)
Назовем ее Классической, потому что львиная часть музыки для спектрума
написана именно в данной таблице тональностей.
(в Pt2 данная табличка с небольшим косяком, но сути не меняет вцелом)
Таблица легко делается сдвигами (делением)
Ориентирована на частоту 2,0 мгц (почти))
C #C D #D E F #F G #G A #A B
ClassicToneTable DW #EF8, #E10, #D60, #C80, #BD8, #B28, #A88, #9F0, #960, #8E0, #858, #7E0
DW #77C, #708, #6B0, #640, #5EC, #594, #544, #4F8, #4B0, #470, #42C, #3F0
DW #3BE, #384, #358, #320, #2F6, #2CA, #2A2, #27C, #258, #238, #216, #1F8
NoteC_4 DW #1DF, #1C2, #1AC, #190, #17B, #165, #151, #13E, #12C, #11C, #10B, #0FC
DW #0EF, #0E1, #0D6, #0C8, #0BD, #0B2, #0A8, #09F, #096, #08E, #085, #07E
DW #077, #070, #06B, #064, #05E, #059, #054, #04F, #04B, #047, #042, #03F
DW #03B, #038, #035, #032, #02F, #02C, #02A, #027, #025, #023, #021, #01F
DW #01D, #01C, #01A, #019, #017, #016, #015, #013, #012, #011, #010, #00F
длина 192 байт (12 нот на 8 октав)
;----------------------------------------------------------------------------------------------
Тут уже моя таблица которая используется в Fast Tracker версии выше 1.00
Делал я ее с учетом таблицы Классической от Виттакера, но с учетом частоты Пентагон 128к
т.е. под 1,75 mhz
(как оказалось, по звучанию тона похожа на нативную табличку
из Vortex Tracker II Pro Tracker 3.4-3.5
(одна из популярных?)
Sand's variant of table (for pentagon aka 1.75mHz+)
Ориентирована на 1,75 мгц
FTR1xxToneTable DW #D10, #C58, #BA0, #B00, #A60, #9C8, #940, #8B8, #840, #7C0, #750, #6F0
DW #688, #62C, #5D0, #580, #530, #4E4, #4A0, #45C, #420, #3E0, #3A8, #378
DW #344, #316, #2E8, #2C0, #298, #272, #250, #22E, #210, #1F0, #1D4, #1BC
NoteC_4 DW #1A2, #18B, #174, #160, #14C, #139, #128, #117, #108, #0F8, #0EA, #0DE
DW #0D1, #0C5, #0BA, #0B0, #0A6, #09C, #094, #08B, #084, #07C, #075, #06F
DW #068, #062, #05D, #058, #053, #04E, #04A, #045, #042, #03E, #03A, #037
DW #034, #031, #02E, #02C, #029, #027, #025, #022, #021, #01F, #01D, #01B
DW #01A, #018, #017, #016, #014, #013, #012, #011, #010, #00F, #00E, #00D
длина 192 байт (12 нот на 8 октав)
А вот и таблица из Вортаха... (теоретически писалась под 1,75 мгц, отчасти
схожа с моей табличкой из Fast Tracker 1.xx)
Проверять лениво, но видимо эта табла из Pt3 под названием ASM or PSC (почему-то)))
Ориентирована на 1,75 мгц
DW #d10, #c55, #ba4, #afc, #a5f, #9ca, #93d, #8b8, #83b, #7c5, #755, #6ec
DW #688, #62a, #5d2, #57e, #52f, #4e5, #49e, #45c, #41d, #3e2, #3ab, #376
DW #344, #315, #2e9, #2bf, #298, #272, #24f, #22e, #20f, #1f1, #1d5, #1bb
NoteC_4 DW #1a2, #18b, #174, #160, #14c, #139, #128, #117, #107, #0f9, #0eb, #0dd
DW #0d1, #0c5, #0ba, #0b0, #0a6, #09d, #094, #08c, #084, #07c, #075, #06f
DW #069, #063, #05d, #058, #053, #04e, #04a, #046, #042, #03e, #03b, #037
DW #034, #031, #02f, #02c, #029, #027, #025, #023, #021, #01f, #01d, #01c
DW #01a, #019, #017, #016, #015, #014, #012, #011, #010, #00f, #00e, #00d
длина 192 байт (12 нот на 8 октав)
;----------------------------------------------------------------------------------------------
Таблица из S.Q.Tracker (плеер Витамина), авторы тракера скорее всего выбрали
ее именно из-за того что эта таблица максимально близка (практически точно) к
частоте Мелодика (чехословацкий доп.девайс с АУ8910)
Такую же таблицу использовал Линдон Шарп в Капитан Динамо и в Баббл Диззи
(в данных играх Линдон Шарп (ну или автор музыкальной инжины) скорее всего руководствовался частотой
из даташита на Микросхему AY891X
Частота MELODIK и частота из DATASHEET на АУк очень схожи = 1,789770 мелодик
и 1,789500 - в даташите, к слову на MSX АУк также работает на схожей частоте - 1,7897725 мгц)
ориентирована на частоту 1,789770 мгц
SQTrackerToneTabble DW #d5d, #c9c, #be7, #b3c, #a9b, #a02, #973, #8eb, #86b, #7f2, #780, #714
DW #6ae, #64e, #5f4, #59e, #54f, #501, #4b9, #475, #435, #3f9, #3c0, #38a
DW #357, #327, #2fa, #2cf, #2a7, #281, #25d, #23b, #21b, #1fc, #1e0, #1c5
NoteC_4 DW #1ac, #194, #17d, #168, #153, #140, #12e, #11d, #10d, #0fe, #0f0, #0e2
DW #0d6, #0ca, #0be, #0b4, #0aa, #0a0, #097, #08f, #087, #07f, #078, #071
DW #06b, #065, #05f, #05a, #055, #050, #04c, #047, #043, #040, #03c, #039
DW #035, #032, #030, #02d, #02a, #028, #026, #024, #022, #020, #01e, #01c
DW #01b, #019, #018, #016, #015, #014, #013, #012, #011, #010, #00f, #00e
длина 192 байт (12 нот на 8 октав)
;----------------------------------------------------------------------------------------------
А вот и та самая табличка из ПЗУ 128к, так же встречал ее в каком-то трэкере на Амстраде
Её же использовали парни из Players в своих трэках (а может и тот самый тракер на
Амстраде?), В Астро Марине Корпс похожая таблица.
Применялась так же (возможно в сдвинутом виде) в самой первой демоверсии Фаста
прям академическая можно сказать табличка )))
Ориентирована вот именно в таком виде на 1,773400 мгц ...довольно точно
ROM128ToneTable DW #FBF, #EDC, #E07
DW #D3D, #C7F, #BCC, #B22, #A82, #9EB, #95D, #8D6, #857, #7DF, #76E, #703
DW #69F, #640, #5E6, #591, #541, #4F6, #4AE, #46B, #42C, #3F0, #3B7, #382
DW #34F, #320, #2F3, #2C8, #2A1, #27B, #257, #236, #216, #1F8, #1DC, #1C1
NoteC_4 DW #1A8, #190, #179, #164, #150, #13D, #12C, #11B, #10B, #0FC, #0EE, #0E0
DW #0D4, #0C8, #0BD, #0B2, #0A8, #09F, #096, #08D, #085, #07E, #077, #070
DW #06A, #064, #05E, #059, #054, #04F, #04B, #047, #043, #03F, #03B, #038
DW #035, #032, #02F, #02D, #02A, #028, #025, #023, #021, #01F, #01E, #01C
DW #01A, #019, #018, #016, #015, #014, #013, #012, #011, #010, #00F, #00E
DW #00D, #00C, #00C, #00B, #00B, #00A, #009, #009, #008
длина 216 байт (12 нот на 8 октав + допольнительные)
Эта таблица использовалась в ASC Sound Macter (ASM) от Андрея Сендицкого и
в редакторе Pro Sound Creator от KVA
(отталкивались они от таблицы из ПЗУ 128к, ну и сдвинули на пару тонов,
видимо для близкого созвучия с Классической таблицей от Виттакера)
полагаю похожа на Классическую по звучанию, или на таблицу из ПЗУ 128К Бэйсика (если ее сдвинуть)
(в pt3.4r есть похожая таблица)
Ориентирована на 2,0 Мгц в таком варианте (но если выбрать иную С-4.... то, ну вы понимаете)
так же по звучанию близка к таблице от Фоллина, особенно с 4ой октавы
ASC_PSC_ToneTable DW #edc, #e07 ;subcontr
DW #d3e, #c80, #bcc, #b22, #a82, #9ec, #95c, #8d6, #858, #7e0, #76e, #704
DW #69f, #640, #5e6, #591, #541, #4f6, #4ae, #46b, #42c, #3f0, #3b7, #382
DW #34f, #320, #2f3, #2c8, #2a1, #27b, #257, #236, #216, #1f8, #1dc, #1c1
NoteC_4 DW #1a8, #190, #179, #164, #150, #13d, #12c, #11b, #10b, #0fc, #0ee, #0e0
DW #0d4, #0c8, #0bd, #0b2, #0a8, #09f, #096, #08d, #085, #07e, #077, #070
DW #06a, #064, #05e, #059, #054, #050, #04b, #047, #043, #03f, #03c, #038
DW #035, #032, #02f, #02d, #02a, #028, #026, #024, #022, #020, #01e, #01c
;DW #01a, #019, #017, #016, #015, #014, #013, #012, #011, #010 not used in ASM
длина 176 байт (в ASM - 12 нот на 7 октав, плюс еще две ноты в субконтр октавы)
Табличка из Pro Sound Maker от Дратова Дениса. Все та же таблицу из ПЗУ 128к
но со сдвигом на несколько тонов (зачем? слух? частота?)
а вот и ответ:
Дратов просто убрал "лишние" частоты (субконтр октава, например) для более корректного
звучания на частотах 1,7ххх
Ориентирована в таком виде на 1,7734 мгц (естественно что звучать она будет как табла из ПЗУ 128к)
PsmToneTable DW #D3D, #C7F, #BCB, #B22, #A82, #9EB, #95D, #8D6, #857, #7DF, #76E, #703
DW #69F, #63F, #5E6, #591, #541, #4F6, #4AE, #46B, #42C, #3F0, #3B7, #382
DW #34F, #320, #2F3, #2C8, #2A1, #27B, #257, #236, #216, #1F8, #1DC, #1C1
NoteC_4 DW #1A8, #190, #179, #164, #150, #13D, #12C, #11B, #10B, #0FC, #0EE, #0E0
DW #0D4, #0C8, #0BD, #0B2, #0A8, #09F, #096, #08D, #085, #07E, #077, #070
DW #06A, #064, #05E, #059, #054, #04F, #04B, #047, #043, #03F, #03B, #038
DW #035, #032, #02F, #02D, #02A, #028, #025, #023, #021, #01F, #01E, #01C
DW #01A, #019, #018, #016, #015, #014, #013, #012, #011, #010, #00F, #00E
длина 192 байт (12 нот на 8 октав)
;----------------------------------------------------------------------------------------------
А вот и таблица которую использовал Тим Фоллин (и, далеко не только он)
Такая же таблица найдена в тракере SoundTrakker-128, StArkos на Амстрад и
Equinoxe Tracker от UBI Soft на том же Амстраде, так же данная таблица в слегка урезаном виде
есть в редакторе Magic Sound от Henri Bittner 1987 года)
Использовалась в игре Lode Runner 128k
рассчитана на 2,0 мгц
(возможно что Фоллин использовал эту таблицу на спектруме со сдвигом)
(так же как поступил Дратов в ПСМ с табличкой из РОМ128....взять за С_4 тон #1AA)
(а может быть и нет - может Фоллин писал прямо на Амстраде, история пока умалчивает)
Данная таблица частично похожа на таблицу Виттакера, но У виттакера скажем так - упрощенный вариант.
;;; dw #EEE, #E18, #D4D
AmstradFolToneTable dw #EEE, #E18, #D4D, #C8E, #BDA, #B2F, #A8F, #9F7, #968, #8E1, #861, #7E9
dw #777, #70C, #6A7, #647, #5ED, #598, #547, #4FC, #4B4, #470, #431, #3F4
dw #3BC, #386, #353, #324, #2F6, #2CC, #2A4, #27E, #25A, #238, #218, #1FA
NoteC_4_2_0 dw #1DE, #1C3, #1AA, #192, #17B, #166, #152, #13F, #12D, #11C, #10C, #0FD
dw #0EF, #0E1, #0D5, #0C9, #0BE, #0B3, #0A9, #09F, #096, #08E, #086, #07F
dw #077, #071, #06A, #064, #05F, #059, #054, #050, #04B, #047, #043, #03F
dw #03C, #038, #035, #032, #02F, #02D, #02A, #028, #026, #024, #022, #020
dw #01E, #01C, #01B, #019, #018, #016, #015, #014, #013, #012, #011, #010
;;; dw #010, #010, #010, #010
длина 206 байт (фактический рабочий 192 байта)
;----------------------------------------------------------------------------------------------
Так называемая таблица натуральных величин (или типа того) автор Siril
ориентирована на 1,520640
NaturlScldToneTable DW #B40, #A8C, #A00, #960, #900, #870, #7E9, #780, #708, #6C0, #654, #600
DW #5A0, #546, #500, #4B0, #480, #438, #3F5, #3C0, #384, #360, #32A, #300
DW #2D0, #2A3, #280, #258, #240, #21C, #1FA, #1E0, #1C2, #1B0, #195, #180
NoteC_4 DW #168, #152, #140, #12C, #120, #10E, #0FD, #0F0, #0E1, #0D8, #0CB, #0C0
DW #0B4, #0A9, #0A0, #096, #090, #087, #07F, #078, #071, #06C, #065, #060
DW #05A, #054, #050, #04B, #048, #044, #03F, #03C, #038, #036, #033, #030
DW #02D, #02A, #028, #026, #024, #022, #020, #01E, #01C, #01B, #019, #018
DW #017, #015, #014, #013, #012, #011, #010, #00F, #00E, #00E, #00D, #00C
длина 192 байт (12 нот на 8 октав)
;---------------------------------------------------------------------------------------------
ProTracker3.3-3.4r TABLE_PROTRACKER3_3 (эта таблица отличается от нижней - стороной округления)
в Оригинальном Pt3.1 от GDC используется именно эта таблица
В самом тракере называется PROTRACKER
DW #c21, #b73, #ace, #a33, #9a0, #916, #893, #818, #7a4, #736, #6ce, #66d
DW #610, #5b9, #567, #519, #4d0, #48b, #449, #40c, #3d2, #39b, #367, #336
DW #308, #2dc, #2b3, #28c, #268, #245, #224, #206, #1e9, #1cd, #1b3, #19b
DW #184, #16e, #159, #146, #134, #122, #112, #103, #0f4, #0e6, #0d9, #0cd
DW #0c2, #0b7, #0ac, #0a3, #09a, #091, #089, #081, #07a, #073, #06c, #066
DW #061, #05b, #056, #051, #04d, #048, #044, #040, #03d, #039, #036, #033
DW #030, #02d, #02b, #028, #026, #024, #022, #020, #01e, #01c, #01b, #019
DW #018, #016, #015, #014, #013, #012, #011, #010, #00f, #00e, #00d, #00c
длина 192 байт (12 нот на 8 октав)
в середине рассчитана на 1,625 мгц
ProTracker3.4-3.5 TABLE_PROTRACKER3_4 (эта таблица отличается от верхней - стороной округления)
вроде как дело рук ММцМ (используется в новых версиях ПТ3, насколько я понимаю)
DW #c22, #b73, #acf, #a33, #9a1, #917, #894, #819, #7a4, #737, #6cf, #66d
DW #611, #5ba, #567, #51a, #4d0, #48b, #44a, #40c, #3d2, #39b, #367, #337
DW #308, #2dd, #2b4, #28d, #268, #246, #225, #206, #1e9, #1ce, #1b4, #19b
DW #184, #16e, #15a, #146, #134, #123, #112, #103, #0f5, #0e7, #0da, #0ce
DW #0c2, #0b7, #0ad, #0a3, #09a, #091, #089, #082, #07a, #073, #06d, #067
DW #061, #05c, #056, #052, #04d, #049, #045, #041, #03d, #03a, #036, #033
DW #031, #02e, #02b, #029, #027, #024, #022, #020, #01f, #01d, #01b, #01a
DW #018, #017, #016, #014, #013, #012, #011, #010, #00f, #00e, #00d, #00c
длина 192 байт (12 нот на 8 октав)
Еще одна таблица под названием Real была использована в Pro Tracker 3.x (плеер Витамина)
Видимо делалась одним из авторов доработок PT3 (тоже самое что и выше но сдвинута слегка....)
в середине рассчитана на 1,625 мгц, сдвинута на тон (вроде как ее зачем юзал WYZ tracker в первых версиях)
RealPT3ToneTable DW #cda, #c22, #b73, #acf, #a33, #9a1, #917, #894, #819, #7a4, #737, #6cf
DW #66d, #611, #5ba, #567, #51a, #4d0, #48b, #44a, #40c, #3d2, #39b, #367
DW #337, #308, #2dd, #2b4, #28d, #268, #246, #225, #206, #1e9, #1ce, #1b4
DW #19b, #184, #16e, #15a, #146, #134, #123, #112, #103, #0f5, #0e7, #0da
DW #0ce, #0c2, #0b7, #0ad, #0a3, #09a, #091, #089, #082, #07a, #073, #06d
DW #067, #061, #05c, #056, #052, #04d, #049, #045, #041, #03d, #03a, #036
DW #033, #031, #02e, #02b, #029, #027, #024, #022, #020, #01f, #01d, #01b
DW #01a, #018, #017, #016, #014, #013, #012, #011, #010, #00f, #00e, #00d
длина 192 байт (12 нот на 8 октав)
;----------------------------------------------------------------------------------------------
Таблица с MSX использовалась в PSG Tracker (c)1992 Flying Bytes
очень близка к таблице из ПЗУ 128к Бэйсика на спектруме
dw #D5D, #C9C, #BE7, #B3C, #A9B, #A03, #973, #8EB, #86B, #7F2, #780, #714
dw #6AE, #64E, #5F4, #59E, #54D, #501, #4B9, #475, #435, #3F9, #3C0, #38A
dw #357, #327, #2FA, #2CF, #2A7, #281, #25D, #23B, #21B, #1FC, #1E0, #1C5
dw #1AC, #194, #17D, #168, #153, #140, #12E, #11D, #10D, #0FE, #0F0, #0E2
dw #0D6, #0CA, #0BE, #0B4, #0AA, #0A0, #097, #08F, #087, #07F, #078, #071
dw #06B, #065, #05F, #05A, #055, #050, #04C, #047, #043, #040, #03C, #039
dw #035, #032, #030, #02D, #02A, #028, #026, #024, #022, #020, #01E, #01C
; dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
;----------------------------------------------------------------------------------------------
TURBO SOUND FM
Программирование Turbo Sound by Shiru Otaku
Выбор чипа
Оба чипа управляются одними и теми же стандартными портами (#FFFD - выбор регис─
тра AY, #BFFD - вывод значения в регистр AY). В один момент времени к этим портам
подключается один из чипов, и все выводы в регистры AY направляются в него.При сбросе
выбирается чип номер 0. Переключение между чипами производится путём выбора регистра
AY с номером,находящимся вне диапазона существующих регистров. Выбор регистра #FF
подключает нулевой чип, #FE - первый.
Условная процедура выбора 0-го чипа:
selChip0:
LD BC,#FFFD
LD A,#FF
OUT (C),A
RET
Аналогично происходит выбор 1-го чипа:
selChip1:
LD BC,#FFFD
LD A,#FE
OUT (C),A
RET
Можно сделать простую процедуру,выбирающую нужный чип по его номеру в аккумуляторе (0...1):
selChip:
LD BC,#FFFD
AND 1 ; во избежание ошибок
; при числе в аккумуляторе,
; не равном 0 или 1
XOR B
OUT (C),A
RET
Полная совместимость TS со всем ранее написанным ПО обеспечивается при условии,
что оно не пытается производить выбор несуществующих регистров AY (на текущий
момент программ, не отвечающих этому условию, не замечено).
Использование TS
Так как второй чип не имеет собственных управляющих портов, обеспечивается возмож─
ность использовать любые ранее написанные процедуры вывода звука без всяких дорабо─
ток. Например,есть две процедуры: проигрывающая музыку и проигрывающая звуковые
эффекты. На одном чипе для проигрывания звуковых эффектов параллельно с музыкой
приходится временно выключать один или несколько каналов музыки, чтобы дать возмож─
ность проиграться звуковому эффекту. Приналичии Turbo Sound можно запустить музыку
на одном чипе, а эффекты на втором,используя те же процедуры. Нужно только вставить
переключение чипов между ними.
Например, было:
CALL musicPlay
CALL soundPlay
Стало:
CALL selChip1
CALL musicPlay
CALL selChip0
CALL soundPlay
При отсутствии TS новый вариант будет работать точно так же, как старый.
На текущий момент не существует специализированных плееров музыки под TS. Но в
тестовых целях возможно осуществить такое проигрывание, создав два обычных трёхкана─
льных модуля (в каждом из них должно содержаться по три канала шестиканальной ком─
позиции) и откомпилировав их в разные адреса. Для проигрывания достаточно будет
сделать следующее:
CALL selChip1
CALL player1
CALL selChip0
CALL player0
При этом каждому из плееров не нужно знать, на какой чип он выводит данные.
Важный момент: после завершения работы с TS нужно выбирать нулевой чип. Также это
нужно делать перед выполнением программного сброса.В противном случае возможны про─
блемы с работой устройств, подключаемых к портам ввода-вывода AY, так как на разъём
TS заводятся только линии портов ввода-вывода нулевого чипа. Примеры кода,приведён─
ные выше, учитывают это требование.
Другие реализации Turbo Sound
Всё вышесказанное относилось к работе с TS версии NedoPC. Но существует также два
старых варианта схемы Turbo Sound: QUADRA от Amazing Soft Making и вариант TS от
Power of Sound.
Вариант QUADRA полностью несовместим с NedoPC TS - в нём предполагается наличие
собственных управляющих портов для второго чипа (#EEFD - выбор регистра AY, #AFFD -
вывод значения). Поддержка этого варианта схемы лишена смысла ввиду его непопулярно─
сти и полном отсутствии программного обеспечения.
Вариант TS от Power of Sound поддержан в единственном на данный момент редакторе
шестиканальной музыки, Turbo Sound Editor. С точки зрения программирования он отлича─
ется только способом переключения чипов, что позволяет легко добавить его поддержку
в создаваемом для NedoPC TS программном обеспечении.Для этого достаточно предоста─
вить пользователю возможность выбора типа TS и соответствующим образом доработать
процедуры выбора чипа.
Переключение чипов в PoS TS происходит посредством вывода значения 0..1 в порт
#1F (номер выбираемого чипа соответствует выводимому значению).
Ниже приводится вариант универсальной процедуры выбора чипа по его номеру в ак─
кумуляторе. Предполагается,что тип TS сохранён по адресу chipType+1, нулевое значе─
ние соответствует варианту TS от NedoPC, ненулевое - варианту от PoS.
selChip:
AND 1
chipType:
LD B,0 ; вместо нуля присутствует
;число, соответствующее типу
TS
DEC B
JR NC,chipPoS
LD C,#FD
XOR B
OUT (C),A
RET
chipPoS:
OUT (#1F),A
RET
Автоопределение наличия и типа TS
Ниже приводится текст процедуры автоматического определения наличия и типа TS
(варианты NedoPC и PoS ), использующаяся в Turbo Sound Editor . Следует учесть, что в
случае невозможности чтения значений регистров AY (встречается в редких случаях)
автоматическое определение работать не будет.Поэтому желательно предусматривать во─
зможность указания типа TS 'вручную'.
;Turbo-Sound checker by Himik's ZxZ/PoS-WT
;24.05.05 at work ;)
;Found:
;No AY/YM chip on board
;Single AY/YM chip on board
;Turbo-Sound port by PoS & Bitwalker
;(port #1F for swith)
;Turbo-Sound port by NedoPC
;(registers #FE-#FF selection)
ORG #61A8
C_1
DI
XOR A
LD HL,#FE00
LD DE,#FFBF
LD BC,#FFFD
OUT (C),B ;SELECT TS AY0 CHRV
OUT (C),A ;SELECT REG 0
LD B,E
OUT (C),B ;#BF-> REG 0 AY0 CHRV
INC A
OUT (#1F),A ;SELECT TS AY1 POS
OUT (C),C ;#FD-> REG 0 AY1 POS
LD B,D
OUT (C),H ;SELECT TS AY1 CHRV
OUT (C),L ;SELECT REG 0
LD B,E
OUT (C),H ;#FE-> REG 0 AY1 CHRV
LD A,L
OUT (#1F),A ;SELECT TS AY0 POS
OUT (C),L ;#00-> REG 0 AY0 POS
INC A
OUT (#1F),A ;SELECT TS AY1 POS
LD B,D
OUT (C),D ;SELECT TS AY0 CHRV
OUT (C),L ;SELECT REG 0
IN A,(C) ;READ BYTE FROM REG 0
CP C
;переходим, если найден TS by NedoPC
JR Z,TS_ENABLE_CHRV
;переходим, если найден TS by PoS
CP #FE
JR Z,TS_ENABLE_POS
;переходим, если ни одного чипа не найдено
CP #FF
JR Z,NO_AY
C_2
;найден только один чип
TE_DISABLE
LD A,1
OUT (#FE),A
RET
NO_AY
LD A,2
OUT (#FE),A
RET
TS_ENABLE_CHRV
LD A,4
OUT (#FE),A
RET
TS_ENABLE_POS
LD A,6
OUT (#FE),A
RET
DISPLAY /A, "Length: ",C_2-C_1
Shiru (NedoPC team)
проверка наличия TSFM
LD BC,#FFFD
LD A,%11111000
OUT (C),A ;FM on,status rg read on
ld d,b
dec d
jr nz,$-1 ;pause
XOR A
OUT (C),A ;select rg 0
dec d
jr nz,$-1 ;pause
LD B,#BF
OUT (C),B ;write some data in rg 0
dec d
jr nz,$-1 ;pause
inc a ;a=1
LD B,#FF
INF ;read status (P=ready)
JP P,tfmpresent ;a=1
xor a ;TFM not present
tfmpresent
ld (TURBOFMON),a
BEEPER
;из нового Fast Tracker'a
Beep LD BC, #0418 ;04 = громкость
LD A, (BorderColor)
XOR C
OUT (#FE), A
bee1 DJNZ bee1
XOR C
OUT (#FE), A
RET
xor a
loopy xor %00010000 ;bits ---S-BBB S=Sound B=Border
out (#fe),a
ld bc,3000 ;Lower number=higher pitch
pausey dec c
jr nz,pausey
dec b
jr nz,pausey
jr loopy
;1BIT SAMPLE PLAYER FOR BEEPER BY RAINBOW SOFT
LD HL,начало инструмента
LD DE,длина инструмента
L1 LD А,(HL)
LD С,%00010000 ; маска
LD В,задержка
DJNZ $
RLA
LD В,А
SBC А,А
AND С
0UT (#FE),А
LD А,В ¬
RLA ¦
LD В,А ¦ повторть 7 раз
SBC А,А ¦
AND С ¦
0UT (#FE),А -
INC HL
DEC DE
LD А,D
0R E
JP NZ,L1
;Steve Turner / Hewson Cons
;sfx engineused in Ranarama, Quazatron, IronMan
;можно вешать на прерывания или вызывать в основном цикле
;you can use this routine on interrupts (as autor) or in main game cicle
sound ld a,(sonreq) ;новый звук играем?
and a
jr z,nonew ;нет
;да
ld (sonnow),a
dec a
jr z,noise ;#01 шум
ld hl,sfx_data
dec a
add a,a
add a,a
add a,a
ld e,a
xor a
ld (sonreq),a
ld d,a
add hl,de
ld bc,08
ld de,sonfrq
ldir
jr process
nonew ld a,(sonnow) ;а старый звук есть?
and a
ret z
dec a ;продолжать шум?
jr nz,process ;продолжать звук
jr cnois
noise ld a,0ah
ld (sonlen),a
xor a
ld (sonreq),a
cnois ld b,30h
gain call random
and 10h
out (0feh),a
ld c,02h
make dec c
jr nz,make
djnz gain
ld hl,sonlen
dec (hl)
ret nz
xor a
ld (sonnow),a
ret
process ld a,(sonfrq)
ld h,a
ld a,10h
ld d,0ffh
sonlp ld e,h
out (0feh),a
xor 10h
freq dec d
jr z,mod
dec e
jr nz,freq
jr sonlp
mod ld a,(soncfg)
add a,h
ld (sonfrq),a
ld hl,sonmod
dec (hl)
ret nz
ld hl,sonlen
dec (hl)
jr nz,modify
xor a
ld (sonnow),a
ld a,(sonnex)
and a
ret z
ld (sonreq),a
ret
modify ld a,(sobrsf)
ld c,a
ld a,(sontyp)
and a
jr z,reset
dec a
jr z,typ1
dec a
jr z,typ2
typoth ld a,(soncfg)
neg
ld (soncfg),a
jr mode
typ2 inc c
inc c
ld a,c
ld (sobrsf),a
jr reset
typ1 dec c
dec c
ld a,c
ld (sobrsf),a
jr reset
reset ld a,c
ld (sonfrq),a
mode ld a,(sonrnd)
ld (sonmod),a
ret
random push hl
ld hl,(rnseed)
inc hl
ld a,h
and 03
ld h,a
rok ld (rnseed),a
ld a,r
xor (hl)
pop hl
ret
rnseed defw 1000h
sonfrq defb 00 ;начальная частота ;start frequency
soncfg defb 00 ;скорость изменения частоты ;frequency change
sonmod defb 00 ;количество модуляций в звуке ;change times
sonlen defb 00 ;количество повторений звука ;repeat times
sontyp defb 00 ;вид модуляции ;modulate type
;0 sawtooth
;1 2nd mod down
;2 2nd mod up
;3+ triangle
sobrsf defb 00 ;частота сброса ;reset frequency
sonrnd defb 00 ;темп изменения частоты сброса ;change reset temp
sonnex defb 00 ;приклеенный эффект ;linked sfx
sonnow defb 00 ;что играем ;
sonreq defb 00 ;меняем эффект ;
sfx_data
;here all souned excepts number 1 reserved for random noise
; defb 0, 5, 5, 1, 0, 0, 0, 0
; defb 28h, 5, 0Ah, 1, 0, 0, 0, 0
; defb 0, 80h, 1Eh, 1, 0, 0, 0, 0
; defb 0, 2, 1Eh, 1, 0, 0, 0, 0
; defb 0, 7Dh, 20h, 1, 0, 0, 0, 0
; defb 0FFh, 83h, 20h, 1, 0, 0, 0, 0
; defb 0FFh, 83h, 28h, 20h, 1, 3Ch, 1, 0
; defb 0F0h,0F0h, 8, 3, 0, 3Ch, 6, 0
; defb 2, 80h, 0Ah, 1, 0, 0, 0, 0
; defb 28h,0FAh, 8, 1, 0, 0, 0, 0
; defb 0FAh, 2Ch, 6, 0Ah, 1, 5Ah, 1, 0
; defb 0,0FCh, 14h, 8, 1, 50h, 8, 0
; defb 0E6h,0E6h, 4, 1, 1, 0, 0, 0
; defb 2Dh, 43h, 14h, 1, 1, 0, 0, 0
; defb 0, 0, 0, 0, 0, 0, 0, 0
; defb 0, 0, 0, 0, 0, 0, 0, 0
; defb 0, 0, 0, 0, 0, 0, 0, 0
; defb 0, 0, 0, 0, 0, 0, 0, 0
; defb 0, 0, 0, 0, 0, 0, 0, 0
; defb 0, 0, 0, 0, 0, 0, 0, 0
COVOX
Теперь конкретно о воспроизведении
Digital эффектов:
Оцифрованный звук представляет из себя
конечный набор восьмиразрядных байт, характе-
ризующих значения амплитуды цифруемых колеба-
ний в дискретные моменты времени...
Для воспроизведения оцифровок нужно подавать
в порты управления громкостью эти байты с
определенной частотой...
Распространены два вида форматов Digital звука
1.Wav-файлы: Оцифровка производиться по 256-
бальной шкале (0..255), только положительной
части звуковой волны...
2.Aif-файлы: Оцифровка производиться по двум
128-бальным шкалам:
Положительная часть волны (0...128)
Отрицательная часть волны (-128...0)
Рассмотрим как же воспроизвести оцифр. звук:
Если у вас есть COVOX, то значения байтов из
WAV-файла целиком и полностью подаются в порт
данных COVOX`а. Для проигрывания AIF-файлов к
каждому байту перед подачей его на COVOX надо
прибавить число 128...
Это обуславливается тем, что COVOX -это уст-
ройство для воспроизведения восьмибитных WAV-
-файлов... и AIF необходимо преобразовать для
корректного проигрывания COVOX`ом...
А теперь программа:
ORG 30000
COVOX EQU #FB
START EQU 35000
LENGTH EQU 5000
PAUSA EQU 20
XOR A
OUT (COVOX),A
LD HL,START
LD DE,LENGTH
LOOP LD A,(HL)
*** ADD A,128
OUT (COVOX),A
LD B,PAUSA
DJNZ $
INC HL
DEC DE
LD A,D
OR E
JR NZ,LOOP
RET
Если же у вас нет COVOX`а,но есть AY,то можно
воспроизвести DIGITAL звук на нем, хотя каче-
ство будет похуже:
ORG 30000
START EQU 35000
LENGTH EQU 5000
PAUSA EQU 20
CHANEL EQU 10
LD BC,#FFFD
LD A,#07
OUT (C),A
LD B,#BF
LD A,#FF
OUT (C),A
LD HL,START
LD DE,LENGTH
LD BC,#FF
LD A,CHANEL
OUT (C),A
LOOP LD B,#BF
LD A,(HL)
*** ADD A,128
SRL A
SRL A
SRL A
OUT (C),A
LD B,PAUSA
DJNZ $
INC HL
DEC DE
LD A,D
OR E
JR NZ,LOOP
RET
Переменные используемые в программах имеют
следующие значения:
START-адресс хранения в памяти оцифровки.
LENGTH-длина оцифровки.
COVOX-порта COVOX`а (обычно #FB для PENT128,
и 231 для SCORPIONA256)...
CHANEL-канал для воспроизведения(см. выше)
PAUSA-самый главный параметр.Он определяет с
какой частотой будет проигрываться оцифровка.
Вообще говоря PAUSA задает время паузы между
двумя соседними выводами в звук. канал...
В обоих случаях звездочкой помечены строчки,
которые необходимо оставить, если воспроизво-
дятся AIF-файлы, и надо удалить для воспроиз-
ведения WAV-файлов...
Вот в принципе и все что надо знать чтобы вос-
произвести оцифровку...
Hо данный способ воспроизведения является
частотовремязависимым, т.е. при изменении час-
тоты изменяется соответствующим образом и вре-
мя воспроизведения инструмента...
DI
LD HL,ADR ;начало
LD DE,LEN ;длина
CYC LD A,(HL)
OUT (251),A ;COVOX
LD B,N ;N-замед-
TORM DJZN TORM ;ление
INC HL
DEC DE
LD A,D
OR E
JR NZ,CYC
EI
RET
Управление
KEYBOARD
;keyboard bits-ports table by sand/mhm
bit: 0 1 2 3 4 , 4 3 2 1 0 :bit
----port .---+---+---+---+---|---+---+---+---+---. port----
3 | #F7 | 1 ¦ 2 ¦ 3 ¦ 4 ¦ 5 | 6 ¦ 7 ¦ 8 ¦ 9 ¦ 0 | #EF | 4
.---+---+---+---+---|---+---+---+---+---.
2 | #FB | Q ¦ W ¦ E ¦ R ¦ T | Y ¦ U ¦ I ¦ O ¦ P | #DF | 5
:---+---+---+---+---|---+---+---+---+---:
1 | #FD | A ¦ S ¦ D ¦ F ¦ G | H ¦ J ¦ K ¦ L ¦ENT| #BF | 6
'---+---+---+---+---|---+---+---+---+---'
0 | #FE |CS ¦ Z ¦ X ¦ C ¦ V | B ¦ N ¦ M ¦SS ¦SPC| #7F | 7
'---+---+---+---+---'---+---+---+---+---'
WAITFORKEY:
xor a
in a, (#fe)
cpl
and #1f ;%00011111
jp z, WAITFORKEY
....key pressed
anykey
XOR A
IN A, (#FE)
CPL
AND #1F
RET
;ожидание отпускания клавиши
anykeypushed
call anykey
jr nz,anykeypushed
ret
;опрос BREAK и EDIT (например в качестве выхода)
key_Break
LD A, #FE ;BREAK (CS+SPACE) and EDIT MODE (CS+1)
IN A, (#FE)
RRA ;1
RET C
LD A, #7F
IN A, (#FE)
RRA ;SPC
RET NC
LD A, #F7
IN A, (#FE)
RRA ;CS
RET
;ПРОЦЕДУРА KEY ВОЗВРАЩАЕТ ФЛАГ Z=0,ЕСЛИ НА КЛАВИАТУРЕ ЧТО-НИБУДЬ НАЖАТО.
KEY XOR A
IN A,(#FE)
AND %00011111
CP %00011111
RET
;Y or N wait (какой-то из них вроде глючит в FUSE)
LD BC,#DFFE ;---------OLD KEYS----------
IN A,(C) ; XOR A
LD A,#20 ; IN A,(#FE)
IN A,(#FE) ; CPL
AND B ; AND #1F
OR #E0 ; JR Z,KEYS
INC A ; LD A,#DF
JR Z,KEYS ; IN A,(#FE)
BIT 4,B ; AND #10
JR Z,POKE ;Y ; JR Z,POKE ;Y
JR NZ,GAME ;N ; JR NZ,GAME ;N
;---------------------------
; Опросник нажатой клавиши через #5c08 из Фаста, например
KeyScaner XOR A
LD HL, SYS5C00+8 ;последняя нажатая
LD (HL), A
KS1 EI
HALT
OR (HL)
JR Z, KS1
CP #20
JR NZ, ksz
LD A, #FE
IN A, (#FE)
RRA
LD A, #20
JR C, ksz
LD A, #1F
ksz LD (HL), 0
RET
Колесников Сергей
KEY HALT ;Ждем следующее прерывание
XOR A ;обнуляем аккумулятор.
LD A,#F7 ;заносим в аккумулятор
;наш полуряд (третий).
IN A,(#FE);младший бит адреса порта.
BIT 0,A ;определяем состоян. бита, ;RRCA
;соответствующ.клавише "1"
;если бит активен, то флаг
;Z=1, если нет, то Z=0.
JR Z,BLUE ;если Z=1, то операция ;JR C,BLUE
;JR Z... будет выполняться
;если Z=0, мы ее "проско-
;чили".
LD A,#F7 ;опять наш полуряд.
IN A,(#FE);опрос клавы.
BIT 1,A ;определяем состояние пер-
;вого бита, который соот-
;ветствует клавише "2".
JR Z,RED ;здесь аналогично предыду-
;щему.
JR KEY ;больше опрашивать ничего
;не надо, "зацикливаемся".
BLUE LD A,1 ;в аккумулят. - цвет синий
OUT (254),A; BORDER 1.
RET
RED LD A,2 ;двойка соответст. красному
OUT (254),A; BORBER 2.
RET
Следует заметить, что есть еще один
практически такой же способ опроса клавиа-
туры, который отличается от вышеизложенно-
го тем, что спецификация проверяемого ряда
находится в регистровой паре BC, а код на-
жатой клавиши передается в регистр А. То
есть:
LD BC,nnnn
IN A,(C) ;ввод данных из порта
;в аккумулятор.
Здесь nnnn - адрес внешнего порта, ус-
танавливаемого в BC. Надо только помнить,
что в регистре С будет #FE (постоянно), а
в В - значение, соответствующее номеру по-
луряда. Например, для нашей первой прог-
раммки вместо:
LD A,#F7
IN A,(#FE)
следует набрать:
LD BC,#F7FE
IN A,(C)
; Title: ZX Spectrum Keyboard Routines
; Author: Dean Belfield
; Created: 29/07/2011
; Last Updated: 29/07/2011
; Requires:
; Modinfo:
;; Read the in-game controls
; HL: The control map
; Returns:
; A: Input flags - 000UDLRF (Up, Down, Left, Right, Fire)
; Zero flag set if no key pressed
Read_Controls: LD D, 5 ; Number of controls to check
LD E, 0 ; The output flags
LD C,0xFE ; Low is always 0xFE for reading keyboard
Read_Controls1: LD B,(HL) ; Get the keyboard port address
INC HL
IN A,(C) ; Read the rows in
AND (HL) ; And with the mask
JR NZ, Read_Controls2 ; Skip if not pressed (bit is 0)
SCF ; Set C flag
Read_Controls2: RL E ; Rotate the carry flag into E
INC HL
DEC D
JR NZ, Read_Controls1 ; Loop
LD A,E ; Fetch the key flags
AND A ; Check for 0
RET
; As Read_Keyboard, but with debounce
Read_Keyboard_Debounce: CALL Read_Keyboard ; A debounced versiion - Read the keyboard
AND A ; Quick way to do CP 0
JR NZ, Read_Keyboard_Debounce ; Loop until key released
1xx CALL Read_Keyboard ; And second loop reading the keyboard
AND A ; CP 0
JR Z, 1xx ; Loop until key is pressed
RET
; Read the keyboard and return an ASCII character code
; Returns:
; A: The character code, or 0 if no key pressed
; BC: The keyboard port (0x7FFE to 0xFEFE)
;
Read_Keyboard: LD HL,Keyboard_Map ; Point HL at the keyboard list
LD D,8 ; This is the number of ports (rows) to check
LD C,#FE ; Low is always 0xFE for reading keyboard ports
Read_Keyboard_0: LD B,(HL) ; Get the keyboard port address
INC HL ; Increment to keyboard list of table
IN A,(C) ; Read the row of keys in
AND #1F ; We are only interested in the first five bits
LD E,5 ; This is the number of keys in the row
Read_Keyboard_1: SRL A ; Shift A right; bit 0 sets carry bit
JR NC,Read_Keyboard_2 ; If the bit is 0, we've found our key
INC HL ; Go to next table address
DEC E ; Decrement key loop counter
JR NZ,Read_Keyboard_1 ; Loop around until this row finished
DEC D ; Decrement row loop counter
JR NZ,Read_Keyboard_0 ; Loop around until we are done
AND A ; Clear A (no key found)
RET
Read_Keyboard_2: LD A,(HL) ; We've found a key at this point; fetch the character code!
RET
Keyboard_Map: DB #FE,"#","Z","X","C","V"
DB #FD,"A","S","D","F","G"
DB #FB,"Q","W","E","R","T"
DB #F7,"1","2","3","4","5"
DB #EF,"0","9","8","7","6"
DB #DF,"P","O","I","U","Y"
DB #BF,"#","L","K","J","H"
DB #7F," ","#","M","N","B"
Input_Custom: DB #FB, %00000001 ; Q (Up)
DB #FD, %00000001 ; A (Down)
DB #DF, %00000010 ; O (Left)
DB #DF, %00000001 ; P (Right)
DB #7F, %00000001 ; Space (Fire)
; FROM JERRI
;scan_kbd -вешается на прерывания сканирование клавиатуры
; -остальные подпрограммы работают с массивом созданым этой подпрограммой
;HL,BC,AF
;scan_ctrl -проверяет нажатые клавиши согласно таблице k_table до 8 штук
;HL,DE,BC,AF,HL',DE',BC'
;A -результат
;test_joypad - проверка управления scan_ctrl
;HL,DE,BC,AF,HL',DE',BC'
; - в ячейке key_presed только что нажатых кнопок
; - в ячейке key_holded удерживаемых кнопок
;scan_tbl -используется при задании таблицы сканкодов для k_table
;HL,BC,AF
;BC -результат B-ряд 0-7 С-маска #10-#01
;key_char -сканирование нажатой клавиши
;HL,DE,BC,AF
;A,key_presed -результат
;test_key -проверка нажатия любой новой клавиши через key_char
;HL,DE,BC,AF
;A -клавиша
;Carry -нажата новая клавиша
;NotCarry -удерживаемых старая клавиша или ничего не нажато
;test_kemp -тестирование подключенного kempston joystikа с 1ой кнопкой
;HL,DE,BC,AF
;A,kemp_act -#00 джойстик присутсвует
; -#c9 джойстик не найден
;по схеме
; store_kemp_act
; call test_kemp
; xor #c9
; xor store_kemp_act
; ld (kemp_act),a
;можно включать/выключать обработку джойстика
;------------------------------------
scan_kbd
ld a,-2
ld c,a
ld hl,kb_buf
dup 7
ld b,a
ini
rlca
edup
ld b,a
ini
ret
;------------------------------------
scan_ctrl
ld bc,k_table
ld de,kb_buf
exx
ld e,#80
scan_ctrl0
exx
ld a,(bc)
inc bc
ld l,a
ld h,0
add hl,de
ld a,(bc)
inc bc
and (hl)
cp 1
exx
rr e
jr nc,scan_ctrl0
ld a,e
kemp_act ret
in a,(#1f)
and #1f
or e
ret
;------------------------------------
test_joypad
call scan_ctrl
ld c,a
ld a,(key_holded)
xor c
and c
ld (key_presed),a
ld a,c
ld (key_holded),a
ret
;------------------------------------
scan_tbl
ld hl,kb_buf
ld b,8
scan_tbl0
ld c,#10
scan_tbl1
ld a,(hl)
and c
cp 1
jr c,scan_tbl2
rrc c
jr nc,scan_tbl1
inc hl
djnz scan_tbl0
ld c,b
ret
scan_tbl2
ld a,8
sub b
ld b,a
ret
;------------------------------------
key_char
ld de,keys_table
ld hl,kb_buf
ld c,8
key_char0
ld a,(hl)
inc hl
ld b,5
key_char1
rra
jr nc,key_char2
inc de
djnz key_char1
dec c
jr nz,key_char0
key_char2
ld a,(de)
ld (key_presed),a
ret
;------------------------------------
test_key
call key_char
ld c,0
old_key equ $-1
ld (old_key),a
cp c
scf
ccf
ret z
cp 1
ccf
ret
;------------------------------------
test_kemp
ld bc,#001f
ld l,b
ld e,b
i_00
in a,(c)
or e
ld e,a
dec l
jr nz,i_00
ld a,e
and c
jr z,kemp_present
ld a,#c9
kemp_present
ld (kemp_act),a
ret
;------------------------------------
k_table
; p o a q Sp En H R
; 0 1 2 3 4
; r l d u f
db 5, 1 ;right p 5, 1
db 5, 2 ;left o 5, 2
db 1, 1 ;down a 1, 1
db 2, 1 ;up q 3, 1
db 7, 1 ;fire spc 7, 1
db 6, 1 ;key6 en 6, 1
db 2, 8 ;key7 R 2, 8
db 5, 16 ;key8 Y 5, 16
key_presed db 0
key_holded db 0
kemp_var db 0
cur_keys db 0
kb_buf ds 8
; 1 2 4 8 16
;#fe 0 cs z x c v
;#fd 1 a s d f g
;#fb 2 q w e r t
;#f7 3 1 2 3 4 5
;#ef 4 0 9 8 7 6
;#df 5 p o i u y
;#bf 6 en l k j h
;#7f 7 sp ss m n b
keys_table
db #01,"ZXCV"
db "ASDFG"
db "QWERT"
db "12345"
db "09876"
db "POIUY"
db #0d,"LKJH"
db #20,#02,"MNB",#00
KEMPSTON
;kempston (original) table by sand'mHm
.----------.
| up |
| ???00100 |
.----------x----------x----------.
| left | fire | right |
| ???00010 | ???10000 | ???00001 |
'----------x----------x----------'
| down |
| ???01000 |
'----------'
LD B,0 ;Проверяем порт на стабильность, т.к. работа нестабильного кемпстона нежелательна.
LOOP IN A,(31) ;#1F NOT #DF!!!!
AND A
JR NZ,KEMP_OFF
DJNZ LOOP KEMP_OFF
LD (KEMPSTON),A
. . . . KEMPSTON DEFB 0 ;Если число не равно нулю, то кемпстон отсутствует.
;from chain reaction
CHKEMP LD BC,0 ; Check for kempston joystick present
CHKEM1 IN A,(#1F)
OR C
LD C,A
DJNZ CHKEM1
AND #1F
RET ; Returns zero flag set if kempston
;from Sword Of Ianna
; Read routine for kempston joysticks
read_kempston_joystick:
ld c, 31
in c, (c)
ld a, 255
cp c
jr z, nokempston ; if the value read is 255, then there is no kempston interface
xor a ; clear carry and A
kempston_right rr c
jr nc, kempston_left
or $08 ; right is pressed
kempston_left rr c
jr nc, kempston_down
or $04 ; left is pressed
kempston_down rr c
jr nc, kempston_up
or $02 ; down is pressed
kempston_up rr c
jr nc, kempston_fire
or $01 ; up is pressed
kempston_fire rr c
ret nc ; no carry, just return
or $10
ret
nokempston xor a
ret ; nothing read
; Example Kempston Joystick control routine.
joycon ld bc,31 ; Kempston joystick port.
in a,(c) ; read input.
and 2 ; check "left" bit.
call nz,joyl ; move left.
in a,(c) ; read input.
and 1 ; test "right" bit.
call nz,joyr ; move right.
in a,(c) ; read input.
and 8 ; check "up" bit.
call nz,joyu ; move up.
in a,(c) ; read input.
and 4 ; check "down" bit.
call nz,joyd ; move down.
in a,(c) ; read input.
and 16 ; try the fire bit.
call nz,fire ; fire pressed.
SINCLAIR 1 & 2
;from Sword Of Ianna
; Read routine for Sinclair 1 joysticks
read_sinclair1_joystick:
ld bc, $effe
in c, (c) ; Leemos solo la fila 6-0. Los bits a 0 estan pulsados
xor a
sinclair1_fire rr c
jr c, sinclair1_up
or $10 ; fire is pressed
sinclair1_up rr c
jr c, sinclair1_down
or $01 ; up is pressed
sinclair1_down rr c
jr c, sinclair1_right
or $02 ; down is pressed
sinclair1_right rr c
jr c, sinclair1_left
or $08 ; right is pressed
sinclair1_left rr c
ret c ; no carry, just return
or $04 ; left pressed
ret
;from Sword Of Ianna
; Read routine for Sinclair 2 joysticks
read_sinclair2_joystick:
ld bc, $f7fe
in c, (c) ; Leemos solo la fila 1-5. Los bits a 0 estan pulsados
xor a
sinclair2_left rr c
jr c, sinclair2_right
or $04 ; left is pressed
sinclair2_right rr c
jr c, sinclair2_down
or $08 ; right is pressed
sinclair2_down rr c
jr c, sinclair2_up
or $02 ; down is pressed
sinclair2_up rr c
jr c, sinclair2_fire
or $01 ; up is pressed
sinclair2_fire rr c
ret c ; no carry, just return
or $10 ; left pressed
ret
MOUSE
.---------------------x-------------------.
|KEMPSTON MOUSE port: | AMX MOUSE ports: |
|---------------------x-------------------|
| buttons= #FADF | #1F(31) Data A |
| X-AXIS = #FBDF | #3F(63) Data B |
| Y-AXIS = #FFDF | #DF mouse button |
'---------------------x-------------------'
(c) Alexander Nikiforov
Hi world! Решил я тут немного рассказать о драйвере поддержки
kempston mouse. Эта идея возникла у меня после просмотра газеты
BODY, в которой SATSOFT никак не может (или не хочет ?) сделать
нормально работающий драйвер мышки.
Kempston mouse подключается к порту #DF:
#FADF - состояние кнопок мышки:
bit 0 - левая кнопка,
bit 1 - правая кнопка,
bit 2 - средняя кнопка.
#FBDF - X-координата мышки
#FFDF - Y-координата мышки
Состояние портов #FBDF и #FFDF само меняется при движении мы-
ши. Старшие пять битов порта #FADF не используются и установлены
в еденицу, а три младших бита показывают состояние кнопок: 0 -
кнопка не нажата, 1 - нажата. Но с кнопками не все так просто.
Дело в том, что в мышках, которые продает в Минске Stinger, не
используется третья кнопка. Поэтому, если Вы собираетесь исполь-
зовать в программе третью кнопку, то продублируйте ее нажатием
двух крайних кнопок одновременно (т.е. одновременное нажатие ле-
вой и правой кнопки должно вызывать то же действие, что и третья
кнопка). Так же не существует единого мнения какой бит (0 или 1)
должен отвечать за левую кнопку, а какой за правую. У меня нуле-
вой бит - это левая кнопка (поддерживает FUT и ZX-WORD), а у
кого-то возможно это правая кнопка (поддерживает НЛО 2, ЧЕРНЫЙ
ВОРОН (демо), MINESWEEPER). Поэтому рекомендуется делать драйвер
с автоопределением кнопки fire, т.е. первая нажатая кнопка ста-
новится выстрелом (так сделано в ZX-FORMAT, ZxNews и в этом но-
мере LPRINT'а). Или делайте обе кнопки выстрелом (MOVE, ENERGY,
старые номера LPRINT /с моим viewer'ом/).
Ну а теперь собственно драйвер с небольшими комментариями.
Программа проверки наличия kempston mouse. Если мышки у Вас
нет то из всех трех портов должно считываться одинаковое число
(обычно 0 или 255). Если же мыша у Вас есть, то вероятность сов-
падения всех трех чисел практически равна нулю (единственный ва-
риант когда она высока - это если Вы включили комп в сеть и еще
ни разу не касались мышки, в этом случае из всех трех портов бу-
дет считываться число 255). Правда возможен такой вариант, что у
Вашей машины нестабильная шина данных и тогда со всех трех пор-
тов считаются разные числа. Для этого случая я проверяю старшие
пять битов порта кнопок #FADF и если они не равны еденице, зна-
чит мыша отсутствует. Также в Питерских программах перед провер-
кой наличия мышки в порты #5F и #7F заносится байт #90. Зачем
это нужно я не знаю, но на всякий случай советую выполнять эту
процедуру. Но в любом случае рекомендуется предусмотреть ручное
отключение/включение kempston mouse.
; Проверка наличия kempston mouse
INIT LD A,#90 ; ???
OUT (#7F),A ; ???
OUT (#5F),A ; ???
LD BC,#FADF
IN A,(C) ; Считываем в A состояние порта #FADF
PUSH AF ; и сохраняем его на стеке
AND #F8 ; Проверка старших пяти битов на 1
CP #F8
CALL NZ,NOTMOUS ; Вызов подпрограммы отключения мышки
INC B
IN L,(C) ; Считываем в L состояние порта #FBDF
LD B,#FF
IN H,(C) ; Считываем в H состояние порта #FFDF
POP AF ; Восстанавливаем регистр A
CP L ; Проверка на совпадение значений
JR NZ,$+6 ; Если байты разные, то на YESMOUSE
CP H ; Если одинаковые, то проверям дальше
CALL Z,NOTMOUS ; Если опять сопали, то на откл. мышки
YESMOUS ... ; Kempston mouse present
Для того чтобы после старта программы курсор появлялся в нуж-
ном месте экрана, а не черт знает где, вызывайте это подпрограм-
му (так же вызывайте ее после ручного включения мышки, чтобы
курсор не прыгал в неизвестном направлении):
; Восстановление координат
MXY LD A,#FB ; Прочитать состоянии X-координаты
IN A,(#DF) ; мышки и
LD (MOUSE_X+1),A ; занести его в метку MOUSE_X
LD A,#FF ; Прочитать состоянии Y-координаты
IN A,(#DF) ; мышки и
LD (MOUSE_Y+1),A ; занести его в метку MOUSE_Y
RET
Эта программа служит для опроса кнопок мыши. Тут думаю все и
так понятно, но комментарии все таки поставлю.
; Проверка состояния кнопок мышки
FIRE LD A,#FA ; Чтение байта состояния кнопок
IN A,(#DF)
RRA ; Проверка нажатия левой кнопки
JP NC,L_BUT ; Если нажата, то прейти на метку L_BUT
RRA ; Проверка нажатия правой кнопки
JP NC,R_BUT ; Если нажата, то прейти на метку R_BUT
... ; Кнопки не были нажаты (третья кнопка не используется)
Ну и программка опроса движения мышки. Я использую эту прог-
рамму так: сначала стираю курсор с экрана, вызываю программу MO-
VE, которая изменяет координаты X и Y, и печатаю курсор (Все
это происходит естественно в прерывании). В своих программах я
увеличиваю смещение мышки в два раза, чтобы курсор перемещался
быстрее. Если Вам это не надо, то уберите команды ADD A,A.
; Движение мышки. X=0-255, Y=0-191
MOVE LD A,#FB
IN A,(#DF) ; Читаем состояние X-координаты мышки
MOUSE_X LD E,0 ; В регистре E предыдущая X-координата
LD (MOUSE_X+1),A ; Сохраняем новую координату
SUB E ; Проверяем изменилась ли X-координата
CALL NZ,MOVE_X ; Если да, то вызываем подпрог. MOVE_X
LD A,#FF
IN A,(#DF) ; Читаем состояние Y-координаты мышки
MOUSE_Y LD E,0 ; В регистре E предыдущая Y-координата
LD (MOUSE_Y+1),A ; Сохраняем новую координату
SUB E ; Проверяем изменилась ли Y-координата
CALL NZ,MOVE_Y ; Если да, то вызываем подпрог. MOVE_Y
...
MOVE_Y JP M,MOUSE_D ; Если флаг S = 1, значит мышку двигали вниз, иначе вверх
MOUSE_U ADD A,A ; Увеличиваем смешение в два раза
LD D,A ; Сохраняем его в рег. D
LD A,(Y_COORD) ; Берем текущую Y-координату курсора
SUB D ; Уменьшаем ее
JR NC,$+3 ; Если результат оказался меньше 0,
XOR A ; то обнулить Y-координату
LD (Y_COORD),A ; Заносим новую Y-координату курсора
RET
MOUSE_D NEG ; Приводим смешение в нормальный вид
ADD A,A ; Увеличиваем смешение в два раза
LD D,A ; Сохраняем его в рег. D
LD A,(Y_COORD) ; Берем текущую Y-координату курсора
ADD A,D ; Увеличиваем ее
JR C,MM_2 ; Если результат оказался больше 255,
CP 192 ; или больше 191, то
JR C,$+4
MM_2 LD A,191 ; меняем его на 191
LD (Y_COORD),A ; Заносим новую Y-координату курсора
RET
MOUSE_L NEG ; Приводим смешение в нормальный вид
ADD A,A ; Увеличиваем смешение в два раза
LD D,A ; Сохраняем его в рег. D
LD A,(X_COORD) ; Берем текущую X-координату курсора
SUB D ; Уменьшаем ее
JR NC,$+3 ; Если результат оказался меньше 0,
XOR A ; то обнулить X-координату
LD (X_COORD),A ; Заносим новую X-координату курсора
RET
MOVE_X JP M,MOUSE_L ; Если флаг S = 1, значит мышку дви гали влево, иначе вправо
MOUSE_R ADD A,A ; Увеличиваем смешение в два раза
LD D,A ; Сохраняем его в рег. D
LD A,(X_COORD) ; Берем текущую X-координату курсора
ADD A,D ; Увеличиваем ее
JR NC,$+4 ; Если результат оказался больше 255,
LD A,255 ; то меняем его на 255
LD (X_COORD),A ; Заносим новую X-координату курсора
RET
; Kempston mouse driver with master/slave support. Written by Patrik Rak in 2013, and revised in 2016.
; Based on a driver originally written for testing ZXDS
updatemouse
ld hl,ports
ld de,mouseinput
ld c,0xDF
.loop ld b,(hl)
inc hl
in a,(c)
ld (de),a
inc de
ld a,(mouseinput+6)%256
cp e
jr nz,.loop
ex de,hl
call .pair
.pair ld c,255
ld a,(de)
call .update
ld c,191
ld a,(de)
cpl
.update inc de
ld b,a
sub (hl)
ld (hl),b
inc hl
jp m,.dec
add a,(hl)
jr c,.clamp
cp c
jr c,.ok
.clamp ld a,c
jr .ok
.dec add a,(hl)
jr c,.ok
xor a
.ok ld (hl),a
inc hl
ret
ports db 0xFB ; mouse 1 X port
db 0xFF ; mouse 1 Y port
db 0x0B ; mouse 2 X port
db 0x0F ; mouse 2 Y port
db 0xFA ; mouse 1 button port
db 0x0A ; mouse 2 button port
mouseinput
mouse1x db 0
mouse1y db 0
mouse2x db 0
mouse2y db 0
mouse1b db 0 ; mouse 1 buttons
mouse2b db 0 ; mouse 2 buttons
old1x db 0
pos1x db 0 ; mouse 1 X
old1y db 0
pos1y db 0 ; mouse 1 Y
old2x db 0
pos2x db 0 ; mouse 2 X
old2y db 0
pos2y db 0 ; mouse 2 Y
;FROM VELESOFT site
KEMPSTON MOUSE - DRIVER (SHORT VERSION 1)
Driver code is optimised for short length.
Before first run mouse driver(or after long time delay) must be called KOREKCE routine for mouse calibration.
call KOREKCE --> k-mouse calibration + set new axis values (for master k-mouse or any other kempston mouse interfaces)
INPUT : L=new X-axis(0-255) / H=new Y-axis(0-191) ;OUTPUT: none
call MOUSE --> call mouse driver (for master k-mouse or any other kempston mouse interfaces)
INPUT : none ;OUTPUT: A=buttons status (*) + modify mouse positions (*)
(*) - MOUSE DRIVER OUTPUT:
master k-mouse or any other kempston mouse interface:
register L or (coord+0) = 1B new x-axis window position(absolute)
register H or (coord+1) = 1B new y-axis window position(absolute)
register A or (contrb) = 1B new mouse buttons
(D0=right button,D1=left button,D2=middle button) - 0=inactive/1=active button
register E or (oldco+0) = 1B previous(old) x-axis window position(absolute)
register D or (oldco+1) = 1B previous(old) y-axis window position(absolute)
register BC is modified, after return from driver will contain value #FADF.
examples for your program:
before first call mouse driver (or after start game) you must set new default X-axis and Y-axis values:
LD L,new_X-axis
LD H,new_Y-axis
CALL KOREKCE
...
after long time delay (pause / start game / exit from menu) must be used this routine:
LD HL,(COORD)
CALL KOREKCE
...
mouse driver can be called each interrupt:
PUSH BC
PUSH DE
CALL MOUSE
POP DE
POP BC
JP NZ,FIRE ;press any mouse button to fire
...
...
from X and Y positions your program must calculate videoram adress for print cursor
;SHORT KEMPSTON MOUSE DRIVER FROM VELE
cpu Z80UNDOC
RELAXED ON
;RUSSIAN KEMPSTON MOUSE DRIVER
XMAX equ 255
XMIN equ 0
YMAX equ 191
YMIN equ 0
;XECUTE MOUSE
;TAKE COORDINATES FROM CURPOS=
;=COORD
MOUSE ld hl,(COORD)
;L=X-axis ;H=Y-axis
ld bc,$FBDF
ld de,(OLDCO)
in a,(c)
ld (OLDCO),a
sub e
jr z,NM_X
jp p,MX_PL
add a,l
jr c,ZER_X
xor a
ZER_X ld l,a
jr NM_X
MX_PL add a,l
jr c,BEX_Z
cp XMAX
jr c,BEX_B
BEX_Z ld a,XMAX
BEX_B ld l,a
NM_X ld b,$FF
in a,(c)
ld (OLDCO+1),a
sub d
jr z,NM_Y
neg
jp p,MY_PL
add a,h
jr c,ZER_Y
xor a
ZER_Y ld h,a
jr NM_Y
MY_PL add a,h
jr c,BEY_Z
cp YMAX
jr c,BEY_B
BEY_Z ld a,YMAX
BEY_B ld h,a
NM_Y ld a,h
cp $FF
jr c,BIGY
ld h,$FF
BIGY cp YMIN
jr nc,SMALY
ld h,YMIN
SMALY ld a,l
cp $FF
jr c,DIRY
ld l,$FF
DIRY cp XMIN
jr nc,DIMENS
ld l,XMIN
DIMENS ld (COORD),hl
ld bc,$FADF
in a,(c)
cpl
and 7
ld (CONTRB),a
ret
;INPUT: L=new X-axis;H=new Y-axis
KOREKCE ld a,$FB
in a,($DF)
ld (OLDCO),a
ld a,$FF
in a,($DF)
ld (OLDCO+1),a
ld (COORD),hl
ret
;new cursor position
COORD defb 0,0
;buttons status
CONTRB defb 0
;working (previous position)
OLDCO defb 0,0
LENGTH equ $-MOUSE
;MOUSE DRIVER WITH FIRE BUTTON AUTOCONFIG (like in zx-format)
;(С) Andrey Rachkin'95
JR MDRV
DIRECTZ DEFB 0 ;FIRE
DEFB 0 ;UP
DEFB 0 ;DOWN
DEFB 0 ;RIGHT
DEFB 0 ;LEFT
DEFB 0 ;CANCEL
MCOORD DEFW 0 ;LAST CURSOR COORDS IN PIXELZ
MPORTS DEFW 0 ;LAST READED MAUSY COORDS
NONDEF AND 3 ;HERE COMEZ BUTTONZ CONTROL IF FIRE BUTON NOT DEFINED
JR Z,MDRV4 ;IF NONE BUTTON PUSHED
CP 1
JR Z,NONDEF_
XOR A
LD (MDRV3+2),A
LD A,5
LD (MDRV2+2),A
NONDEF_ LD HL,0
LD (MDRV1),HL
POP IX
;MAIN PROC OF MOUSEDRIVER
MDRV PUSH IX
LD HL,DIRECTZ
PUSH HL
POP IX
XOR A
LD (HL),A ;CLEARING
INC HL ;OF
LD (HL),A ;DIRECTZ
INC HL ;BUFER
LD (HL),A
INC HL
LD (HL),A
INC HL
LD (HL),A
INC HL
LD (HL),A
INC HL
LD BC,#FADF ;BUTTONZ CONTROL
IN A,(C) ;READING FROM PORT OF BUTTONS
CPL
MDRV1 JR NONDEF ;JR UNTIL FIRE BUTTON NOT DEFINED
RRA
MDRV2 RL (IX+0) ;FIRE
RRA
MDRV3 RL (IX+5) ;CANCEL
;COORDS CONTROL
MDRV4 LD HL,(MCOORD) ;FORM LAST CURSOR COORDS
LD DE,(MPORTS) ;FROM LAST READED MOUSE COORDS
LD BC,#FBDF
IN A,(C) ;READING FROM PORT X-COORD (0-#FF)
LD (MPORTS),A
SUB E
JR Z,MDRV9
JP P,MDRV6
LD (IX+4),1 ;MOVE LEFT
ADD A,L
JR C,MDRV5
XOR A ;MIN X-COORD
MDRV5 LD L,A
JR MDRV9
MDRV6 ADD A,L
LD (IX+3),1 ;MOVE RIGHT
JR C,MDRV7
CP #FE ;MAX X-COORD
JR C,MDRV8
MDRV7 LD A,#FE ;MAX X-COORD
MDRV8 LD L,A
MDRV9 LD B,#FF
IN A,(C) ;READING FROM PORT Y-COORD (0-#FF)
LD (MPORTS+1),A
SUB D
JR Z,MDRV14
NEG
JP P,MDRV11
LD (IX+1),1 ;MOVE UP
ADD A,H
JR C,MDRV10
XOR A ;MIN Y-COORD
MDRV10 LD H,A
JR MDRV14
MDRV11 ADD A,H
LD (IX+2),1 ;MOVE DOWN
JR C,MDRV12
CP #BF ;MAX Y-COORD
JR C,MDRV13
MDRV12 LD A,#BF ;MAX Y-COORD
MDRV13 LD H,A
MDRV14 LD (MCOORD),HL ;NEW CURSOR POSITION IN PIXELZ
POP IX
RET
AMX-Mouse Port Details
Port: Meaning:
#1F(31) Data A
#3F(63) Data B
#DF mouse button
The AMX interface uses a Z80-PIO (programmable In/Out Interface).
This chip is fairly complicated, and can operate in several modes. The
AMX interface only uses mode 1 (no connection with IM 1 by the way), so
this is the only mode that is emulated. The following discussion
applies to mode 1 only, and refers to the I/O addresses as used by the
AMX mouse interface.
The PIO contains two 8-bit interrupt vector registers (VECA and VECB),
two 2-bit mode registers (MODEA and MODEB), two 8-bit data registers
(DATAA and DATAB), and two 1-bit interrupt-enable latches (IEA and
IEB). The data registers can be read at any time through IN ports #1F
and #3F respectively. (Note that #1F is also used by the Kempston
joystick interface, and also by the Disciple interface, and that #3F is
also used by the Multiface.) The other registers cannot be read, only
set, through the write-only control register CTRLA and CTRLB, accessed
via OUT port #5F and #7F respectively:
Bit 7 6 5 4 3 2 1 0
WRITE | interrupt vector | 0 | Set interrupt vector
Bit 7 6 5 4 3 2 1 0
WRITE | IE | x | x | x | 0 | x | x | 1 | Set IE latch
Bit 7 6 5 4 3 2 1 0
WRITE | MODE | x | x | 1 | 1 | 1 | 1 | Set PIO mode
If IEA or IEB are set (1), the corresponding interrupt sequences will
generate interrupts when prompted by the hardware, and put the
corresponding interrupt vector on the data bus at interrupt time. In
Z80 Interrupt mode 2, this will then be used as a vector. Note that
bit 0 of the vector byte is always 0. Z80 only emulates PIO mode 1
behaviour (bit 7=0, bit 6=1 when setting PIO mode), and won't do
anything if the mode is set differently.
The AMX interface generates an A interrupt for each 'mickey' the mouse
produces in the X direction, and a B interrupt for each mickey in the
Y direction. The sign of the direction is stored as a 0 (positive) or
1 (negative) in the respective data registers.
One additional port, IN port #DF, reads out the mouse button status.
It returns #FF or #00.
The AMX is emulated as follows. At every 50 Hz frame, the mouse status
is checked, and a PIO interrupt is emulated if necessary. The IRET
instruction is trapped, and if caught, more PIO interrupts are emulated
if necessary, after actually executing the IRET, with a maximum of 32
interrupts per frame per coordinate. Care is taken to execute the 50
Hz frame interrupt as well. This latter interrupt can be distinguished
from PIO interrupts by the fact that it puts a #FF on the bus. It was
also necessary to take care of cases where the first instruction
executed after a RETI was a HALT, which is then skipped. It seems
that the PIO leaves the Z80 in peace for a few clock cycles after
seeing a RETI, before generating another interrupt.
A reset signal causes the IE latches of the PIO to reset.
Системное...
RAM 48/128/+3/...
128,+2 ;from WOS
The additional memory features of the 128K/+2 are controlled to by writes to port 0x7ffd.
As normal on Sinclair hardware, the port address is in fact only partially decoded
and the hardware will respond to any port address with bits 1 and 15 reset.
However, 0x7ffd should be used if at all possible to avoid conflicts with other hardware.
When memory is being paged, interrupts should be disabled and the stack should be
in an area which is not going to change. If normal interrupt code is to run, then the
system variable at 0x5B5C (23388) must be kept updated with the last value sent
to port 0x7FFD. Reading from 0x7ffd produces no special results: floating bus values
will be returned as would be returned from any other port not attached to any hardware.
The byte output will be interpreted as follows:
Bits 0-2: RAM page (0-7) to map into memory at 0xc000.
Bit 3: Select normal (0) or shadow (1) screen to be displayed. The normal screen is in bank 5,
whilst the shadow screen is in bank 7. Note that this does not affect the
memory between 0x4000 and 0x7fff, which is always bank 5.
Bit 4: ROM select. ROM 0 is the 128k editor and menu system; ROM 1 contains 48K BASIC.
Bit 5: If set, memory paging will be disabled and further output to
this port will be ignored until the computer is reset.
The memory map of these computers is:
0xffff +--------+--------+--------+--------+--------+--------+--------+--------+
| Bank 0 | Bank 1 | Bank 2 | Bank 3 | Bank 4 | Bank 5 | Bank 6 | Bank 7 |
| | |(also at| | |(also at| | |
| | | 0x8000)| | | 0x4000)| | |
| | | | | | screen | | screen |
0xc000 +--------+--------+--------+--------+--------+--------+--------+--------+
| Bank 2 | Any one of these pages may be switched in.
| |
| |
| |
0x8000 +--------+
| Bank 5 |
| |
| |
| screen |
0x4000 +--------+--------+
| ROM 0 | ROM 1 | Either ROM may be switched in.
| | |
| | |
| | |
0x0000 +--------+--------+
RAM banks 1,3,4,6 and most of 7 are used for the silicon disc
; the rest of 7 contains editor scratchpads.
An example of a typical bank switch on the 128 is:
LD A,(0x5b5c) ;Previous value of port
AND 0xf8
OR 4 ;Select bank 4
LD BC,0x7ffd
DI
LD (0x5b5c),A
OUT (C),A
EI
The principle is the same for all bank switching: change only the bits you need to.
Memory banks 1,3,5 and 7 are contended, which reduces the speed of memory access in
these banks as detailed in the Contended Memory section. As on the 48K machine, Port 0xfe
(and all other even ports) are contended. Port 0x7ffd is not contended as of itself,
but the high byte of the port address being 0x7f causes delays.
See the Contended Input/Output section for full details.
The contended memory timings for these machines are similar to that for the 48K machine,
except that the 6,5,4,3,2,1,0,0 pattern starts at 14361 T states after the interrupt
as opposed to 14335, and repeats every 228 T states, as opposed to 224
+3/+2A ;from WOS
The basic principle of paging on the +2A and +3 is the same as
for the 128K/+2. However, the +2A and +3 have four ROMs rather
than two, and certain extra memory configurations.
Port 0x7ffd behaves in the almost exactly the same way as on
the 128K/+2, with two exceptions:
Bit 4 is now the low bit of the ROM selection.
The partial decoding used is now slightly different: the hardware will
respond only to those port addresses with bit 1 reset,
bit 14 set and bit 15 reset (as opposed to just bits 1 and 15
reset on the 128K/+2).
The extra paging features of the +2A/+3 are controlled by port 0x1ffd
(again, partial decoding applies here: the hardware will respond to all port
addresses with bit 1 reset, bit 12 set and bits 13, 14 and 15 reset).
This port is also write-only, and its last value should be
saved at 0x5b67 (23399).
Port 0x1FFD responds as follows:
Bit 0: Paging mode. 0=normal, 1=special
Bit 1: In normal mode, ignored.
Bit 2: In normal mode, high bit of ROM selection. The four ROMs are:
ROM 0: 128k editor, menu system and self-test program
ROM 1: 128k syntax checker
ROM 2: +3DOS
ROM 3: 48 BASIC
Bit 3: Disk motor; 1=on, 0=off
Bit 4: Printer port strobe.
When special mode is selected, the memory map changes to one of four
configurations specified in bits 1 and 2 of port 0x1ffd:
Bit 2 =0 Bit 2 =0 Bit 2 =1 Bit 2 =1
Bit 1 =0 Bit 1 =1 Bit 1 =0 Bit 1 =1
0xffff +--------+ +--------+ +--------+ +--------+
| Bank 3 | | Bank 7 | | Bank 3 | | Bank 3 |
| | | | | | | |
| | | | | | | |
| | | screen | | | | |
0xc000 +--------+ +--------+ +--------+ +--------+
| Bank 2 | | Bank 6 | | Bank 6 | | Bank 6 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
0x8000 +--------+ +--------+ +--------+ +--------+
| Bank 1 | | Bank 5 | | Bank 5 | | Bank 7 |
| | | | | | | |
| | | | | | | |
| | | screen | | screen | | screen |
0x4000 +--------+ +--------+ +--------+ +--------+
| Bank 0 | | Bank 4 | | Bank 4 | | Bank 4 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
0x0000 +--------+ +--------+ +--------+ +--------+
RAM banks 1,3,4 and 6 are used for the disc cache and RAMdisc,
while Bank 7 contains editor scratchpads and +3DOS workspace.
The contended memory timings differ on the +2A/+3 from the earlier machines
; firstly, the timing differences mean that the top-left pixel of the screen
is displayed 14364 T-states after the 50 Hz interrupt occurs, as opposed
to 14336. The T-states (relative to the interrupt) at which delays occur
are given in the following table:
Cycle # Delay
------- -----
14365 1
14366 No delay
14367 7
14368 6
14369 5
14370 4
14371 3
14372 2
14373 1
14374 No delay
14375 7
14376 6
and so on, until cycle 14494, when the display of the first scanline
on the screen has been completed, and no more delays are inserted until
14593 (=14365+228) when the cycle repeats. The other difference occurs
for instructions which have multiple 'pc+1' or 'hl' entries in the breakdown
for the other machines: on the +2A/+3, these entries are combined
into just one. This means that, for example, JR becomes pc:4,pc+1:8.
Unlike the base 128K machine, RAM banks 4, 5, 6 and 7 are contended.
However, Port 0xfe is not; whether ports 0x7ffd and 0x1ffd are
contended is currently unknown.
23388. В этой ячейке, как известно, #00 при 48К и #10, если у вас 128К
(если вы, разумеется, не изменяли это значение переключением страниц
памяти из бейсика или, скажем, размещением там распаковщика).
128 to 48 basic switcher BY unknonw
ORG #5D40
DI
LD SP,(#5C3D) ;;23613
POP HL
LD HL,#1303
PUSH HL
LD HL,#1B76 ;7030
PUSH HL
RES 4,(IY+#01)
LD DE,#15BE
LD HL,(#5CF4) ;23631
LD BC,#000F
ADD HL,BC
EX DE,HL
LD C,#04
LDIR
EI
RET
; switch to 48k basic
;by unknown
ld sp, (#5C3D) ; ERR_SP ;23613
ld hl, #1303 ; MAIN_4
ex (sp), hl
res 4, (iy+1)
ld bc, #7FFD
ld a, _7FFD_ROM48K+_7FFD_MAINSCR+0
out (c), a
ei
jp #1B76 ; STMT_RET
;Процедура Robusa 128 to 48 basic
DI
LD HL,10072
LD IY,23610
EXX
LD A,63
LD I,A
IM 1
LD SP,(23613) ;5c3d
LD A,16
OUT (253),A
RES 4,(IY+1)
CALL 81
CALL 7030
JP 4867
Процедура Wlodek Blacka 128 to 48 basic
CALL usr0
usr0 DI
LD SP,(23613) ;5c3d
POP HL
LD HL,4867
PUSH HL
LD HL,7030 ;1b76=stmt-ret
PUSH HL
RES 4,(IY+1)
LD DE,5566 ;15be
LD HL,(23631) ;5c4f
LD BC,15
ADD HL,BC
EX DE,HL
LD C,4
LDIR
EI
RET
Как включить 48-ой Basic
со 128-ой памятью?
Это по правде делается очень просто:
;by Basil
DI
LD IY,23610 ;5c3a
LD HL,4867 ;1303 ;ПОДКЛЮЧЕНИЕ
PUSH HL ;48-ГО
LD (23613),SP ;5c3d
LD HL,(23631) ;5c4f
LD DE,15 ;f
ADD HL,DE
LD DE,5566 ;15be ;BASIC'А
EX DE,HL
LD BC,4
LDIR ;СО 128-ОЙ
RES 4,(IY+1)
LD HL,7030 ;1b76=stmt-ret
PUSH HL ;ПАМЯТЬЮ
IM 1
EI
LD HL,10072
EXX
RET
Желательно выполнить корректную установку им1
;процка из моего ленточного лоадера
ADRCODES EQU 23840
LD HL,MCBEG-$ ; starting from BASIC
LD SP,STACK ;
DI ;
RES 4,(IY+1) ;
ADD HL,BC ;
PUSH HL ;
;
LD HL,(23613) ; SET
LD DE,#1303 ; 48K
LD (HL),E ; BASIC
INC HL ; IF
LD (HL),D ; 128K
LD A,#10 ; TAPE LOADER
LD BC,#7FFD ;
OUT (C),A ;
;
POP HL ;
LD DE,ADRCODES ;
LD BC,MCEND-MCBEG ;
PUSH DE ;
JP #33C3 ; GO TO MAIN CODES (MCBEG)
MCBEG DISP ADRCODES
---yar kodes here
ENT
MCEND
Определения наличия медленой памяти
(SLOW RAM) т.е. тип машины - однополевая
или двухполевая.
LD A,3 ; счетчик холостых прерыв.
LD HL,INT_1 ; адрес возврата
LD (#FDFE),HL
LD BC,0 ; обнуляем счетчик
EI
HALT
HALT
LOOP_1 LD HL,(#4000) ; работа с SLOW RAM
LD (#4000),HL
INC BC
JR LOOP
INT_1 DEC A
EI
RET NZ ; возврат из холостого INT
DI
POP HL ; востанавливаем стек
PUSH BC ; сохраняем результат
LD HL,INT_2 ; адрес возврата
LD (#FDFE),HL
LD A,3 ; счетчик холостых прерыв.
LD BC,0 ; обнуляем счетчик
EI
HALT
HALT
LOOP_2 LD HL,(#FE00) ; работа с FAST RAM
LD (#FE00),HL
INC BC
JR LOOP_2
INT_2 DEC A
EI
RET NZ ; возврат из холостого INT
DI
POP HL ; востанавливаем стек
POP HL ; восстанав. старый резул.
XOR A
SBC HL,BC ; сверяем оба результата
LD HL,ABSENT ; SLOW RAM ABSENT
JR Z,$+5
LD HL,PRESENT ; SLOW RAM PRESENT
CALL PRINT
; check 128k-48k (c) lvd^mhm
; must reside below #c000
; on exit will remain page #10 and scr 5 turned on if was 128k mode
;OUT: A=#00 - 128k, A=#FF - 48k
;KILL: AF,BC,DE,HL
CHK48128
ld hl,#c011
ld bc,#7ffd
ld a,#10
out (c),a
ld e,(hl)
ld (hl),a
out (c),l
ld d,(hl)
ld (hl),l
out (c),a
sub (hl) ;0 - 128k, #FF - 48k
out (c),l
ld (hl),d ;restore pages
ld d,#10
out (c),d
ld (hl),e
ret
;Быстрая и короткая проверка на наличие памяти 128K.
;(C) GRAND, 8.8.2006 15:34.
LD A,#10
LD HL,#C011
LD BC,#7FFD
OUT (C),A
LD (HL),B
OUT (C),L
LD (HL),A
OUT (C),A
CP (HL)
LD (23388),A;BANKM
;на выходе флаг Z=0 - означает наличие 128K
;Проверка наличия памяти 128K и очистка
;электронного диска BASIC 128.
;(C) GRAND, 12.11.2006.
LD BC,#7FFD
LD DE,#1017
OUT (C),D
LD HL,#EBEC
LD (HL),D
OUT (C),E
LD E,19
LD (HL),0
INC HL
DEC E
JR NZ,$-4
LD L,#F7
LD (HL),#C0
OUT (C),D
LD L,#EC
LD A,(HL)
OR A
LD (23388),A;BANKM
LD (23427),HL;SFNEXT
LD H,#2B
LD A,1
LD (23429),HL;SFSPACE
LD (23431),A;
;на выходе флаг Z=0 - означает наличие 128K.
;При работе процедуры прерывания должны быть
;запрещены, а ее код НЕ должен распологаться
;в диапазоне, где устанавливаются банки
;памяти, т.е. #C000...#FFFF. Процедура оперирует
:только с системными переменными BASIC 128,
:расположенными как в бывшем буфере принтера,
;так и в банке 7, поэтому никакое изменение
;ячеек, установленных после ее работы, недопустимо!
ORG #6000
; Активизируем ПЗУ-48, экран |1
; включаем страницу 0
LD BC,#7ffd
LD A,#10
OUT (C),A
; и читаем первый байт
LD HL,#c000
LD A,(HL)
; изменяем его и кладём на
; то же самое место, но в странице 1
CPL
LD E,A
LD A,#11
OUT (C),A
LD (HL),E
; восстанавливаем страницу 0
LD A,#10
OUT (C),A
; её первый байт сравниваем
; с изменённым ранее
LD A,(HL)
CP E
; анализируем результат
JR NZ,mode128
; mode 48
...
...
mode128
;48/128 test
MEMORY ld bc,32765
ld hl,49152
ld de,4113 ;#1011
out (c),d
ld (hl),l
out (c),e
ld (hl),c
out (c),d
ld a,(hl)
or a
jr z,128kb
;48/128 test
LD HL,#C000
LD BC,#7FFD
LD A,#10
OUT (C),A
LD (HL),A
INC A
OUT (C),A
LD (HL),A
DEC A
OUT (C),A
CP (HL)
LD HL,KB_128
JR Z,$+4
LD HL,KB_48
CALL PRINT
;48/128 test
ORG <#8000 ; адрес должен быть
LD BC,#7FFD ; меньше #8000
LD A,#15
OUT (C),A
LD (LABEL+#8000),A
LABEL RST 0 ; при 48Кб - сброс
LD A,#10 ; при 128Кб продол-
OUT (C),A ; жаем работу
ROM 1982 PROCEDURES & CALCULATOR
Подпрограммы BASIC 48
#03B5 949 Подпрограмма BEEP
HL=частота DE=длительность
#03F8 1016 Подпрограмма BEEP
L=частора E=длительность
#04C2 1218 Подпрограмма SAVE
запись на ленту без заголовка
#04C6 1222 Подпрограмма SAVE
тоже,с блокировкой BREAK
#0556 1366 Подпрограмма LOAD
чтение с ленты без заголовка
IX=адрес DE=длина
A=#00-LOAD A=#FF-VERIFY
#0C0A 3082 Печать сообщений
DE=адрес сообщения
A=номер по порядку
#0D4D 3405 Восстановление постоянных
атрибутов экрана
#0D6B 3435 Подпрограмма CLS
Очистка экрана
#0D6E 3438 Очистка служебного
экрана
#0DAF 3503 Подпрограмма CLS
Очистка экрана
#0DFE 3582 Подпрограмма SCROLL
Скролл служебного экрана
на 1 знакоместо
#0E44 3652 Очистка служебного экрана
B=(x-1) число строк
#0E9E 3742 Адрес строки знакогене-
ратора HL=адрес
A=номер строки
#15DE 5598 Подпрограмма INKEY$
A=символ CY=1-установлен
#1601 5633 Определение потока
A=номер потока
#1728 5928 Очистка стека
Очищает установленный стек
#1835 6197 Подпрогамма LIST
HL=адрес бейсик строки
#1A1B 6683 Печать числа на экран
BC=(0...#270F)-число (0.9999)
печатает в 10-тиричном исчислении
#1CAD 7341 Перевод временных
атрибутов в постоянные
#1F3D 7997 Подпрограмма PAUSE
BC=время паузы как в бейсике
#1F54 8020 Подпрограмма BREAK
CY=1-установлен при нажатии
#203C 8252 Подпрограмма PRINT
DE=адрес BC=длина
Задаются также PAPER,INK,BORDER
AT,TAB,OVER,BRIGHT,FLASH и
другие управляющие символы
DB 22,0,0,16,1,17,2,"O.К."-пример
#229B 8859 Подпрограмма BORDER
A=цвет бордюра как и в бейсике
#22B0 8880 Вычисление адреса экрана
A=позиция по X
C=позиция по Y
Выходные данные
HL=адрес экрана
A=смещение (бит)
#22E5 8933 Подпрограмма PLOT
B=позиция по X
C=позиция по Y
#2314 8980 Вывод числа из стека
A=целое число
#232D 9005 Подпрограмма CIRCLE
Стек=радиус <-вершина стека
Стек=позиция по X
Стек=позиция по Y
#2394 9108 Подпрограмма LINE
Стек=длина <-вершина стека
Стек=позиция по X
Стек=позиция по Y
#23BA 9146 Подпрограмма LINE
B=позиция по X
C=позиция по Y
DE=смещение (#00=1 или #FF=-1)
#24B7 9399 Подпрограмма LINE
Стек=длина <-вершина стека
Стек=позиция по X
Стек=позиция по Y
#24BA 9402 Подпрограмма LINE
B=позиция по X
C=позиция по Y
DE=смещение (#00=1 или #FF=-1)
#2AB6 10934 Ввод числа на стек
A,E,D,C,B=>стек
#2BF1 11249 Вывод числа из стека
A,E,D,C,B=>стек
#2CB8 11448 Ввод числа на стек из строки
PRINT "......"=бейсик строка
A=длина строки
#2D28 11560 Ввод числа на стек
A=число
#2D2B 11563 Ввод числа на стек
BC=число
#2D2E 11566 Ввод числа на стек
BC=число
#2DA2 11682 Вывод числа из стека
BC=целое число
#2DE3 11747 Печать числа из стека
На стеке должно быть число,после
печати числа,оно исчезает со стека
#30A9 12457 Умножение двух чисел
DE*HL=HL
#33A9 13225 Проверка на свободную память
в стеке
RST #08
DB (0...N)-обработка ошибок
RST #10
A=символ-печать символа и
кодов управления
RST #18
CH_ADD=(#5C5D)-адрес символа
для печати
RST #20
CH_ADD=(#5C5D)-адрес строки
для вывода на экран
RST #28
DB 5,3,15....-выполняемые действия
DB 56-конец работы калькулятора
Работает с числами находящимися
в данный момент на стеке
RST #30
BC=длина-освобождает место в
рабочей области
RST #38-опрос клавиатуры
Тайники ZX Spectrum
Системные процедуры - для программирующих на ассемблере
Z80 ПЗУ (ROM) ZX Spectrum является ценным хранилищем готовых и
отлаженных процедур в машинном коде.
8. Системные процедуры
Для программирующих на ассемблере Z80 ПЗУ (ROM) ZX Spectrum
является ценным хранилищем готовых и отлаженных процедур в машинном
коде. Некоторые из них могут с успехом использоваться с уровня
BASIC, создавая дополнительные возможности, недоступные другим
способом. Мы ограничимся описанием важнейших системных процедур.
8.1 Работа со звуком
В ZX Spectrum динамик можно возбудить двумя способами:
BEEPER #0385 (949)
- Эта процедура требует двух параметров. В регист-
ре DE помещается время продолжительности звука,
а в регистре HL - частота. Эти значения перед
помещением их в регистры требуют предварительных
преобразований. Допустим, мы хотим получить тон
с частотой f продолжительностью t. Тогда в DE
загружается f*t, а в HL загружается
437500/f-30.125. Избежать этих расчетов можно,
если вызвать другую процедуру.
BEEP #03F8 (1016)
- Она требует задания времени и частоты звука в
нормальных единицах измерения. Эти параметры
передаются BEEP путем их помещения в стек
калькулятора. BEEP снимает их оттуда
самостоятельно, выполняет необходимые
преобразования и вызывает BEEPER.
BEEPER немного лучше инструкции ZX-BASIC BEEP с той точки
зрения, что на него не распространяются ограничения на значения
параметров. Данные контролируются во время преобразований,
выполняемых BEEP. Во время генерации звука все прерывания
запрещены. Последней инструкцией BEEPER перед выполнением RET
является EL.
8.2 Работа с магнитофоном
Как заголовки, так и блоки данных записываются на кассету одной
и той же процедурой:
SAVE_BYTES #04C2 (1218)
- В регистрах DE должна находиться длина записыва-
емого блока, в IX - адрес первого байта, а в
буфере A - тип записываемого блока:
тип 0 - заголовок;
тип 255 (#FF) - блок данных.
В принципе, они отличаются тем, что заголовку
предшествует более длительный вступительный
сигнал (около 5 сек.), чем блоку данных (около 2
сек.). Пользователь может использовать и
остальные значения типа между 0 и 255 на
обозначение типа блока. Это полезно при
обозначении разных типов данных в специальных
приложениях. Такие блоки игнорируются
инструкцией LOAD при чтении (на экран не
выводятся о них никакие данные) и некоторыми
программами копирования.
Стандартный заголовок состоит всегда из 17 байт:
1 байт - тип блока данных:
0 - программа BASIC
1 - числовой массив
2 - знаковый массив
3 - набор байтов.
2-11 байты - Название длиной 10 байт. Допустимы
все коды от 0 до 255. Если название
короче 10 байт, то оно дополняется
пробелами.
12-13 байты - Длина блока, следующего за заголов-
ком.
14-17 байты - Зависит от типа набора:
Для типа 0:
14-15 байты - номер строки запуска,
отказ от запуска
сигнализируется
значением > 32767
(#7FFF).
16-17 байты - длина самой программы
без области перемен-
ных.
Для типов 1 и 2 используется только
15 байт для задания (однолитерного)
имени записываемого массива (имя,
под которым он хранился в области
переменных.
Для типа 3:
14-15 байты - начальный адрес памя-
ти, в которой
находился массив в
момент записи.
Приведенный выше формат заголовка должен сохраняться лишь для
тех случаев, когда записываемые блоки должны быть считаны
инструкцией LOAD.
Считывание записанных наборов производится инструкцией:
LOAD_BYTES #0556 (1566)
- Так же, как и для SAVE_BYTES, перед вызовом в
пару DE заносим длину считываемого блока, в IX -
начальный адрес памяти для записи блока, а в A -
тип считываемого набора. Дополнительно еще
необходимо установить указатель с инструкцией
SCF. Вызов этой процедуры с нулевым указателем C
позволяет отказаться от верификации данного
блока (аналог команды VERIFY). Возможная ошибка
считывания или верификации сигнализируется
обнулением указателя C после выхода из
подпрограммы, нормальное выполнение процедуры
сигнализируется установкой C=1.
8.3 Вывод на экран и печать.
Вывод на экран единичного символа, код которого находится в
буфере, на верхнюю и нижнюю части экрана, а также на принтер,
выполняется одной и той же процедурой. Перед ее вызовом необходимо
открыть соответствующий канал с помощью процедуры:
CHAN_OPEN #1601 (5633)
В буфере должен находиться признак необходимого канала: 1 -
для "K", 2 - для "S", 3 - для "P". Этот канал будет открыт до тех
пор, пока мы его сами не закроем. Если после этого выдать команду
ассемблера:
RST #10
то она будет печатать единичный символ, находящийся в буфере A, по
выбранному каналу.
Программа, приведенная ниже, иллюстрирует применение RST #10.
Ее выполнение равнозначно команде: PRINT FLASH 1; AT 5,3;"X";#3;"A"
LD A,2 ;открыть канал "S"
CALL CHAN_OPEN
LD A,#12 ;код символа FLASH
RST #10
LD A,1 ;аргумент FLASH
RST #10
LD A,#16 ;код символа AT
RST #10
LD A,#5 ;аргументы AT
RST #10
LD A,#3
RST #10
LD A,#58 ;код "X"
RST #10
LD A,3 ;открыть канал "P"
CALL CHAN_OPEN
LD A,#41 ;код символа "A";
RST #10
RET ;вывод из подпрограммы
Обратим внимание, что знак "A" будет послан в буфер принтера,
а фактически будет отпечатан на бумаге только после заполнения
буфера, пересылки в буфер символа конца строки (13) или вызова:
COPY_BUFF #0EC0 (3789)
Этот пример показывает, что пересылка цепочки знаков по одному
символу может быть крайне утомительна. Проще применить процедуру:
PR_STRING #203C (8252)
- Она печатает цепочку знаков, адрес которой задан
в регистре DE и длиной в BC. Перед ее вызовом
необходимо открыть соответствующий канал. Если
печатаемые символы не уточняют цвета, то они
устанавливаются на основании переменных ATTR_T,
MASK_T, а также нечетных битов P_FLAG.
Печать чисел более затруднительна, так как необходимо
осуществлять перевод двоичного числа в последовательность символов
его десятичного представления. Все необходимые вычисления и печать
выполняет процедура:
PRINT_FP #2DE3 (11747)
- Она снимает со стека калькулятора пять байтов,
считая их числами в формате, принятом в системе
ZX-BASIC, затем печатает их с учетом системных
переменных, определяющих цвет, положение и т.д.
Способы размещения чисел на стеке калькулятора
мы оговорим далее.
В случае натуральных чисел от 0 до 9999 можно использовать
более быструю процедуру:
OUT_NUM1 #1A1B (6683)
- Она печатает число, содержащееся в BC, на 4-х
полях, выводя вначале необходимое число
пробелов. ZX Spectrum использует эту процедуру
для печати номеров строк в листингах программ.
8.4 Экранная графика
Для рисования на экране у нас есть аналоги процедур PLOT, DRAW
и CIRCLE:
PLOT_SUB #22E5 (8933)
- Позволяет вывести на экран единичную точку с
координатами (X,Y). Перед вызовом необходимо
поместить X в C и Y в B.
PIXEL_ADD #22AA (8874)
- После занесения в BC значений (Y,X) и вызова
этой функции мы получаем в регистре HL адрес
байта, описывающего данную точку экрана. К тому
же в буфер заносится значение X MOD 8,
уточняющее, о каком бите данного байта идет
речь.
DRAW_1 #2477 (9335)
- Снимает со стека калькулятора числа X и Y, после
чего рисует соответствующий отрезок. Координаты
PLOT выбираются из системных переменных.
DRAW_3 #24BA (9402)
- Для рисования аналогичного отрезка этой процеду-
рой необходимо задать ABS Y -> B, ABS X-> C, SGN
Y -> D, SGN X -> E.
DRAW_ARC #2394(9108)
- Рисует фрагменты дуг. Параметры X,Y,Z для этой
процедуры должны передаваться через стек
калькулятора. Z размещается наверху стека.
CIRCLE_1 #2320 (9005)
- Рисует полную окружность с центром в (X,Y) и
радиусом Z, все параметры также должны быть
помещены в стек калькулятора.
Внимание!!! Приведенные выше процедуры модифицируют регистры HL
(см.раздел "Ошибки системы").
После вывода нужного рисунка на экран его можно вывести на
принтер. Аналогом инструкции COPY является процедура:
COPY #0EAC (3756)
- Эта процедура копирует на принтер 22 верхние
строки.
Параметры всех вышеприведенных процедур подчинены тем же
ограничениям, что и соответствующие команды в BASIC.
8.5 Очистка и перемещение экрана
CLS #0D6B (3435)
- Основная процедура очистки экрана. Ее действие
аналогично действию директивы с тем же именем.
CLS_LOWER #0D6E (3438)
- Очистка нижней части экрана. Она действует на
всей нижней части экрана независимо от ее
текущих размеров и одновременно устанавливает ее
высоту в две строки. Инициализируются также
системные переменные DF_CL и SPOSNL,
определяющие положение курсора.
CL_LINE #0E44 (3652)
- Очистка, начиная с нижнего края экрана, заданно-
го числа строк. Число строк содержится в
регистре B.
Перед вызовом этих процедур нужно убедиться, что нужные каналы
открыты. Две первые из них оставляют после выхода канал "K"
открытым. При стирании экрана цвета устанавливаются на основании
системных переменных BORDER для нижней части экрана, а также ATTR_P
и MASK_P для верхней, после чего их содержимое копируется в ATTR_T
и MASK_T.
CL_SC_ALL #0DFE (3582)
- Перемещает содержимое всего экрана (24 полные
строки) на одну строку вверх (верхняя строка
исчезает). Ее можно вызвать непосредственно из
BASIC, так как она не требует параметров.
CL_SCROLL #0E00 (3584)
- После занесения в регистр B числа строк-1 для
сдвига (не менее 2) эта процедура продвинет на
одну строку столько строк, сколько хотим, не
трогая расположенных выше. Следовательно, можно
иметь вверху экрана рисунок или текст, которые
не уничтожаются при сдвиге на одну строку.
Применяя эти процедуры надо помнить, что вверх будут
подниматься также обязательные атрибуты в нижней части экрана.
8.6 Считывание с клавиатуры
Информацию с клавиатуры выгоднее всего снимать с системной
переменной LAST_K. Проверяя состояние пятого бита переменной FLAGS
можно определить: нажата или не нажата очередная клавиша (1-нажата
новая, 0-еще не нажата ни одна с момента обнуления этого бита).
После считывания кода клавиши, обнуляя этот бит, мы обеспечиваем
себе возможность знать, модифицировалось ли еще содержимое LAST_K
или нет. Действует это только при включенных замаскированных
прерываниях в режиме 1. Именно в этом случае ZX Spectrum
автоматически вызывает процедуру 50 раз в секунду:
KEYBOARD #02BF (703)
- Она просматривает всю клавиатуру, идентифицируя
верную комбинацию клавиш, заносит считанный код
в буфер, а также в переменную LAST_K и
устанавливает 5 бит FLAGS. Интерпретация нажатой
клавиши будет зависеть от трех системных
переменных. Сначала проверяется содержимое MODE,
рассматриваемое как число со знаком в коде
дополнения до двух:
MODE-1 < 0 обозначает курсор K, L или C
MODE-1 = 0 обозначает курсор E
MODE-1 > 0 обозначает курсор G
Курсор K от L и C отличается с помощью третьего
бита переменной FLAGS. 0 означает K, а 1
сигнализирует L или C. Третий бит FLAGS
окончательно определяет, является ли курсор L(0)
или C(1). Эта процедура не ожидает нажатия
клавиши.
Чтобы независимо от курсора установить, была ли нажата
конкретная клавиша, более продуктивным может быть непосредственный
опрос клавиатуры с помощью инструкции IN. Здесь мы поступаем так
же, как и в случае функции с тем же самым названием в BASIC.
8.7 Калькулятор
Различные действия над числами с плавающей запятой выполняются
с помощью большого набора процедур, занимающих пространство ROM от
#2F9B (12187) до #386D (14445). Калькулятор вызывается инструкцией
RST #28, которая выполняет переход на адрес #335B (13457).
Основой работы калькулятора являются 66 различных процедур,
выполняющих набор базовых операций на стеке калькулятора.
Очередность их выполнения задается цепочкой байт, размещенной
непосредственно за командой RST #28. Конец такой цепочки всегда
помечается байтом со значением #38 (56).
По необходимости мы ограничимся операциями, позволяющими
выполнять различные численные расчеты. Допустим, что наверху стека
находятся числа ...Z,X,Y.
----------------------------------------------------------------
|Значение байта| | Состояние стека |
|--------------| Операция | после |
| Дес. | Шест. | | операции |
|--------------------------------------------------------------|
| 1 | #01 | Замена элементов | ... Z Y X |
| 3 | #03 | Вычитание | ... Z X-Y |
| 4 | #04 | Умножение | ... Z X*Y |
| 5 | #05 | Деление | ... Z X/Y |
| 6 | #06 | Степень | ... Z X**Y |
| 15 | #0F | Сложение | ... Z X+Y |
| 27 | #18 | Изменение знака | ... Z X -Y |
| 31 | #1F | Синус | ... Z X sin Y |
| 32 | #20 | Косинус | ... Z X cos Y |
| 33 | #21 | Тангенс | ... Z X tg Y |
| 34 | #22 | Арксинус | ... Z X asn Y |
| 35 | #23 | Арккосинус | ... Z X acs Y |
| 36 | #24 | Арктангенс | ... Z X atg Y |
| 37 | #25 | Логарифм натур. | ... Z X ln Y |
| 38 | #26 | Экспонента | ... Z X exp Y |
| 39 | #27 | Целая часть числа| ... Z X int Y |
| 40 | #28 | Корень квадратный| ... Z X sqr Y |
| 41 | #29 | Знак числа | ... Z X sgn Y |
| 42 | #2A | Абсолютная вел. | ... Z X abs Y |
| 49 | #31 | Копирование стека| ... Z X Y Y |
| 50 | #32 | N MOD M | ... Z остаток частное |
| 52 | #34 | Дописать в стек | ... Z X Y D |
| 56 | #38 | Конец расчетов | ... Z X Y |
| 58 | #3A | INT(Y+.5) | ... Z X INT(Y+.5) |
| 160 | #A0 | Дозапись 0 | ... Z X Y 0 |
| 161 | #A1 | Дозапись 1 | ... Z X Y 1 |
| 162 | #A2 | Дозапись 0.5 | ... Z X Y 0.5 |
| 163 | #A3 | Дозапись PI/2 | ... Z X Y PI/2 |
| 164 | #A4 | Дозапись 10 | ... Z X Y 10 |
----------------------------------------------------------------
Как пример использования калькулятора, рассчитаем значение
1.5*SIN(X)+X**2*COS(X*PI/2). Допустим, что значение X находится
наверху стека. Размещение за инструкцией #28 следующих байт первой
колонки вызовет:
---------------------------------------------------------
| Байт | Состояние стека |
|-------------------------------------------------------|
| #31 | X X |
| #31 | X X X |
| #31 | X X X X |
| #A3 | X X X X PI/2 |
| #04 | X X X X*PI/2 |
| #20 | X X X COS(X*PI/2) |
| #04 | X X X*COS(X*PI/2) |
| #04 | X X*X*COS(X*PI/2) |
| #01 | X*X*COS(X*PI/2) X |
| #1F | X*X*COS(X*PI/2) SIN(X) |
| #34 | Дозапись 1.3 (5 байт) |
| #F1 | |
---------------------------------------------------------
---------------------------------------------------------
| #26 | |
| #66 | |
| #66 | |
| #66 | X*X*COS(X*PI/2) SIN(X) 1.3 |
| #04 | X*X*COS(X*PI/2) 1.3*SIN(X) |
| #0F | X*X*COS(X*PI/2)+1.3*SIN(X) |
| #38 | Конец вычислений |
---------------------------------------------------------
Объяснения требует запись в стек калькулятора данных как
последовательности байт, следующих непосредственно за #34. Байты
#F1#26#66#66#66 представляют число 2*113*0.13, а не 0.13. Это
связано с интерпретацией их как числа в укороченной записи. Схема
действий следующая:
- первый байт делим на #40 (64) и как значение показателя
степени принимается: остаток от этого деления плюс #50
(если он отличен от 0) или следующий байт плюс #50 (если
остаток равен 0);
- целая часть от деления (0,1,2,3) плюс 1 определяет,
сколько затребовано байт для мантиссы. Недостающие до 5
байты заполняются нулями.
В нашем примере #F1 (241), деленное на #40 (64) дает остаток
#31 (49), а также целую часть 3. Это означает, что показателем
степени нашего числа является #31+#50=#81 и что затребовано все 4
байта мантиссы. В этой системе число 0 имеет представление
#40#B0#00, так как #0, деленный на #40 дает остаток и целую часть,
равные 0. Затем становится второй байт +#50 или #B0+#50=#00 (все
эти расчеты на отдельных байтах ведутся по модулю 256) и задается
лишь первый байт мантиссы. Остальные байты (до 5) дополняются
нулями. В свою очередь, число 10 имеет представление #40#B0#00#0A.
Символ #34 позволяет размещать числа в тексте программ на
ассемблере. Однако, часто требуется поместить в стек параметры
инструкций или значения, рассчитанные в программе. Для этого
предназначено много вспомогательных процедур. Они позволяют как
извлечение из стека значения, так и запись в него различных чисел:
STK_TO_BC #2307 (8967)
- Два следующих за собой числа (в 5-байтовом пред-
ставлении) извлекаются из стека и заносятся в
регистры B и C. Их значения должны лежать в
диапазоне от -255 до 255. Иначе осуществляется
возврат в BASIC с сообщением 8 (будет выполнена
инструкция RST #8). Эта процедура может
применяться к отрицательным числам. Их знаки
возвращаются в регистрах D и E. Числа,
извлекаемые из стека, округляются до ближайшего
целого числа.
STK_TO_A #2314 (8980)
- Процедура, аналогичная предыдущей, с тем отли-
чием, что из стека извлекается только одно число
и после округления до целого помещается в буфер.
Знак числа возвращается в регистре C.
STACK_FETCH #2BF1 (11249)
- Эта процедура извлекает из стека все число (5
байтов) и размещает его в регистрах A, E, D, C,
B.
FP_TO_BC #20A2 (11682)
- Число из стека округляется до ближайшего целого
числа и размещается в регистрах BC. Знак числа
определяется указателем Z (0 для отрицательных).
Если значение в стеке превысило максимальное
65535, то указатель C устанавливается в 1 и это
единственная реакция на ошибку (возврата в
ZX-BASIC не происходит).
SIK_STORE #2AB6 (10934)
- Эта процедура размещает наверху стека 5 байтов
из регистров A, E, D, C, B. Число 1.3
(#81#26#66#66#66) можно занести в стек
калькулятора двумя способами (обратим
внимание,что второй способ позволяет сэкономить
3 байта):
LD A,#81 RST #28
LD DE,#6626 или DEFB #34
LD BC,#6666 DEFB #F1
CALL #2AB6 DEFB #26
DEFB #66
DEFB #66
DEFB #66
DEFB #38
STACK_A #2D28 (11560)
- Значение регистра A периодически заносится в
стек в 5-байтовом представлении.
STACK_RC #2D2B (11563)
- Эта процедура аналогична предыдущей, но в стек
заносится число из регистров BC.
После завершения расчетов калькулятор в регистрах HL размещает
адрес первого из 5-ти байтов, находящихся на верхушке стека. При
работе с калькулятором необходимо заботиться о соответствующей
работе со стеком. Следует также помнить, что процедура PRINT_FP
снимает со стека печатаемое число. В конце приводим вызов из
программы в машинном коде генератора псевдослучайных чисел:
LD A,#A5
CALL STACK_A
RST #28
DEFB #2F
DEFB #1D
DEFB #38
RET
Эта программа помещает очередное псевдослучайное число в
вершину стека и модифицирует системную переменную SEED.
8.8 Дополнительные системные процедуры
SET_MIN #16B0 (5808)
- Производит глобальную очистку рабочих областей
системы BASIC, а также стека калькулятора. Она
модифицирует системные переменные, указывающие
на соответствующие области памяти. Физически
затираются только те байты, в которые заносятся
указатели конца области.
MAKE_ROOM #1655 (5717)
- Вызывая ее, следует в регистрах HL дать адрес
байта начала добавочного блока, а в BC - размер
блока. Эта процедура сама устанавливает, какие
системные переменные должны быть модифицируемы и
в случае необходимости модифицирует их.
Следовательно, она допускает вставку в
существующую программу на BASIC или в область
переменных новых операторов, переменных и т.д.
RECLAIM_2 #19E8 (8168)
- Обратная предыдущей функция. Здесь в HL заносит-
ся адрес первого байта, который необходимо
убрать, а в BC - размер стираемого блока.
Также, как и прежде, соответствующие системные
переменные будут автоматически модифицированы.
CLEAR_BUFF #0EEF (8815)
- Процедура очищает буфер принтера и модифицирует
связанные с ним системные переменные.
LINE_ADDR #196E (6510)
- Отыскание адреса строки с заданным номером в
программе на BASIC. Перед вызовом в HL заносится
номер разыскиваемой строки. На выходе в HL имеем
адрес этой строки или первой с большим номером.
Если строка с заданным номером есть, то это
сигнализируется установкой указателя Z.
FREE_MEM #1F1A (7962)
- Определение свободной памяти в области BASIC,
т.е. между STKEND и RAMTOP. Регистры HL и BC
содержат то же самое отрицательное число,
представленное в коде дополнения ло 2,
являющееся разницей между STKEND+80 и SP
(указатель стека процессора Z80).
BREAK_KEY #1F54 (8020)
- Ее вызывают для проверки одновременного нажатия
клавиш CS и BREAK. Если они нажаты, то указатель
C обнулен.
Spectrum ROM Memory map
Address Description
0000
THE 'START'
0008
THE 'ERROR' RESTART
0010
THE 'PRINT A CHARACTER' RESTART
0013
Unused
0018
THE 'COLLECT CHARACTER' RESTART
0020
THE 'COLLECT NEXT CHARACTER' RESTART
0025
Unused
0028
THE 'CALCULATOR' RESTART
002B
Unused
0030
THE 'MAKE BC SPACES' RESTART
0038
THE 'MASKABLE INTERRUPT' ROUTINE
0053
THE 'ERROR-2' ROUTINE
005F
Unused
0066
THE 'NON-MASKABLE INTERRUPT' ROUTINE
0074
THE 'CH-ADD+1' SUBROUTINE
007D
THE 'SKIP-OVER' SUBROUTINE
0095
THE TOKEN TABLE
0205
THE KEY TABLES
028E
THE 'KEYBOARD SCANNING' SUBROUTINE
02BF
THE 'KEYBOARD' SUBROUTINE
031E
THE 'K-TEST' SUBROUTINE
0333
THE 'KEYBOARD DECODING' SUBROUTINE
03B5
THE 'BEEPER' SUBROUTINE
03F8
THE 'BEEP' COMMAND ROUTINE
046E
THE 'SEMI-TONE' TABLE
04AA
THE 'PROGRAM NAME' SUBROUTINE (ZX81)
04C2
THE 'SA-BYTES' SUBROUTINE
053F
THE 'SA/LD-RET' SUBROUTINE
0556
THE 'LD-BYTES' SUBROUTINE
05E3
THE 'LD-EDGE-2' AND 'LD-EDGE-1' SUBROUTINES
0605
THE 'SAVE, LOAD, VERIFY and MERGE' COMMAND ROUTINES
07CB
THE 'VERIFY' CONTROL ROUTINE
0802
THE 'LOAD A DATA BLOCK' SUBROUTINE
0808
THE 'LOAD' CONTROL ROUTINE
08B6
THE 'MERGE' CONTROL ROUTINE
092C
THE 'MERGE A LINE OR A VARIABLE' SUBROUTINE
0970
THE 'SAVE' CONTROL ROUTINE
09A1
THE CASSETTE MESSAGES
09F4
THE 'PRINT-OUT' ROUTINES
0A11
THE 'CONTROL CHARACTER' TABLE
0A23
THE 'CURSOR LEFT' SUBROUTINE
0A3D
THE 'CURSOR RIGHT' SUBROUTINE
0A4F
THE 'CARRIAGE RETURN' SUBROUTINE
0A5F
THE 'PRINT COMMA' SUBROUTINE
0A69
THE 'PRINT A QUESTION MARK' SUBROUTINE
0A6D
THE 'CONTROL CHARACTERS WITH OPERANDS' ROUTINE
0AD9
PRINTABLE CHARACTER CODES
0ADC
THE 'POSITION STORE' SUBROUTINE
0B03
THE 'POSITION FETCH' SUBROUTINE
0B24
THE 'PRINT ANY CHARACTER(S)' SUBROUTINE
0BDB
THE 'SET ATTRIBUTE BYTE' SUBROUTINE
0C0A
THE 'MESSAGE PRINTING' SUBROUTINE
0C3B
THE 'PO-SAVE' SUBROUTINE
0C41
THE 'TABLE SEARCH' SUBROUTINE
0C55
THE 'TEST FOR SCROLL' SUBROUTINE
0D4D
THE 'TEMPORARY COLOUR ITEMS' SUBROUTINE
0D6B
THE 'CLS' COMMAND ROUTINE
0DAF
THE 'CLEARING THE WHOLE DISPLAY AREA' SUBROUTINE
0DD9
THE 'CL-SET' SUBROUTINE
0DFE
THE 'SCROLLING' SUBROUTINE
0E44
THE 'CLEAR LINES' SUBROUTINE
0E88
THE 'CL-ATTR' SUBROUTINE
0E9B
THE 'CL-ADDR' SUBROUTINE
0EAC
THE 'COPY' COMMAND ROUTINE
0ECD
THE 'COPY-BUFF' SUBROUTINE
0EDF
THE 'CLEAR PRINTER BUFFER' SUBROUTINE
0EF4
THE 'COPY-LINE' SUBROUTINE
0F2C
THE 'EDITOR' ROUTINES
0FA0
THE 'EDITING KEYS' TABLE
0FA9
THE 'EDIT KEY' SUBROUTINE
0FF3
THE 'CURSOR DOWN EDITING' SUBROUTINE
1007
THE 'CURSOR LEFT EDITING' SUBROUTINE
100C
THE 'CURSOR RIGHT EDITING' SUBROUTINE
1015
THE 'DELETE EDITING' SUBROUTINE
101E
THE 'ED-IGNORE' SUBROUTINE
1024
THE 'ENTER EDITING' SUBROUTINE
1031
THE 'ED-EDGE' SUBROUTINE
1059
THE 'CURSOR UP EDITING' SUBROUTINE
1076
THE 'ED-SYMBOL' SUBROUTINE
107F
THE 'ED-ERROR' SUBROUTINE
1097
THE 'CLEAR-SP' SUBROUTINE
10A8
THE 'KEYBOARD INPUT' SUBROUTINE
111D
THE 'LOWER SCREEN COPYING' SUBROUTINE
1190
THE 'SET-HL' AND 'SET-DE' SUBROUTINES
11A7
THE 'REMOVE-FP' SUBROUTINE
11B7
THE 'NEW' COMMAND ROUTINE
12A2
THE 'MAIN EXECUTION' LOOP
1391
THE REPORT MESSAGES
1539
THE COPYRIGHT MESSAGE
1555
Report G - No room for line
155D
THE 'MAIN-ADD' SUBROUTINE
15AF
THE 'INITIAL CHANNEL INFORMATION'
15C4
Report J - Invalid I/O device
15C6
THE 'INITIAL STREAM DATA'
15D4
THE 'WAIT-KEY' SUBROUTINE
15E6
THE 'INPUT-AD' SUBROUTINE
15EF
THE 'MAIN PRINTING' SUBROUTINE
1601
THE 'CHAN-OPEN' SUBROUTINE
1615
THE 'CHAN-FLAG' SUBROUTINE
162D
THE 'CHANNEL CODE LOOK-UP' TABLE
1634
THE 'CHANNEL 'K' FLAG' SUBROUTINE
1642
THE 'CHANNEL 'S' FLAG' SUBROUTINE
164D
THE 'CHANNEL 'P' FLAG' SUBROUTINE
1652
THE 'MAKE-ROOM' SUBROUTINE
1664
THE 'POINTERS' SUBROUTINE
168F
THE 'COLLECT A LINE NUMBER' SUBROUTINE
169E
THE 'RESERVE' SUBROUTINE
16B0
THE 'SET-MIN' SUBROUTINE
16D4
THE 'RECLAIM THE EDIT-LINE' SUBROUTINE
16DB
THE 'INDEXER' SUBROUTINE
16E5
THE 'CLOSE #' COMMAND ROUTINE
1701
THE 'CLOSE-2' SUBROUTINE
1716
THE 'CLOSE STREAM LOOK-UP' TABLE
171C
THE 'CLOSE STREAM' SUBROUTINE
171E
THE 'STREAM DATA' SUBROUTINE
1736
THE 'OPEN #' COMMAND ROUTINE
175D
THE 'OPEN-2' SUBROUTINE
177A
THE 'OPEN STREAM LOOK-UP' TABLE
1781
THE 'OPEN-K' SUBROUTINE
1785
THE 'OPEN-S' SUBROUTINE
1789
THE 'OPEN-P' SUBROUTINE
1793
THE 'CAT, ERASE, FORMAT and MOVE' COMMAND ROUTINES
1795
THE 'LIST and LLIST' COMMAND ROUTINES
17F5
THE 'LLIST' ENTRY POINT
17F9
THE 'LIST' ENTRY POINT
1855
THE 'PRINT A WHOLE BASIC LINE' SUBROUTINE
18B6
THE 'NUMBER' SUBROUTINE
18C1
THE 'PRINT A FLASHING CHARACTER' SUBROUTINE
18E1
THE 'PRINT THE CURSOR' SUBROUTINE
190F
THE 'LN-FETCH' SUBROUTINE
1925
THE 'PRINTING CHARACTERS IN A BASIC LINE' SUBROUTINE
196E
THE 'LINE-ADDR' SUBROUTINE
1980
THE 'COMPARE LINE NUMBERS' SUBROUTINE
1988
Unused
198B
THE 'FIND EACH STATEMENT' SUBROUTINE
19B8
THE 'NEXT-ONE' SUBROUTINE
19DD
THE 'DIFFERENCE' SUBROUTINE
19E5
THE 'RECLAIMING' SUBROUTINE
19FB
THE 'E-LINE-NO' SUBROUTINE
1A1B
THE 'REPORT AND LINE NUMBER PRINTING' SUBROUTINE
1A48
THE SYNTAX TABLES
1B17
THE 'MAIN PARSER' OF THE BASIC INTERPRETER
1B28
THE STATEMENT LOOP
1B6F
THE 'SEPARATOR' SUBROUTINE
1B76
THE 'STMT-RET' SUBROUTINE
1B8A
THE 'LINE-RUN' ENTRY POINT
1B9E
THE 'LINE-NEW' SUBROUTINE
1BB2
THE 'REM' COMMAND ROUTINE
1BB3
THE 'LINE-END' ROUTINE
1BBF
THE 'LINE-USE' ROUTINE
1BD1
THE 'NEXT-LINE' ROUTINE
1BEE
THE 'CHECK-END' SUBROUTINE
1BF4
THE 'STMT-NEXT' ROUTINE
1C01
THE 'COMMAND CLASS' TABLE
1C0D
THE 'COMMAND CLASSES - +00, +03 and +05'
1C1F
THE 'COMMAND CLASS +01' ROUTINE
1C22
THE 'VARIABLE IN ASSIGNMENT' SUBROUTINE
1C4E
THE 'COMMAND CLASS +02' ROUTINE
1C56
THE 'FETCH A VALUE' SUBROUTINE
1C6C
THE 'COMMAND CLASS +04' ROUTINE
1C79
THE 'EXPECT NUMERIC/STRING EXPRESSIONS' SUBROUTINE
1C96
THE 'SET PERMANENT COLOURS' SUBROUTINE
1CBE
THE 'COMMAND CLASS +09' ROUTINE
1CDB
THE 'COMMAND CLASS +0B' ROUTINE
1CDE
THE 'FETCH A NUMBER' SUBROUTINE
1CEE
THE 'STOP' COMMAND ROUTINE
1CF0
THE 'IF' COMMAND ROUTINE
1D03
THE 'FOR' COMMAND ROUTINE
1D86
THE 'LOOK-PROG' SUBROUTINE
1DAB
THE 'NEXT' COMMAND ROUTINE
1DDA
THE 'NEXT-LOOP' SUBROUTINE
1DEC
THE 'READ' COMMAND ROUTINE
1E27
THE 'DATA' COMMAND ROUTINE
1E39
THE 'PASS-BY' SUBROUTINE
1E42
THE 'RESTORE' COMMAND ROUTINE
1E4F
THE 'RANDOMIZE' COMMAND ROUTINE
1E5F
THE 'CONTINUE' COMMAND ROUTINE
1E67
THE 'GO TO' COMMAND ROUTINE
1E7A
THE 'OUT' COMMAND ROUTINE
1E80
THE 'POKE' COMMAND ROUTINE
1E85
THE 'TWO-PARAM' SUBROUTINE
1E94
THE 'FIND INTEGERS' SUBROUTINE
1EA1
THE 'RUN' COMMAND ROUTINE
1EAC
THE 'CLEAR' COMMAND ROUTINE
1EED
THE 'GO SUB' COMMAND ROUTINE
1F05
THE 'TEST-ROOM' SUBROUTINE
1F1A
THE 'FREE MEMORY' SUBROUTINE
1F23
THE 'RETURN' COMMAND ROUTINE
1F3A
THE 'PAUSE' COMMAND ROUTINE
1F54
THE 'BREAK-KEY' SUBROUTINE
1F60
THE 'DEF FN' COMMAND ROUTINE
1FC3
THE 'UNSTACK-Z' SUBROUTINE
1FC9
THE 'LPRINT and PRINT' COMMAND ROUTINES
1FDF
THE 'PRINT CONTROLLING' SUBROUTINE
1FF5
THE 'PRINT A CARRIAGE RETURN' SUBROUTINE
1FFC
THE 'PRINT ITEMS' SUBROUTINE
2045
THE 'END OF PRINTING' SUBROUTINE
204E
THE 'PRINT POSITION' SUBROUTINE
2070
THE 'ALTER STREAM' SUBROUTINE
2089
THE 'INPUT' COMMAND ROUTINE
21B9
THE 'IN-ASSIGN' SUBROUTINE
21D6
THE 'IN-CHAN-K' SUBROUTINE
21E1
THE 'COLOUR ITEM' ROUTINES
2294
THE 'BORDER' COMMAND ROUTINE
22AA
THE 'PIXEL ADDRESS' SUBROUTINE
22CB
THE 'POINT' SUBROUTINE
22DC
THE 'PLOT' COMMAND ROUTINE
2307
THE 'STK-TO-BC' SUBROUTINE
2314
THE 'STK-TO-A' SUBROUTINE
2320
THE 'CIRCLE' COMMAND ROUTINE
2382
THE 'DRAW' COMMAND ROUTINE
247D
THE 'INITIAL PARAMETERS' SUBROUTINE
24B7
THE 'LINE-DRAWING' SUBROUTINE
24FB
THE 'SCANNING' SUBROUTINE
250F
THE 'SCANNING QUOTES' SUBROUTINE
2522
THE 'SCANNING TWO CO-ORDINATES' SUBROUTINE
2530
THE 'SYNTAX-Z' SUBROUTINE
2535
THE 'SCANNING SCREEN$' SUBROUTINE
2580
THE 'SCANNING ATTRIBUTES' SUBROUTINE
2596
THE SCANNING FUNCTION TABLE
25AF
THE 'SCANNING UNARY PLUS' ROUTINE
25B3
THE 'SCANNING QUOTE' ROUTINE
25E8
THE 'SCANNING BRACKET' ROUTINE
25F5
THE 'SCANNING FN' ROUTINE
25F8
THE 'SCANNING RND' ROUTINE
2627
THE 'SCANNING PI' ROUTINE
2634
THE' SCANNING INKEY$' ROUTINE
2668
THE 'SCANNING SCREEN$' ROUTINE
2672
THE 'SCANNING ATTR' ROUTINE
267B
THE 'SCANNING POINT' ROUTINE
2684
THE 'SCANNING ALPHANUMERIC' ROUTINE
268D
THE 'SCANNING DECIMAL' ROUTINE
26C9
THE 'SCANNING VARIABLE' ROUTINE
2795
THE TABLE OF OPERATORS
27B0
THE TABLE OF PRIORITIES
27BD
THE 'SCANNING FUNCTION' SUBROUTINE
28AB
THE 'FUNCTION SKIPOVER' SUBROUTINE
28B2
THE 'LOOK-VARS' SUBROUTINE
2951
THE 'STACK FUNCTION ARGUMENT' SUBROUTINE
2996
THE 'STK-VAR' SUBROUTINE
2A52
THE 'SLICING' SUBROUTINE
2AB1
THE 'STK-STORE' SUBROUTINE
2ACC
THE 'INT-EXP' SUBROUTINE
2AEE
THE 'DE,(DE+1)' SUBROUTINE
2AF4
THE 'GET-HL*DE' SUBROUTINE
2AFF
THE 'LET' COMMAND ROUTINE
2BF1
THE 'STK-FETCH' SUBROUTINE
2C02
THE 'DIM' COMMAND ROUTINE
2C88
THE 'ALPHANUM' SUBROUTINE
2C8D
THE 'ALPHA' SUBROUTINE
2C9B
THE 'DECIMAL TO FLOATING POINT' SUBROUTINE
2D1B
THE 'NUMERIC' SUBROUTINE
2D22
THE 'STK-DIGIT' SUBROUTINE
2D28
THE 'STACK-A' SUBROUTINE
2D2B
THE 'STACK-BC' SUBROUTINE
2D3B
THE 'INTEGER TO FLOATING-POINT' SUBROUTINE
2D4F
THE 'E-FORMAT TO FLOATING-POINT' SUBROUTINE (offset +3C)
2D7F
THE 'INT-FETCH' SUBROUTINE
2D8C
THE 'POSITIVE-INT-STORE' SUBROUTINE
2D8E
THE 'INT-STORE' SUBROUTINE
2DA2
THE 'FLOATING-POINT TO BC' SUBROUTINE
2DC1
THE 'LOG(2^A)' SUBROUTINE
2DD5
THE 'FLOATING-POINT TO A' SUBROUTINE
2DE3
THE 'PRINT A FLOATING-POINT NUMBER' SUBROUTINE
2F8B
THE 'CA=10*A+C' SUBROUTINE
2F9B
THE 'PREPARE TO ADD' SUBROUTINE
2FBA
THE 'FETCH TWO NUMBERS' SUBROUTINE
2FDD
THE 'SHIFT ADDEND' SUBROUTINE
3004
THE 'ADD-BACK' SUBROUTINE
300F
THE 'SUBTRACTION' OPERATION (offset +03)
3014
THE 'ADDITION' OPERATION (offset +0F)
30A9
THE 'HL=HL*DE' SUBROUTINE
30C0
THE 'PREPARE TO MULTIPLY OR DIVIDE' SUBROUTINE
30CA
THE 'MULTIPLICATION' OPERATION (offset +04)
31AF
THE 'DIVISION' OPERATION (offset +05)
3214
THE 'INTEGER TRUNCATION TOWARDS ZERO' SUBROUTINE (offset +3A)
3293
THE 'RE-STACK TWO' SUBROUTINE
3297
THE 'RE-STACK' SUBROUTINE (offset +3D)
32C5
THE TABLE OF CONSTANTS
32D7
THE TABLE OF ADDRESSES
335B
THE 'CALCULATE' SUBROUTINE
33A2
THE 'SINGLE OPERATION' SUBROUTINE (offset +3B)
33A9
THE 'TEST 5-SPACES' SUBROUTINE
33B4
THE 'STACK NUMBER' SUBROUTINE
33C0
THE 'MOVE A FLOATING-POINT NUMBER' SUBROUTINE (offset +31)
33C6
THE 'STACK LITERALS' SUBROUTINE (offset +34)
33F7
THE 'SKIP CONSTANTS' SUBROUTINE
3406
THE 'MEMORY LOCATION' SUBROUTINE
340F
THE 'GET FROM MEMORY AREA' SUBROUTINE (offset +41)
341B
THE 'STACK A CONSTANT' SUBROUTINE (offset +3F)
342D
THE 'STORE IN MEMORY AREA' SUBROUTINE (offset +40)
343C
THE 'EXCHANGE' SUBROUTINE (offset +01)
3449
THE 'SERIES GENERATOR' SUBROUTINE (offset +3E)
346A
THE 'ABSOLUTE MAGNITUDE' FUNCTION (offset +2A)
346E
THE 'UNARY MINUS' OPERATION (offset +1B)
3492
THE 'SIGNUM' FUNCTION (offset +29)
34A5
THE 'IN' FUNCTION (offset +2C)
34AC
THE 'PEEK' FUNCTION (offset +2B)
34B3
THE 'USR' FUNCTION (offset +2D)
34BC
THE 'USR STRING' FUNCTION (offset +19)
34E9
THE 'TEST-ZERO' SUBROUTINE
34F9
THE 'GREATER THAN ZERO' OPERATION (offset +37)
3501
THE 'NOT' FUNCTION (offset +30)
3506
THE 'LESS THAN ZERO' OPERATION (offset +36)
350B
THE 'ZERO OR ONE' SUBROUTINE
351B
THE 'OR' OPERATION (offset +07)
3524
THE 'NUMBER AND NUMBER' OPERATION (offset +08)
352D
THE 'STRING AND NUMBER' OPERATION (offset +10)
353B
THE 'COMPARISON' OPERATIONS (offsets +09 to +0E, +11 to +16)
359C
THE 'STRING CONCATENATION' OPERATION (offset +17)
35BF
THE 'STK-PNTRS' SUBROUTINE
35C9
THE 'CHR$' FUNCTION (offset +2F)
35DE
THE 'VAL' AND 'VAL$' FUNCTIONS (offsets +18, +1D)
361F
THE 'STR$' FUNCTION (offset +2E)
3645
THE 'READ-IN' SUBROUTINE (offset +1A)
3669
THE 'CODE' FUNCTION (offset +1C)
3674
THE 'LEN' FUNCTION (offset +1E)
367A
THE 'DECREASE THE COUNTER' SUBROUTINE (offset +35)
3686
THE 'JUMP' SUBROUTINE (offset +33)
368F
THE 'JUMP ON TRUE' SUBROUTINE (offset +00)
369B
THE 'END-CALC' SUBROUTINE (offset +38)
36A0
THE 'MODULUS' SUBROUTINE (offset +32)
36AF
THE 'INT' FUNCTION (offset +27)
36C4
THE 'EXPONENTIAL' FUNCTION (offset +26)
3713
THE 'NATURAL LOGARITHM' FUNCTION (offset +25)
3783
THE 'REDUCE ARGUMENT' SUBROUTINE (offset +39)
37AA
THE 'COSINE' FUNCTION (offset +20)
37B5
THE 'SINE' FUNCTION (offset +1F)
37DA
THE 'TAN' FUNCTION (offset +21)
37E2
THE 'ARCTAN' FUNCTION (offset +24)
3833
THE 'ARCSIN' FUNCTION (offset +22)
3843
THE 'ARCCOS' FUNCTION (offset +23)
384A
THE 'SQUARE ROOT' FUNCTION (offset +28)
3851
THE 'EXPONENTIATION' OPERATION (offset +06)
386E
Unused
3D00
Character set
5C00
KSTATE - Used in reading the keyboard
5C08
LAST-K - Last key pressed
5C09
REPDEL - Time that a key must be held down before it repeats
5C0A
REPPER - Delay between successive repeats of a key held down
5C0B
DEFADD - Address of arguments of user defined function
5C0D
K-DATA - Second byte of colour controls entered from keyboard
5C0E
TVDATA - Colour, AT and TAB controls going to television
5C10
STRMS - Addresses of channels attached to streams
5C36
CHARS - 256 less than address of character set
5C38
RASP - Length of warning buzz
5C39
PIP - Length of keyboard click
5C3A
ERR-NR - One less than the error report code
5C3B
FLAGS - Various flags to control the BASIC system
5C3C
TV-FLAG - Flags associated with the television
5C3D
ERR-SP - Address of item on machine stack to use as error return
5C3F
LIST-SP - Return address from automatic listing
5C41
MODE - Specifies K, L, C, E or G cursor
5C42
NEWPPC - Line to be jumped to
5C44
NSPPC - Statement number in line to be jumped to
5C45
PPC - Line number of statement being executed
5C47
SUBPPC - Number within line of statement being executed
5C48
BORDCR - Border colour
5C49
E-PPC - Number of current line
5C4B
VARS - Address of variables
5C4D
DEST - Address of variable in assignment
5C4F
CHANS - Address of channel data
5C51
CURCHL - Address of information used for input and output
5C53
PROG - Address of BASIC program
5C55
NXTLIN - Address of next line in program
5C57
DATADD - Address of terminator of last DATA item
5C59
E-LINE - Address of command being typed in
5C5B
K-CUR - Address of cursor
5C5D
CH-ADD - Address of the next character to be interpreted
5C5F
X-PTR - Address of the character after the '?' marker
5C61
WORKSP - Address of temporary work space
5C63
STKBOT - Address of bottom of calculator stack
5C65
STKEND - Address of start of spare space
5C67
BREG - Calculator's B register
5C68
MEM - Address of area used for calculator's memory
5C6A
FLAGS2 - More flags
5C6B
DF-SZ - The number of lines in the lower part of the screen
5C6C
S-TOP - The number of the top program line in automatic listings
5C6E
OLDPPC - Line number to which CONTINUE jumps
5C70
OSPCC - Number within line of statement to which CONTINUE jumps
5C71
FLAGX - Various flags
5C72
STRLEN - Length of string type destination in assignment
5C74
T-ADDR - Address of next item in parameter table
5C76
SEED - The seed for RND
5C78
FRAMES - Frame counter
5C7B
UDG - Address of first user defined graphic
5C7D
COORDS - Coordinates of last point plotted
5C7F
P-POSN - Column number of printer position
5C80
PR-CC - Address of next position for LPRINT to print at
5C82
ECHO-E - Column and line number of end of input buffer
5C84
DF-CC - Address in display file of PRINT position
5C86
DF-CCL - Like DF-CC for lower part of screen
5C88
S-POSN - Column and line number for PRINT position
5C8A
S-POSNL - Like S-POSN for lower part of screen
5C8C
SCR-CT - Scroll counter
5C8D
ATTR-P - Permanent current colours
5C8E
MASK-P - Used for transparent colours
5C8F
ATTR-T - Temporary current colours
5C90
MASK-T - Temporary transparent colours
5C91
P-FLAG - More flags
5C92
MEMBOT - Calculator's memory area
5CB0
NMIADD - Non-maskable interrupt address
5CB2
RAMTOP - Address of last byte of BASIC system area
5CB4
P-RAMT - Address of last byte of physical RAM
5CB6
Channel information
SYSTEM VARS 48 & 128
48k Sys Vars
Spectrum ROM System variables
Address Length Description
5C00 8 KSTATE - Used in reading the keyboard
5C08 1 LAST-K - Last key pressed
5C09 1 REPDEL - Time that a key must be held down before it repeats
5C0A 1 REPPER - Delay between successive repeats of a key held down
5C0B 2 DEFADD - Address of arguments of user defined function
5C0D 1 K-DATA - Second byte of colour controls entered from keyboard
5C0E 2 TVDATA - Colour, AT and TAB controls going to television
5C10 38 STRMS - Addresses of channels attached to streams
5C36 2 CHARS - 256 less than address of character set
5C38 1 RASP - Length of warning buzz
5C39 1 PIP - Length of keyboard click
5C3A 1 ERR-NR - One less than the error report code
5C3B 1 FLAGS - Various flags to control the BASIC system
5C3C 1 TV-FLAG - Flags associated with the television
5C3D 2 ERR-SP - Address of item on machine stack to use as error return
5C3F 2 LIST-SP - Return address from automatic listing
5C41 1 MODE - Specifies K, L, C, E or G cursor
5C42 2 NEWPPC - Line to be jumped to
5C44 1 NSPPC - Statement number in line to be jumped to
5C45 2 PPC - Line number of statement being executed
5C47 1 SUBPPC - Number within line of statement being executed
5C48 1 BORDCR - Border colour
5C49 2 E-PPC - Number of current line
5C4B 2 VARS - Address of variables
5C4D 2 DEST - Address of variable in assignment
5C4F 2 CHANS - Address of channel data
5C51 2 CURCHL - Address of information used for input and output
5C53 2 PROG - Address of BASIC program
5C55 2 NXTLIN - Address of next line in program
5C57 2 DATADD - Address of terminator of last DATA item
5C59 2 E-LINE - Address of command being typed in
5C5B 2 K-CUR - Address of cursor
5C5D 2 CH-ADD - Address of the next character to be interpreted
5C5F 2 X-PTR - Address of the character after the '?' marker
5C61 2 WORKSP - Address of temporary work space
5C63 2 STKBOT - Address of bottom of calculator stack
5C65 2 STKEND - Address of start of spare space
5C67 1 BREG - Calculator's B register
5C68 2 MEM - Address of area used for calculator's memory
5C6A 1 FLAGS2 - More flags
5C6B 1 DF-SZ - The number of lines in the lower part of the screen
5C6C 2 S-TOP - The number of the top program line in automatic listings
5C6E 2 OLDPPC - Line number to which CONTINUE jumps
5C70 1 OSPCC - Number within line of statement to which CONTINUE jumps
5C71 1 FLAGX - Various flags
5C72 2 STRLEN - Length of string type destination in assignment
5C74 2 T-ADDR - Address of next item in parameter table
5C76 2 SEED - The seed for RND
5C78 3 FRAMES - Frame counter
5C7B 2 UDG - Address of first user defined graphic
5C7D 2 COORDS - Coordinates of last point plotted
5C7F 1 P-POSN - Column number of printer position
5C80 2 PR-CC - Address of next position for LPRINT to print at
5C82 2 ECHO-E - Column and line number of end of input buffer
5C84 2 DF-CC - Address in display file of PRINT position
5C86 2 DF-CCL - Like DF-CC for lower part of screen
5C88 2 S-POSN - Column and line number for PRINT position
5C8A 2 S-POSNL - Like S-POSN for lower part of screen
5C8C 1 SCR-CT - Scroll counter
5C8D 1 ATTR-P - Permanent current colours
5C8E 1 MASK-P - Used for transparent colours
5C8F 1 ATTR-T - Temporary current colours
5C90 1 MASK-T - Temporary transparent colours
5C91 1 P-FLAG - More flags
5C92 30 MEMBOT - Calculator's memory area
5CB0 2 NMIADD - Non-maskable interrupt address
5CB2 2 RAMTOP - Address of last byte of BASIC system area
5CB4 2 P-RAMT - Address of last byte of physical RAM
5CB6 21 Channel information
128K SysVars
Spectrum ROM System variables
Address Length Description
5C00 8 KSTATE - Used in reading the keyboard
5C08 1 LAST-K - Last key pressed
5C09 1 REPDEL - Time that a key must be held down before it repeats
5C0A 1 REPPER - Delay between successive repeats of a key held down
5C0B 2 DEFADD - Address of arguments of user defined function
5C0D 1 K-DATA - Second byte of colour controls entered from keyboard
5C0E 2 TVDATA - Colour, AT and TAB controls going to television
5C10 38 STRMS - Addresses of channels attached to streams
5C36 2 CHARS - 256 less than address of character set
5C38 1 RASP - Length of warning buzz
5C39 1 PIP - Length of keyboard click
5C3A 1 ERR-NR - One less than the error report code
5C3B 1 FLAGS - Various flags to control the BASIC system
5C3C 1 TV-FLAG - Flags associated with the television
5C3D 2 ERR-SP - Address of item on machine stack to use as error return
5C3F 2 LIST-SP - Return address from automatic listing
5C41 1 MODE - Specifies K, L, C, E or G cursor
5C42 2 NEWPPC - Line to be jumped to
5C44 1 NSPPC - Statement number in line to be jumped to
5C45 2 PPC - Line number of statement being executed
5C47 1 SUBPPC - Number within line of statement being executed
5C48 1 BORDCR - Border colour
5C49 2 E-PPC - Number of current line
5C4B 2 VARS - Address of variables
5C4D 2 DEST - Address of variable in assignment
5C4F 2 CHANS - Address of channel data
5C51 2 CURCHL - Address of information used for input and output
5C53 2 PROG - Address of BASIC program
5C55 2 NXTLIN - Address of next line in program
5C57 2 DATADD - Address of terminator of last DATA item
5C59 2 E-LINE - Address of command being typed in
5C5B 2 K-CUR - Address of cursor
5C5D 2 CH-ADD - Address of the next character to be interpreted
5C5F 2 X-PTR - Address of the character after the '?' marker
5C61 2 WORKSP - Address of temporary work space
5C63 2 STKBOT - Address of bottom of calculator stack
5C65 2 STKEND - Address of start of spare space
5C67 1 BREG - Calculator's B register
5C68 2 MEM - Address of area used for calculator's memory
5C6A 1 FLAGS2 - More flags
5C6B 1 DF-SZ - The number of lines in the lower part of the screen
5C6C 2 S-TOP - The number of the top program line in automatic listings
5C6E 2 OLDPPC - Line number to which CONTINUE jumps
5C70 1 OSPCC - Number within line of statement to which CONTINUE jumps
5C71 1 FLAGX - Various flags
5C72 2 STRLEN - Length of string type destination in assignment
5C74 2 T-ADDR - Address of next item in parameter table
5C76 2 SEED - The seed for RND
5C78 3 FRAMES - Frame counter
5C7B 2 UDG - Address of first user defined graphic
5C7D 2 COORDS - Coordinates of last point plotted
5C7F 1 P-POSN - Column number of printer position
5C80 2 PR-CC - Address of next position for LPRINT to print at
5C82 2 ECHO-E - Column and line number of end of input buffer
5C84 2 DF-CC - Address in display file of PRINT position
5C86 2 DF-CCL - Like DF-CC for lower part of screen
5C88 2 S-POSN - Column and line number for PRINT position
5C8A 2 S-POSNL - Like S-POSN for lower part of screen
5C8C 1 SCR-CT - Scroll counter
5C8D 1 ATTR-P - Permanent current colours
5C8E 1 MASK-P - Used for transparent colours
5C8F 1 ATTR-T - Temporary current colours
5C90 1 MASK-T - Temporary transparent colours
5C91 1 P-FLAG - More flags
5C92 30 MEMBOT - Calculator's memory area
5CB0 2 NMIADD - Non-maskable interrupt address
5CB2 2 RAMTOP - Address of last byte of BASIC system area
5CB4 2 P-RAMT - Address of last byte of physical RAM
5CB6 21 Channel information
В режиме 128К в области с #5B00 по #5BFF также
используются системой. Там находятся системные переменные.
Существует некоторая разница между областями системных
переменных в режимах 128К и 48К. В 48К все переменные по
адресу ниже #5C00 не существуют. Вместо этого там находится
буфер принтера, но многие хранят там маленькие программы и
если одна из них будет запущена в режиме 128К, то призойдет
сбой в системе. Поэтому большинство программ рекомендуется
запускать из режима 48К (хотя сначала лучше проверить
работает она или нет в 128К-режиме).
SWAP, 20 байт, #5B00, R, 23296
Подпрограмма смены страниц памяти:
PUSH AF
PUSH BC
LD BC,#7DDF
LD A,(#5B5C)
XOR #10
DI
LD (#5B5C),A
OUT (C),A
EI
POP BC
POP AF
RET
YOUNGER, 9 байт, #5B14, R, 23316
Подпрограмма обмена страниц памяти
ON_ERR, 18 байт, #5B19, R, 23325
Подпрограмма обмена страниц в случае ошибки.
P_IN, 5 байт, #5B2F, R, 23343
Подпрограмма ввода с RS232
P_OUT, 22 байта, #5B34, R, 23348
Подпрограмма вывода в RS232
P_OUT2, 14 байт, #5B4A, R, 23370
Подпрограмма вывода 1 байта в RS232
TARGET, слово, #5B58, N, 23384
Адрес вызываемой подпрограммы из Бейсик-ПЗУ (старое ПЗУ).
RETADDR, слово, #5B5A, X, 23386
Адрес возврата в ПЗУ-128К, после вызова процедуры.
BANK_M, байт, #5B5C, X, 23388
Копия последнего байта посланного в порт #7FFD
RAMRST, байт, #5B5D, X, 23389
Здесь храниться код инструкции Z80 RST 8
RAMERR, байт, #5B5E, N, 23390
Код ошибки Бейсик-ПЗУ.
BAUD, слово, #5B5F, , 23391
Скорость передачи данных для RS232. Храниться как битовый
период в виде Т/26. Храниться сначала старший байт, потом
младший.
SERF, 2 байта, #5B61, N, 23393
Флаг состояния, принимаемого с RS232 значения и само
значение.
COL, байт, #5B63, N, 23395
Текущая колонка печати на принтере
WIDTH, байт, #5B64, , 23396
Ширина страницы принтера
TVPARS, байт, #5B65, , 23397
Код параметра ожидаемого с RS232
FLAGS3, байт, #5B66, , 23398
Флаговый байт
N_STR, 10 байт, #5B67, N, 23399
Имя файла, записываемого в RAM-DISK
HD00, байт, #5B71, N, 23409
Тип файла, записываемого в RAM-DISK
HD0B, слово, #5B72, N, 23410
Длина файла, записываемого в RAM-DISK
HD0D, слово, #5B74, N, 23412
Адрес, с которого был записан в RAM-DISK
HD0F, слово, #5B76, N, 23414
Длина бейсик-программы, записанной в RAM-DISK. Отняв
HD0F от HD0B получите размер области переменных записанных в
RAM-DISK
HD11, слово, #5B78, N, 23416
Номер строки, с которой будет запускаться бейсик-программа
при считывания RAM-DISKа
SC00, байт, 5B7A, N, 23418
Тип файла
SC01, слово, #5B7B, N, 23419
Длина файла
SC08, слово, #5B7D, , 23421
Начало загрузки
SC0F, слово, #5B7F, , 23423
Длина программы
OLDSP, слово, #5B81, X, 23425
Здесь временно сохраняется регистр SP при использовании
TSTRACK.
SFNEXT, слово, #5B83, X, 23427
Указатель свободного места в директории
SFSPACE, 3 байта, #5B85, X, 23429
Размер свободной памяти RAM-DISKа
ROW01, байт, #5B88, , 23432 малая клавиатура +1 ряд
ROW02, байт, #5B89, , 23433 2+3 ряд
ROW03, байт, #5B8A, , 23434 4+5 ряд
Недокументированные ячейки, назначение их неясно.
(Возможно, они предпологались для обслуживания клавиатуры с
последователеной передачей данных, как в IBM PC)
SYSRET, слово #5B8B, X, 23435
Адрес возврата для процедуры ON_ERR-32767
LAST_V, 5 байт, #5B8D, , 23437
Последнее значение введенное в калькуляторе
RNLINE, слово, #5B92, , 23442
Текущая перенумеруемая строка
RNFIRST, слово, #5B94, , 23444
Новый номер первой строки при перенумерации. Хранить сначала
старший байт, потом младший.
RNSTEP, слово, #5B96, , 23446
Приращение номера строки при перенумерации. Хранить сначала
старший байт, потом младший.
STRPI1, 8 BAJT, #5B98, N, 23448
Полоска первой карты битов
STRIP2, 8 байта, #5BA0, N, 23456
Полоска второй карты битов
TSTACK, ,,X,
Область временного стека, начинающегося с адреса #5BFE и
растет вниз по памяти.
TRDOS COMANDS & VARS
Системные переменные TR-DOS 5.04T.
╔═════╤═══╤══════════════════════════════════════════════════╗
║Адрес│Дл.│ Содержимое. ║
╟─────┼───┼──────────────────────────────────────────────────╢
║23734│ 1 │Используется, если есть ИНТЕPФЕЙС-1. Если равно ║
║ │ │244, то область переменных не переносится, иначе ║
║ │ │проверяется 23832. ║
║23735│ 11│Не используется. ║
║23746│ 1 │Содержит команду RET. Используется для переключе- ║
║ │ │ния ПЗУ на бейсик. ║
║23747│ 5 │Не используется. ║
║23752│ 1 │Тип дисковода A: ║
║ │ │ бит 7=0 - дисковод 40-дорожечный. ║
║ │ │ 1 - дисковод 80-дорожечный. ║
║ │ │ бит 1=0 - дисковод односторонний. ║
║ │ │ 1 - дисковод двухсторонний. ║
║ │ │ бит 0=0 - использовать 80-дорожечный дисковод как║
║ │ │ 40-дорожечный. ║
║23753│ 1 │Тип дисковода B. ║
║23754│ 1 │Тип дисковода C. ║
║23755│ 1 │Тип дисковода D. ║
║23756│ 1 │Текущий сектор при работе с каталогом. ║
║23757│ 1 │Если не 0, то после позиционирования будет задерж-║
║ │ │ка. Регистр состояния ВГ-93 перед проверкой дорож-║
║ │ │ки. Бит 7 регистра состояния ВГ-93 перед чтением ║
║ │ │адресного маркера. ║
║23758│ 1 │Флаг операции с секторами. При 0 -чтение секторов,║
║ │ │при 255 - запись. ║
║23759│ 2 │Адрес рабочей области памяти для MOVE, COPY, LIST.║
║ │ │и при обработке номера записи при выводе в файл ║
║ │ │данных прямого доступа. ║
║23761│ 1 │Длина перемещаемого файла для MOVE. ║
║23762│ 1 │Имя массива при записи / загрузке массива в виде: ║
║ │ │биты 0 - 4 - имя массива ( от "A"=1 до "Z"=26), ║
║ │ │бит 5 - если 0, то массив числовой, ║
║ │ │бит 6 - если 1, то массив строковый, ║
║ │ │бит 7 - всегда 1. ║
║23761│ 2 │Номер строки автостарта при записи программы на ║
║ │ │бейсике. ║
║23763│ 2 │Счетчик секторов перемещаемого файла для MOVE. ║
║23764│ 1 │Номер стираемого файла для MOVE. ║
║23765│ 1 │Текущий сектор перемещаемого файла для MOVE. ║
║23766│ 1 │Текущая дорожка перемещаемого файла для MOVE. Ко- ║
║ │ │личество дефектных секторов при форматировании и ║
║ │ │проверке диска. Для подпрограммы сжатия строки: ║
║ │ │если 0, то команда находится в строке программы на║
║ │ │бейсике, иначе в другом месте. Для подпрограммы ║
║ │ │загрузки файла: если 0, то адрес загрузки и длина ║
║ │ │берутся из описателя файла, если 3, то из 23769 и ║
║ │ │23771 соответственно, иначе адрес загрузки берется║
║ │ │из 23769, а длина - из описателя файла. ║
║23767│ 1 │Текущий сектор стираемого файла при MOVE. Количес-║
║ │ │тво дорожек при определении типа дисковода и фор- ║
║ │ │матировании. ║
║23768│ 1 │Текущая дорожка стираемого файла при MOVE. Если не║
║ │ │0, то форматируемая дорожка не проверяется. ║
║23767│ 2 │Сохраняет CH_ADD при обработке номера записи в ║
║ │ │файле последовательного доступа. Адрес переменной ║
║ │ │длины строки для подпрограммы сжатия строки. Адрес║
║ │ │старого массива при загрузке массива. Адрес секто-║
║ │ │ра для PEEK и POKE. ║
║23769│ 1 │Относительный адрес записи при обработке номера ║
║ │ │записи в файле последовательного доступа. ║
║23770│ 1 │Номер открываемого блока файла произвольного дос- ║
║ │ │тупа при обработке номера записи. Если равно 128, ║
║ │ │то форматируются две стороны, иначе только одна. ║
║23769│ 2 │Счетчик освобождающихся секторов для MOVE. Адрес ║
║ │ │загрузки файла для LOAD, номер сектора для PEEK и ║
║ │ │POKE. Адрес ключевого слова для подпрограммы сжа- ║
║ │ │тия строки. Длина файла для записи при SAVE. ║
║23771│ 1 │Номер загружаемого сектора блока файла произволь- ║
║ │ │ного доступа при обработке номера записи. Номер ║
║ │ │первого сектора перемещаемого файла для MOVE. ║
║23772│ 1 │номер первой дорожки перемещаемого файла для MOVE.║
║23771│ 2 │Длина файла для LOAD. Длина файла для указания в ║
║ │ │каталоге при SAVE. Номер потока для CAT и LIST. ║
║23773│ 8 │Имя файла или диска при форматировании. ║
║23781│ 1 │Расширение файла. ║
║23782│ 2 │Адрес загрузки файла. Адрес таблицы секторов для ║
║ │ │форматирования. ║
║23784│ 2 │Длина файла. Адрес таблицы секторов для проверки ║
║ │ │дорожки. ║
║23786│ 1 │Обьем файла в секторах. ║
║23787│ 1 │Номер первого сектора файла. ║
║23788│ 1 │Номер первой дорожки файла. ║
║23789│ 2 │Адрес загрузки старого файла для COPY. ║
║23791│ 2 │Длина старого файла в байтах для COPY. ║
║23793│ 1 │Длина старого файла в секторах для COPY. ║
║23794│ 1 │Номер первого сектора старого файла для COPY. ║
║23795│ 1 │Номер первой дорожки старого файла для COPY. ║
║23796│ 1 │Номер текущего сектора для подпрограммы ║
║ │ │загрузки / записи секторов. ║
║23797│ 1 │Номер текущей дорожки для подпрограммы ║
║ │ │загрузки / записи секторов. ║
║23798│ 2 │Номер дисковода для операции (0 - 3). ║
║23800│ 1 │Дисковод-источник для COPY. Если равно 255, то при║
║ │ │выводе в файл данных буфер не удаляется. ║
║23801│ 1 │Дисковод-приемник для COPY. Номер дисковода при ║
║ │ │выводе каталога. Признак операции с файлом: 0 - ║
║ │ │- загрузка, 255 - верификация. ║
║23802│ 1 │Время перемещения головки дисковода A: (8 - 11). ║
║23803│ 1 │То же для дисковода B:. ║
║23804│ 1 │То же для дисковода C:. ║
║23805│ 1 │То же для дисковода D:. ║
║23806│ 1 │Команда контроллера для подпрограммы чтения / за- ║
║ │ │писи сектора. ║
║23807│ 1 │Номер сектора для подпрограммы чтения / записи ║
║ │ │сектора. ║
║23808│ 2 │Адрес сектора для подпрограммы чтения / записи ║
║ │ │сектора. ║
║23810│ 2 │Сохраняет HL для подпрограммы вызова подпрограмм ║
║ │ │из ПЗУ бейсика и 15635. ║
║23812│ 2 │Сохраняет DE. ║
║23814│ 1 │Число проверяемых байтов описателя файла при его ║
║ │ │поиске. ║
║23815│ 1 │Количество стертых файлов для подпрограммы стира- ║
║ │ │ния файлов. ║
║23816│ 1 │Первый символ имени файла для подпрограммы стира- ║
║ │ │ния файлов. ║
║23817│ 1 │тип файла данных для OPEN# ("R", "W" или "RND"). ║
║23819│ 2 │Не используется. ║
║23820│ 1 │Флаг наличия буфера: 0 - есть, иначе - нет. ║
║23821│ 1 │Номер текущего файла при копировании всего диска с║
║ │ │двумя дисководами. ║
║23822│ 1 │Флаг состояния рабочей области памяти. Если равно ║
║ │ │255, то рабочая область использовалась. Если равно║
║ │ │254, то подпрограмма 963 игнорирует ошибки. ║
║23823│ 1 │Код ошибки TR-DOS. При поиске файла подпрограммой ║
║ │ │15635: 255 - файл не найден, иначе - номер файла. ║
║23824│ 1 │Флаг операции для подпрограммы загрузки / верифи- ║
║ │ │кации файла: 0 - операция с файлом, 255 - загруз- ║
║ │ │ка / верификация сектора файла, иначе - запись ║
║ │ │сектора файла. ║
║23825│ 2 │Адрес командной строки. ║
║23827│ 2 │Сохраняет содержимое ERR_SP для подпрограмм воз- ║
║ │ │врата в бейсик. ║
║23829│ 1 │Если 0, то на экран выводятся сообщения об ошиб- ║
║ │ │ках, иначе не выводятся. ║
║23830│ 1 │Копия системного регистра. ║
║23831│ 1 │Если равно 170, то при вызове 15612 заставка не ║
║ │ │выводится, иначе выводится заставка и проверяется ║
║ │ │байт по адресу 23296. Если он равен 170, то проис-║
║ │ │ходит запуск файла "boot". ║
║23832│ 1 │Используется, если есть ИHТЕPФЕЙС-1. Если не 0, то║
║ │ │меняются местами блоки памяти длиной 45 байтов по ║
║ │ │адресам 23747 и 23859. ║
║23833│ 1 │Номер дисковода по умолчанию. ║
║23834│ 2 │Адрес возврата из подпрограммы завеpшения. ║
║23836│ 2 │Сохраняет SP для подпрограмм возвpата в бейсик. ║
║23838│ 1 │Номер файла при его поиске. ║
║23839│ 1 │Флаг способа вызова TR-DOS. Если 0, то вызов был ║
║ │ │из машинного кода, иначе - из бейсика. Первый сек-║
║ │ │тор файла на диске - приемнике для COPY S. ║
║23840│ 1 │Первый сектор файла на диске-приемнике для COPY S.║
║23840│ 3 │Сохраняет 3 первых символа командной строки. ║
║23841│ 1 │Если не 0, то идет первый проход копирования, ина-║
║ │ │че продолжение. ║
║23843│ 1 │Размер доступной памяти в секторах для MOVE и ║
║ │ │COPY. ║
╚═════╧═══╧══════════════════════════════════════════════════╝
При инициализации системы используются следующие ячейки:
╔═════╤═══╤══════════════════════════════════════════════════╗
║Адрес│Дл.│ Содержимое. ║
╟─────┼───┼──────────────────────────────────────────────────╢
║23746│ 1 │Команда RET. Используется для вызова подпрограмм ║
║ │ │из ПЗУ бейсика. ║
║24320│ 2 │Сохраняет HL для подпрограммы выполнения команды ║
║ │ │процессора в ОЗУ. ║
║24322│ 14│Не используется. ║
║24336│ 3 │Подпрограмма перемещения блоков памяти LDIR или ║
║ │ │LDDR. ║
║24339│237│Временный стек. ║
╚═════╧═══╧══════════════════════════════════════════════════╝
Также при инициализации системных переменных TR-DOS 20 бай-
тов с адреса 23698 используются для размещения подпрограммы
проверки наличия ИHТЕPФЕЙСа-1.
Способы обращения к ПЗУ TR-DOS.
ПЗУ TR-DOS является теневым, поэтому к нему нельзя обра-
титься непосредственно при помощи CALL. Но для того, чтобы оно
было доступно для использования, существуют адреса, при пере-
ходе на которые включается ПЗУ TR-DOS. В ПЗУ бейсика-48 в этих
адресах находится знакогенератор, следовательно обычно управ-
ление туда никогда не передается. Внимание!!! В ПЗУ бейси-
ка-128 в этих адресах находится программа, поэтому при вклю-
ченном ПЗУ бейсика-128 ПЗУ TR-DOS блокируется полностью. Ниже
вы видите список точек входа, переключающих ПЗУ.
15616 - вход в командный процессор TR-DOS.
15619 - выполнение команд TR-DOS из бейсика.
15622 - подпрограмма ввода из файла данных.
15629 - подпрограмма вывода в файл данных.
15632 - подпрограмма изменения памяти.
15635 - вызов подпрограмм TR-DOS из машинного кода.
15638 - подпрограмма обработки ошибок, поступающих из ПЗУ бей-
сика.
15663 - переход на любой адрес в ПЗУ TR-DOS.
Как пользоваться этими точками входа.
15616 - простой вызов. Можно установить переменные 23831 и
23296.
15619 - из бейсика:
RANDOMIZE USR 15619:REM:<команда>
из машинного кода:
1) разместить в памяти командную строку в ASCII виде с
префиксом REM:.
2) поместить в CH_ADD адрес этой строки.
3) CALL 15619.
Hапример:
LD HL,LINE ;установка CH_ADD
LD (23645),HL
JP 15619 ;выполнение команды
;командная строка
LINE DEFB 234 ;REM
DEFB ":" ;:
DEFB 239 ;HOAD
DEFB 34 ;"
DEFM "EXAMPLE";EXAMPLE
DEFB 34 ;"
DEFB 13 ;ENTER
15622 - открыть канал файла данных и вызвать. На выходе символ
из файла будет в аккумуляторе.
15629 - открыть канал файла данных, поместить в A символ и
вызвать. Внимание!!! Содержит ошибку.
15632 - просто вызвать. Проверяет 23734 и 23832 и меняет блоки
памяти местами, если нужно.
15635 - Номер подпрограммы поместите в регистр C, остальное
согласно таблице:
╔═════╤══════════════════════════════════════════════════════╗
║ Ком.│ Функции ║
╟─────┼──────────────────────────────────────────────────────╢
║ 0 │Восстановление с ожиданием INTRQ. Воспринимает BREAK. ║
║ 1 │Выбор дисковода. Номер дисковода поместите в регистр ║
║ │A. Если в переменной с временем перемещения головки ║
║ │дисковода бит 7 включен, то определяется времЯ пеpеме-║
║ │щения головки и проверяется переменная с типом диско- ║
║ │вода. Если она не равна 255 (ошибка), то будет опреде-║
║ │лено количество дорожек дисковода. При этом предпола- ║
║ │гается, что дисковод односторонний (ошибка). Пpи воз- ║
║ │врате определяется номеp дорожки, на которой стоит го-║
║ │ловка дисковода и заносится в регистр дорожки. ║
║ 2 │Позиционирование. Логический ноль дорожки поместите в║
║ │A. Если по адресу 23757 не 0, то после позиционирова- ║
║ │ния будет задержка. В программе есть ошибка. ║
║ 3 │Помещает содержимое аккумулятора по адресу 23807. ║
║ 4 │Помещает содержимое HL по адресу 23808. ║
║ 5 │Чтение группы секторов. В HL поместите адрес в памяти,║
║ │в D - номер первой дорожки, в E - номер первого секто-║
║ │ра, в B - количество секторов. В программе есть ошиб- ║
║ │ка. ║
║ 6 │Запись группы секторов. Параметры и ошибка аналогично ║
║ │команде 5. ║
║ 7 │Вывод каталога. В аккумулятор поместите номер потока, ║
║ │а в 23801 поместите номер дисковода из 23798. В прог- ║
║ │рамме есть ошибка. ║
║ 8 │Чтение описателя файла по адресу 23773. Номер файла ║
║ │поместите в аккумулятор. ║
║ 9 │Запись описателя файла. Описатель разместите по адре- ║
║ │су 23773 и поместите номер файла в аккумулятор. ║
║ 10 │Поиск файла. Проверяемую часть описателя разместите с ║
║ │адреса 23773, а ее длину поместите в 23814. Если файл ║
║ │найден, то в BC, 23823 и 23838 будет его номер, иначе ║
║ │23838 не меняется, а в 23823 и BC будет 255. ║
║ 11 │Запись файла. Имя и расширение поместите с 23773, на- ║
║ │чало в памяти поместите в HL, а длину - в DE. ║
║ 12 │Запись программы на бейсике. Имя и расширение помести-║
║ │те с адреса 23773. Если расширение не "B", то файл за-║
║ │писывается как кодовый. ║
║ 13 │Не используется. ║
║ 14 │Выполняет 5 функций: ║
║ │ Загрузка файла: в 23801 и 23824 поместите 0, имя и ║
║ │ расширение поместите с 23773, а также: ║
║ │ Для бейсик - программ - больше ничего. ║
║ │ Для файлов CODE : ║
║ │ Пpи A=0 - адрес загрузки и длина берутся из катало-║
║ │ га. ║
║ │ При A=3 - адрес загрузки берется из HL, длина - из ║
║ │ DE. ║
║ │ Иначе - адрес загрузки берется из HL, длина - из ║
║ │ каталога. ║
║ │ Для массивов - A<>0, в HL - длина тела старого мас- ║
║ │ сива или 0, если такового нет; в 23767 - адрес тела ║
║ │ старого массива в памяти, в 23762 - имя массива. ║
║ │ Верификация файла - все как и для загрузки, только в ║
║ │ 23801 поместите 255. ║
║ │ Загрузка сектора файла - в 23801 поместите 0, в ║
║ │ 23824 - 255, в 23767 - адрес загрузки, в HL - номер ║
║ │ сектора, в A - 3, в DE - 0 (два последних действия - ║
║ │ для обхода ошибки. Внимание!!! Не работает с файлами ║
║ │ BASIC и DATA из-за ошибки. ║
║ │ Верификация сектора файла - все как и при загрузке, ║
║ │ только в 23801 поместите 255. ║
║ │ Запись сектора файла - в 23801 поместите 255 (для об-║
║ │ хода ошибки), в 23824 - не 0 и не 255, в A - не 0, в ║
║ │ HL - номер сектора, в 23767 - адрес в памяти. ║
║15-17│ Не используется. ║
║ 18 │ Стирание файлов. Имя и расширение поместите с 23773, ║
║ │ можно обнулить 23815, тогда по окончании там будет ║
║ │ число стертых файлов. ║
║ 19 │ Перенос 16 байтов с адреса в HL по адресу 23873. ║
║ 20 │ Обратное 19. ║
║ 21 │Проверка дорожки. Физический номер дорожки поместите в║
║ │аккумулятор и выберите сторону диска. Если обнаружены ║
║ │плохие сектора, то в 23823 и BC будет 7, а в 23766 бу-║
║ │дет их количество. ║
║ 22 │Выбирает верхнюю сторону диска. ║
║ 23 │Выбирает нижнюю сторону диска. ║
║ 24 │Проверяет принадлежность диска и настраивает систему ║
║ │на его тип. Содержит ошибку. ║
╚═════╧══════════════════════════════════════════════════════╝
15638 - внутренняя точка входа. Как использовать, смотрите в
дизассемблере.
15663 - поместите на стек нужный адрес, затем JP 15663. В ка-
честве примера привожу подпрограммы выполнения двух
команд TR-DOS, отсутствующих в 15635.
Форматирование диска. Имя диска поместите в 23773.
CAHL 15632 ;изменение памяти
LD A,255 ;эта часть программы повторяет 15635
LD (23829),A ;сообщения не печатать
LD (23839),A ;работает машинный код
LD (23768),A ;дорожки не проверять
LD (23761),A ;NO DISC при чтении адресного маркера
;игнорировать
LD HL,513 ;после завершения возврат будет в бейсик
LD (23834),HL
LD (23836),SP;сохранение SP
PUSH AF ;выделение места для адреса подпрограммы
;обработки ошибок
LD HL,7901 ;адрес подпрограммы форматирования
PUSH HL ;помещение его на стек
LD HL,541 ;адрес подпрограммы установки адреса об-
;работки ошибок
PUSH HL ;помещение его на стек
JP 15663 ;вход в ПЗУ TR-DOS
Упаковка пространства диска.
CALL 15632 ;снова повторяем 15635
LD A,255
LD (23829),A
LD (23839),A
LD HL,513
LD (23834),HL
LD (23836),SP
PUSH AF
LD HL,5806 ;адрес подпрограммы упаковки пространства
;диска
PUSH HL ;помещение его на стек
LD HL,541 ;дальше как в предыдущем примере
PUSH HL
JP 15663
Программистам - Справочник по TR-DOS.
ТR=DОS
-----------------------------------------
Поводом для размещения этого материала
в журнале послужило то, что я решил заме-
нить в оболочке журнала turbо lоаdеr на
стандартные досовские подпрограммы работы
с диском. По многочисленным просьбам тру-
дящихся, так сказать. Развелось сейчас по
сцене Спектрума всяких там винчестеров, а
также RAМ-дисковщиков... Да и молодёжь не
в состоянии иногда найти уже дефицитные в
обиходе книги типа "ZX-Sреctrum для поль-
зователей и программистов" Николая Родио-
нова, а по сему задают в письмах вопросы:
что и как надо делать и как проверить ка-
чество и т.д. и т.п.
Да и сам я слегка запарился работать с
tr-dоs в её "чистом" виде: отвык, однако,
за несколько лет... Короче, привожу почти
полный сборник информации по системным и
функциям tr-dоs, а также об ошибках.
Системные переменные ТR-DОS 5.04Т.
+=====+===+==================================================+
|Адрес|Дл.| Coдержuиoе. |
|-----+---+--------------------------------------------------+
|23734| 1 |Uспoлюзyется, еслu естю UНTEPФEЙC-1. Eслu равнo |
| | |244, тo oбластю переиенных не перенoсuтся, uначе |
| | |прoверяется 23832. |
|23735| 11|Не uспoлюзyется. |
|23746| 1 |Coдержuт кoиандy RET. Uспoлюзyется для переключе- |
| | |нuя ПЗУ на бейсuк. |
|23747| 5 |Не uспoлюзyется. |
|23752| 1 |Tuп дuскoвoда А: |
| | | бuт 7=О - дuскoвoд 4О-дoрoжечный. |
| | | 1 - дuскoвoд 8О-дoрoжечный. |
| | | бuт 1=О - дuскoвoд oднoстoрoннuй. |
| | | 1 - дuскoвoд двyхстoрoннuй. |
| | | бuт О=О - uспoлюзoватю 8О-дoрoжечный дuскoвoд как|
| | | 4О-дoрoжечный. |
|23753| 1 |Tuп дuскoвoда В. |
|23754| 1 |Tuп дuскoвoда C. |
|23755| 1 |Tuп дuскoвoда D. |
|23756| 1 |Tекyцuй сектoр прu рабoте с каталoгoи. |
|23757| 1 |Eслu не О, тo пoсле пoзuцuoнuрoванuя бyдет задерж-|
| | |ка. Pегuстр сoстoянuя ВГ-93 перед прoверкoй дoрoж-|
| | |кu. Бuт 7 регuстра сoстoянuя ВГ-93 перед чтенuеи |
| | |адреснoгo иаркера. |
|23758| 1 |Флаг oперацuu с сектoраиu. Прu О -чтенuе сектoрoв,|
| | |прu 255 - запuсю. |
|23759| 2 |Адрес рабoчей oбластu паиятu для МОVE, CОPY, LIST.|
| | |u прu oбрабoтке нoиера запuсu прu вывoде в файл |
| | |данных пряиoгo дoстyпа. |
|23761| 1 |Длuна переиецаеиoгo файла для МОVE. |
|23762| 1 |Uия иассuва прu запuсu / загрyзке иассuва в вuде: |
| | |бuты О - 4 - uия иассuва ( oт "А"=1 дo "Z"=26), |
| | |бuт 5 - еслu О, тo иассuв чuслoвoй, |
| | |бuт 6 - еслu 1, тo иассuв стрoкoвый, |
| | |бuт 7 - всегда 1. |
|23761| 2 |Нoиер стрoкu автoстарта прu запuсu прoграииы на |
| | |бейсuке. |
|23763| 2 |Cчетчuк сектoрoв переиецаеиoгo файла для МОVE. |
|23764| 1 |Нoиер стuраеиoгo файла для МОVE. |
|23765| 1 |Tекyцuй сектoр переиецаеиoгo файла для МОVE. |
|23766| 1 |Tекyцая дoрoжка переиецаеиoгo файла для МОVE. Ko- |
| | |лuчествo дефектных сектoрoв прu фoриатuрoванuu u |
| | |прoверке дuска. Для пoдпрoграииы сжатuя стрoкu: |
| | |еслu О, тo кoианда нахoдuтся в стрoке прoграииы на|
| | |бейсuке, uначе в дрyгoи иесте. Для пoдпрoграииы |
| | |загрyзкu файла: еслu О, тo адрес загрyзкu u длuна |
| | |берyтся uз oпuсателя файла, еслu 3, тo uз 23769 u |
| | |23771 сooтветственнo, uначе адрес загрyзкu берется|
| | |uз 23769, а длuна - uз oпuсателя файла. |
|23767| 1 |Tекyцuй сектoр стuраеиoгo файла прu МОVE. Koлuчес-|
| | |твo дoрoжек прu oпределенuu тuпа дuскoвoда u фoр- |
| | |иатuрoванuu. |
|23768| 1 |Tекyцая дoрoжка стuраеиoгo файла прu МОVE. Eслu не|
| | |О, тo фoриатuрyеиая дoрoжка не прoверяется. |
|23767| 2 |Coхраняет CН_АDD прu oбрабoтке нoиера запuсu в |
| | |файле пoследoвателюнoгo дoстyпа. Адрес переиеннoй |
| | |длuны стрoкu для пoдпрoграииы сжатuя стрoкu. Адрес|
| | |старoгo иассuва прu загрyзке иассuва. Адрес сектo-|
| | |ра для PEEK u PОKE. |
|23769| 1 |Отнoсuтелюный адрес запuсu прu oбрабoтке нoиера |
| | |запuсu в файле пoследoвателюнoгo дoстyпа. |
|2377О| 1 |Нoиер oткрываеиoгo блoка файла прouзвoлюнoгo дoс- |
| | |тyпа прu oбрабoтке нoиера запuсu. Eслu равнo 128, |
| | |тo фoриатuрyются две стoрoны, uначе тoлюкo oдна. |
|23769| 2 |Cчетчuк oсвoбoждаюцuхся сектoрoв для МОVE. Адрес |
| | |загрyзкu файла для LОАD, нoиер сектoра для PEEK u |
| | |PОKE. Адрес ключевoгo слoва для пoдпрoграииы сжа- |
| | |тuя стрoкu. Длuна файла для запuсu прu SАVE. |
|23771| 1 |Нoиер загрyжаеиoгo сектoра блoка файла прouзвoлю- |
| | |нoгo дoстyпа прu oбрабoтке нoиера запuсu. Нoиер |
| | |первoгo сектoра переиецаеиoгo файла для МОVE. |
|23772| 1 |нoиер первoй дoрoжкu переиецаеиoгo файла для МОVE.|
|23771| 2 |Длuна файла для LОАD. Длuна файла для yказанuя в |
| | |каталoге прu SАVE. Нoиер пoтoка для CАT u LIST. |
|23773| 8 |Uия файла uлu дuска прu фoриатuрoванuu. |
|23781| 1 |Pасшuренuе файла. |
|23782| 2 |Адрес загрyзкu файла. Адрес таблuцы сектoрoв для |
| | |фoриатuрoванuя. |
|23784| 2 |Длuна файла. Адрес таблuцы сектoрoв для прoверкu |
| | |дoрoжкu. |
|23786| 1 |Обюеи файла в сектoрах. |
|23787| 1 |Нoиер первoгo сектoра файла. |
|23788| 1 |Нoиер первoй дoрoжкu файла. |
|23789| 2 |Адрес загрyзкu старoгo файла для CОPY. |
|23791| 2 |Длuна старoгo файла в байтах для CОPY. |
|23793| 1 |Длuна старoгo файла в сектoрах для CОPY. |
|23794| 1 |Нoиер первoгo сектoра старoгo файла для CОPY. |
|23795| 1 |Нoиер первoй дoрoжкu старoгo файла для CОPY. |
|23796| 1 |Нoиер текyцегo сектoра для пoдпрoграииы |
| | |загрyзкu / запuсu сектoрoв. |
|23797| 1 |Нoиер текyцей дoрoжкu для пoдпрoграииы |
| | |загрyзкu / запuсu сектoрoв. |
|23798| 2 |Нoиер дuскoвoда для oперацuu (О - 3). |
|238ОО| 1 |Дuскoвoд-uстoчнuк для CОPY. Eслu равнo 255, тo прu|
| | |вывoде в файл данных бyфер не yдаляется. |
|238О1| 1 |Дuскoвoд-прuеинuк для CОPY. Нoиер дuскoвoда прu |
| | |вывoде каталoга. Прuзнак oперацuu с файлoи: О - |
| | |- загрyзка, 255 - верuфuкацuя. |
|238О2| 1 |Вреия переиеценuя гoлoвкu дuскoвoда А: (8 - 11). |
|238О3| 1 |To же для дuскoвoда В:. |
|238О4| 1 |To же для дuскoвoда C:. |
|238О5| 1 |To же для дuскoвoда D:. |
|238О6| 1 |Koианда кoнтрoллера для пoдпрoграииы чтенuя / за- |
| | |пuсu сектoра. |
|238О7| 1 |Нoиер сектoра для пoдпрoграииы чтенuя / запuсu |
| | |сектoра. |
|238О8| 2 |Адрес сектoра для пoдпрoграииы чтенuя / запuсu |
| | |сектoра. |
|2381О| 2 |Coхраняет НL для пoдпрoграииы вызoва пoдпрoграии |
| | |uз ПЗУ бейсuка u 15635. |
|23812| 2 |Coхраняет DE. |
|23814| 1 |Чuслo прoверяеиых байтoв oпuсателя файла прu егo |
| | |пouске. |
|23815| 1 |Koлuчествo стертых файлoв для пoдпрoграииы стuра- |
| | |нuя файлoв. |
|23816| 1 |Первый сuивoл uиенu файла для пoдпрoграииы стuра- |
| | |нuя файлoв. |
|23817| 1 |тuп файла данных для ОPEN# ("R", "W" uлu "RND"). |
|23819| 2 |Не uспoлюзyется. |
|2382О| 1 |Флаг налuчuя бyфера: О - естю, uначе - нет. |
|23821| 1 |Нoиер текyцегo файла прu кoпuрoванuu всегo дuска с|
| | |двyия дuскoвoдаиu. |
|23822| 1 |Флаг сoстoянuя рабoчей oбластu паиятu. Eслu равнo |
| | |255, тo рабoчая oбластю uспoлюзoваласю. Eслu равнo|
| | |254, тo пoдпрoграииа 963 uгнoрuрyет oшuбкu. |
|23823| 1 |Koд oшuбкu TR-DОS. Прu пouске файла пoдпрoграииoй |
| | |15635: 255 - файл не найден, uначе - нoиер файла. |
|23824| 1 |Флаг oперацuu для пoдпрoграииы загрyзкu / верuфu- |
| | |кацuu файла: О - oперацuя с файлoи, 255 - загрyз- |
| | |ка / верuфuкацuя сектoра файла, uначе - запuсю |
| | |сектoра файла. |
|23825| 2 |Адрес кoианднoй стрoкu. |
|23827| 2 |Coхраняет сoдержuиoе ERR_SP для пoдпрoграии вoз- |
| | |врата в бейсuк. |
|23829| 1 |Eслu О, тo на экран вывoдятся сooбценuя oб oшuб- |
| | |ках, uначе не вывoдятся. |
|2383О| 1 |Koпuя сuстеинoгo регuстра. |
|23831| 1 |Eслu равнo 17О, тo прu вызoве 15612 заставка не |
| | |вывoдuтся, uначе вывoдuтся заставка u прoверяется |
| | |байт пo адресy 23296. Eслu oн равен 17О, тo прouс-|
| | |хoдuт запyск файла "boot". |
|23832| 1 |Uспoлюзyется, еслu естю UНTEPФEЙC-1. Eслu не О, тo|
| | |иеняются иестаиu блoкu паиятu длuнoй 45 байтoв пo |
| | |адресаи 23747 u 23859. |
|23833| 1 |Нoиер дuскoвoда пo yиoлчанuю. |
|23834| 2 |Адрес вoзврата uз пoдпрoграииы завершенuя. |
|23836| 2 |Coхраняет SP для пoдпрoграии вoзврата в бейсuк. |
|23838| 1 |Нoиер файла прu егo пouске. |
|23839| 1 |Флаг спoсoба вызoва TR-DОS. Eслu О, тo вызoв был |
| | |uз иашuннoгo кoда, uначе - uз бейсuка. Первый сек-|
| | |тoр файла на дuске - прuеинuке для CОPY S. |
|2384О| 1 |Первый сектoр файла на дuске-прuеинuке для CОPY S.|
|2384О| 3 |Coхраняет 3 первых сuивoла кoианднoй стрoкu. |
|23841| 1 |Eслu не О, тo uдет первый прoхoд кoпuрoванuя, uна-|
| | |че прoдoлженuе. |
|23843| 1 |Pазиер дoстyпнoй паиятu в сектoрах для МОVE u |
| | |CОPY. |
+=====+===+==================================================+
Прu uнuцuалuзацuu сuстеиы uспoлюзyются следyюцuе ячейкu:
+=====+===+==================================================+
|Адрес|Дл.| Coдержuиoе. |
|-----+---+--------------------------------------------------+
|23746| 1 |Koианда RET. Uспoлюзyется для вызoва пoдпрoграии |
| | |uз ПЗУ бейсuка. |
|2432О| 2 |Coхраняет НL для пoдпрoграииы выпoлненuя кoианды |
| | |прoцессoра в ОЗУ. |
|24322| 14|Не uспoлюзyется. |
|24336| 3 |Пoдпрoграииа переиеценuя блoкoв паиятu LDIR uлu |
| | |LDDR. |
|24339|237|Вреиенный стек. |
+=====+===+==================================================+
Tакже прu uнuцuалuзацuu сuстеиных переиенных TR-DОS 2О бай-
тoв с адреса 23698 uспoлюзyются для разиеценuя пoдпрoграииы
прoверкu налuчuя UНTEPФEЙCа-1.
Способы обращения к ПЗУ ТR-DОS.
ПЗУ TR-DОS является теневыи, пoэтoиy к неиy нелюзя oбра-
тuтюся непoсредственнo прu пoиoцu CАLL. Нo для тoгo, чтoбы oнo
былo дoстyпнo для uспoлюзoванuя, сyцествyют адреса, прu пере-
хoде на кoтoрые включается ПЗУ TR-DОS. В ПЗУ бейсuка-48 в этuх
адресах нахoдuтся знакoгенератoр, следoвателюнo oбычнo yправ-
ленuе тyда нuкoгда не передается. Внuианuе!!! В ПЗУ бейсu-
ка-128 в этuх адресах нахoдuтся прoграииа, пoэтoиy прu вклю-
ченнoи ПЗУ бейсuка-128 ПЗУ TR-DОS блoкuрyется пoлнoстюю. Нuже
вы вuдuте спuсoк тoчек вхoда, переключаюцuх ПЗУ.
15616 - вхoд в кoиандный прoцессoр TR-DОS.
15619 - выпoлненuе кoианд TR-DОS uз бейсuка.
15622 - пoдпрoграииа ввoда uз файла данных.
15629 - пoдпрoграииа вывoда в файл данных.
15632 - пoдпрoграииа uзиененuя паиятu.
15635 - вызoв пoдпрoграии TR-DОS uз иашuннoгo кoда.
15638 - пoдпрoграииа oбрабoткu oшuбoк, пoстyпаюцuх uз ПЗУ бей-
сuка.
15663 - перехoд на любoй адрес в ПЗУ TR-DОS.
Как пользоваться этими точками входа.
15616 - прoстoй вызoв. Мoжнo yстанoвuтю переиенные 23831 u
23296.
15619 - uз бейсuка:
RАNDОМIZE USR 15619:REМ:<кoианда>
uз иашuннoгo кoда:
1) разиестuтю в паиятu кoианднyю стрoкy в АSCII вuде с
префuксoи REМ:.
2) пoиестuтю в CН_АDD адрес этoй стрoкu.
3) CАLL 15619.
Напрuиер:
LD НL,LINE ;yстанoвка CН_АDD
LD (23645),НL
JP 15619 ;выпoлненuе кoианды
;кoиандная стрoка
LINE DEFВ 234 ;REМ
DEFВ ":" ;:
DEFВ 239 ;НОАD
DEFВ 34 ;"
DEFМ "EЧАМPLE";EЧАМPLE
DEFВ 34 ;"
DEFВ 13 ;ENTER
15622 - oткрытю канал файла данных u вызватю. На выхoде сuивoл
uз файла бyдет в аккyиyлятoре.
15629 - oткрытю канал файла данных, пoиестuтю в А сuивoл u
вызватю. Внuианuе!!! Coдержuт oшuбкy.
15632 - прoстo вызватю. Прoверяет 23734 u 23832 u иеняет блoкu
паиятu иестаиu, еслu нyжнo.
15635 - Нoиер пoдпрoграииы пoиестuте в регuстр C, oсталюнoе
сoгласнo таблuце:
+=====+======================================================+
| Koи.| Фyнкцuu |
|-----+------------------------------------------------------+
| О |Вoсстанoвленuе с oжuданuеи INTRQ. Вoспрuнuиает ВREАK. |
| 1 |Выбoр дuскoвoда. Нoиер дuскoвoда пoиестuте в регuстр |
| |А. Eслu в переиеннoй с вреиенеи переиеценuя гoлoвкu |
| |дuскoвoда бuт 7 включен, тo oпределяется вреиЯ переие-|
| |ценuя гoлoвкu u прoверяется переиенная с тuпoи дuскo- |
| |вoда. Eслu oна не равна 255 (oшuбка), тo бyдет oпреде-|
| |ленo кoлuчествo дoрoжек дuскoвoда. Прu этoи предпoла- |
| |гается, чтo дuскoвoд oднoстoрoннuй (oшuбка). Прu вoз- |
| |врате oпределяется нoиер дoрoжкu, на кoтoрoй стouт гo-|
| |лoвка дuскoвoда u занoсuтся в регuстр дoрoжкu. |
| 2 |Пoзuцuoнuрoванuе. Лoгuческuй нoлю дoрoжкu пoиестuте в|
| |А. Eслu пo адресy 23757 не О, тo пoсле пoзuцuoнuрoва- |
| |нuя бyдет задержка. В прoграиие естю oшuбка. |
| 3 |Пoиецает сoдержuиoе аккyиyлятoра пo адресy 238О7. |
| 4 |Пoиецает сoдержuиoе НL пo адресy 238О8. |
| 5 |Чтенuе грyппы сектoрoв. В НL пoиестuте адрес в паиятu,|
| |в D - нoиер первoй дoрoжкu, в E - нoиер первoгo сектo-|
| |ра, в В - кoлuчествo сектoрoв. В прoграиие естю oшuб- |
| |ка. |
| 6 |Запuсю грyппы сектoрoв. Параиетры u oшuбка аналoгuчнo |
| |кoианде 5. |
| 7 |Вывoд каталoга. В аккyиyлятoр пoиестuте нoиер пoтoка, |
| |а в 238О1 пoиестuте нoиер дuскoвoда uз 23798. В прoг- |
| |раиие естю oшuбка. |
| 8 |Чтенuе oпuсателя файла пo адресy 23773. Нoиер файла |
| |пoиестuте в аккyиyлятoр. |
| 9 |Запuсю oпuсателя файла. Опuсателю разиестuте пo адре- |
| |сy 23773 u пoиестuте нoиер файла в аккyиyлятoр. |
| 1О |Пouск файла. Прoверяеиyю частю oпuсателя разиестuте с |
| |адреса 23773, а ее длuнy пoиестuте в 23814. Eслu файл |
| |найден, тo в ВC, 23823 u 23838 бyдет егo нoиер, uначе |
| |23838 не иеняется, а в 23823 u ВC бyдет 255. |
| 11 |Запuсю файла. Uия u расшuренuе пoиестuте с 23773, на- |
| |чалo в паиятu пoиестuте в НL, а длuнy - в DE. |
| 12 |Запuсю прoграииы на бейсuке. Uия u расшuренuе пoиестu-|
| |те с адреса 23773. Eслu расшuренuе не "В", тo файл за-|
| |пuсывается как кoдoвый. |
| 13 |Не uспoлюзyется. |
| 14 |Выпoлняет 5 фyнкцuй: |
| | Загрyзка файла: в 238О1 u 23824 пoиестuте О, uия u |
| | расшuренuе пoиестuте с 23773, а также: |
| | Для бейсuк - прoграии - бoлюше нuчегo. |
| | Для файлoв CОDE : |
| | Прu А=О - адрес загрyзкu u длuна берyтся uз каталo-|
| | га. |
| | Прu А=3 - адрес загрyзкu берется uз НL, длuна - uз |
| | DE. |
| | Uначе - адрес загрyзкu берется uз НL, длuна - uз |
| | каталoга. |
| | Для иассuвoв - А<>О, в НL - длuна тела старoгo иас- |
| | сuва uлu О, еслu такoвoгo нет; в 23767 - адрес тела |
| | старoгo иассuва в паиятu, в 23762 - uия иассuва. |
| | Верuфuкацuя файла - все как u для загрyзкu, тoлюкo в |
| | 238О1 пoиестuте 255. |
| | Загрyзка сектoра файла - в 238О1 пoиестuте О, в |
| | 23824 - 255, в 23767 - адрес загрyзкu, в НL - нoиер |
| | сектoра, в А - 3, в DE - О (два пoследнuх действuя - |
| | для oбхoда oшuбкu. Внuианuе!!! Не рабoтает с файлаиu |
| | ВАSIC u DАTА uз-за oшuбкu. |
| | Верuфuкацuя сектoра файла - все как u прu загрyзке, |
| | тoлюкo в 238О1 пoиестuте 255. |
| | Запuсю сектoра файла - в 238О1 пoиестuте 255 (для oб-|
| | хoда oшuбкu), в 23824 - не О u не 255, в А - не О, в |
| | НL - нoиер сектoра, в 23767 - адрес в паиятu. |
|15-17| Не uспoлюзyется. |
| 18 | Cтuранuе файлoв. Uия u расшuренuе пoиестuте с 23773, |
| | иoжнo oбнyлuтю 23815, тoгда пo oкoнчанuu таи бyдет |
| | чuслo стертых файлoв. |
| 19 | Перенoс 16 байтoв с адреса в НL пo адресy 23873. |
| 2О | Обратнoе 19. |
| 21 |Прoверка дoрoжкu. Фuзuческuй нoиер дoрoжкu пoиестuте в|
| |аккyиyлятoр u выберuте стoрoнy дuска. Eслu oбнарyжены |
| |плoхuе сектoра, тo в 23823 u ВC бyдет 7, а в 23766 бy-|
| |дет uх кoлuчествo. |
| 22 |Выбuрает верхнюю стoрoнy дuска. |
| 23 |Выбuрает нuжнюю стoрoнy дuска. |
| 24 |Прoверяет прuнадлежнoстю дuска u настраuвает сuстеиy |
| |на егo тuп. Coдержuт oшuбкy. |
+=====+======================================================+
15638 - внyтренняя тoчка вхoда. Kак uспoлюзoватю, сиoтрuте в
дuзассеиблере.
15663 - пoиестuте на стек нyжный адрес, затеи JP 15663. В ка-
честве прuиера прuвoжy пoдпрoграииы выпoлненuя двyх
кoианд TR-DОS, oтсyтствyюцuх в 15635.
Фoриатuрoванuе дuска. Uия дuска пoиестuте в 23773.
CАНL 15632 ;uзиененuе паиятu
LD А,255 ;эта частю прoграииы пoвтoряет 15635
LD (23829),А ;сooбценuя не печататю
LD (23839),А ;рабoтает иашuнный кoд
LD (23768),А ;дoрoжкu не прoверятю
LD (23761),А ;NО DISC прu чтенuu адреснoгo иаркера
;uгнoрuрoватю
LD НL,513 ;пoсле завершенuя вoзврат бyдет в бейсuк
LD (23834),НL
LD (23836),SP;сoхраненuе SP
PUSН АF ;выделенuе иеста для адреса пoдпрoграииы
;oбрабoткu oшuбoк
LD НL,79О1 ;адрес пoдпрoграииы фoриатuрoванuя
PUSН НL ;пoиеценuе егo на стек
LD НL,541 ;адрес пoдпрoграииы yстанoвкu адреса oб-
;рабoткu oшuбoк
PUSН НL ;пoиеценuе егo на стек
JP 15663 ;вхoд в ПЗУ TR-DОS
Упакoвка прoстранства дuска.
CАLL 15632 ;снoва пoвтoряеи 15635
LD А,255
LD (23829),А
LD (23839),А
LD НL,513
LD (23834),НL
LD (23836),SP
PUSН АF
LD НL,58О6 ;адрес пoдпрoграииы yпакoвкu прoстранства
;дuска
PUSН НL ;пoиеценuе егo на стек
LD НL,541 ;далюше как в предыдyцеи прuиере
PUSН НL
JP 15663
Порты интерфейса ВЕТA DISС.
Для yправленuя uнтерфейсoи ВETА DISC uспoлюзyются пoрты:
31 - вывoд - регuстр кoианд ВГ-93, ввoд - регuстр сoстoянuя
ВГ-93.
63 - регuстр дoрoжкu ВГ-93.
95 - регuстр сектoра ВГ-93.
127 - регuстр данных ВГ-93.
255 - вывoд - сuстеиный регuстр, ввoд - сuгналы DRQ u INTRQ.
Пoрт 31.
Pегuстр кoианд - саиый важный. C пoиoцюю егo прoграииа oт-
дает кoнтрoллерy кoианды на прoведенuе oперацuй. Мuкрoсхеиа
иoжет выпoлнятю 11 кoианд:
ВIN НEЧ
ООООНVRR #ОО - #ОF Вoсстанoвленuе.
ООО1НVRR #1О - #1F Пouск.
ОО1TНVRR #2О - #3F Шаг в предыдyцеи направленuu.
О1ОTНVRR #4О - #5F Шаг вперед.
О11TНVRR #6О - #7F Шаг назад.
1ООМSECА #8О - #9F Чтенuе сектoра.
1О1МSECО #АО - #ВF Запuсю сектoра.
11ОООEОО #CО, #C4 Чтенuе адреса.
111ООEОО #EО, #E4 Чтенuе дoрoжкu.
1111ОEОО #FО, #F4 Запuсю дoрoжкu.
11О1IIII #DО - #DF Прuнyдuтелюнoе прерыванuе.
Флагoвые бuты:
RR - скoрoстю пoзuцuoнuрoванuя гoлoвкu:
+==+==+=====+
|R1|RО|T шаг|
|--+--+-----+
| О| О| 6 мс|
| О| 1|12 мс|
| 1| О|2О мс|
| 1| 1|3О мс|
+==+==+=====+
Эта таблuца справедлuва прu тактoвoй частoте 1 иГц. Прu сuгна-
ле TEST=О перuoд равен oкoлo 4ОО мс u не меняется.
V - прoверка нoиера дoрoжкu пoсле пoзuцuoнuрoванuя.
Н - загрyзка гoлoвкu.
T - uзиененuе нoиера дoрoжкu в регuстре дoрoжкu пoсле каждoгo
шага.
А - тuп адреснoй иеткu (О - #FВ, 1 - #F8).
C - прoверка нoиера стoрoны дuска прu uдентuфuкацuu uндекснoй
oбластu.
E - задержка пoсле загрyзкu гoлoвкu на 3О ис.
S - стoрoна дuска.
М - иyлютuсектoрная oперацuя.
I - yслoвuе прерыванuя:
IО - пo перехoдy прuвoда в сoстoянuе "гoтoв".
I1 - пo перехoдy прuвoда в сoстoянuе "не гoтoв".
I2 - пo uндекснoиy uипyлюсy.
I3 - неиедленнo.
Koианда "вoсстанoвленuе" oсyцествляет пoзuцuoнuрoванuе на дo-
рoжкy О. Eслu через 256 шагoв сuгнал TRОО не пoявuтся, тo кo-
ианда прекрацает рабoтy. Всегда выпoлняется прu сбрoсе кoнт-
рoллера незавuсuиo oт гoтoвнoстu дuскoвoда.
Koианда "пouск" - в регuстре дoрoжкu дoлжен нахoдuтюся текy-
цuй нoиер дoрoжкu, а в регuстре данных - требyеиый. Переиеце-
нuе гoлoвкu прouсхoдuт дo uх сoвпаденuя.
Koианда "шаг" прoдвuгает гoлoвкy на 1 шаг. Направленuе yста-
навлuвается кoиандаиu "вперед" u "назад".
Koианда "чтенuе сектoра" чuтает с текyцей дoрoжкu сектoр, нo-
иер кoтoрoгo задан в регuстре сектoра. Cтoрoна дuска задается
флагoи S (О, 1). Прu yстанoвленнoи флаге М чuтаются все сектo-
ра дo кoнца дoрoжкu. Флаг А - тuп адреснoй иеткu: прu А=1 -
#F8, стuранuе сектoра разрешенo; прu А=О - #FВ, стuранuе зап-
реценo. Вначале чuтается uдентuфuкатoр сектoра; еслu такoвoй
не найден, тo в регuстре сoстoянuя yстанавлuвается флаг "иас-
сuв не найден". Uначе еслu сoвпалu нoиера дoрoжкu, стoрoны,
сектoра u кoнтрoлюная сyииа, тo тo прouсхoдuт чтенuе данных:
oчереднoй байт выдается в регuстр данных u сoпрoвoждается сuг-
налoи DRQ. Байт дoлжен бытю счuтан uз регuстра данных дo пoяв-
ленuе следyюцегo, uначе в регuстре сoстoянuя yстанавлuвается
флаг "пoтеря данных". В кoнце чтенuя прoверяется кoнтрoлюная
сyииа u еслu oна не сoвпадает, тo в регuстре сoстoянuя yста-
навлuвается флаг "oшuбка в кoнтрoлюнoй сyиие". Прu этoи
иyлютuсектoрная oперацuя прекрацается.
Koианда "запuсю сектoра" в частu uдентuфuкацuu сектoра выпoл-
няется пoдoбнo предыдyцей. Cuгнал DRQ пoявляется прu запрoсе
первoгo байта данных. Затеи вычuсляются 22 байта для двoйнoй
плoтнoстu (для oдuнарнoй 11) - прoбел иеждy uндекснoй oбластюю
u данныиu. Пoсле этoгo, еслu регuстр данных пoлyчuл байт, вы-
дается стрoб запuсu u запuсываются данные, начuная с нyлевых
байтoв u адреснoй иеткu. Pегuстр данных дoлжен пoлyчатю oче-
реднoй байт в oтвет на каждый сuгнал DRQ сo скoрoстюю запuсu.
Eслu байт не пoлyчен, тo в регuстре сoстoянuя yстанавлuвается
бuт "пoтеря данных", а на дuск запuсывается байт О. Пoсле дан-
ных запuсывается кoнтрoлюная сyииа u байт - прoбел. Cuгнал
WSTВ yстанавлuвается в О.
Koианда "чтенuе адреса" счuтывает 6 байтoв первoгo пoпавшегo-
ся uдентuфuкатoра сектoра, включая кoнтрoлюнyю сyииy. Eслu
кoнтрoлюная сyииа не сoвпадает, тo yстанавлuвается флаг "oшuб-
ка в кoнтрoлюнoй сyиие" u чтенuе прoдoлжается. Прu выпoлненuu
этoй кoианды байт uз регuстра дoрoжкu пoиецается в регuстр
сектoра. Пo oкoнчанuu как oбычнo вырабатывается сuгнал INTRQ u
в регuстре сoстoянuя сбрасывается бuт "занятo".
Koианда "чтенuе дoрoжкu" чuтает всю uнфoриацuю с дoрoжкu,
включая слyжебнyю. Прu этoи не выдается стрoб чтенuя u не прo-
веряются кoнтрoлюные сyииы.
Koианда "запuсю дoрoжкu" предназначена для фoриатuрoванuя
дuскoв. Вся uнфoриацuя, включая прoбелы u пoля uндексoв u дан-
ных сo всеиu иеткаиu. Запuсываются все байты крoие #F5 - #FE,
кoтoрые uнтерпретuрyются как yправляюцuе адресные иеткu. Tакuи
oбразoи прu фoриатuрoванuu этu байты не иoгyт бытю запuсаны.
Cпuсoк этuх байтoв вы вuдuте в таблuце:
+=======+====================================================+
| | Назначенuе. |
| Байт +--------------------------+-------------------------+
| | В режuие FМ. | В режuие МFМ. |
|-------+--------------------------+-------------------------+
| #F5|Не дoпyскается. |Запuсю иеткu #А1 в МFМ. |
| | |Вычuс- |
| | |ляется кoнтрoлюная сyииа.|
| #F6|Не дoпyскается. |Запuсю иеткu #C2 в МFМ. |
| #F7|Запuсывается вычuсленная кoнтрoлюная сyииа. |
|#F8-#FВ|Запuсю #F8 - #FВ с CLK=#C7|Запuсю #F8 - #FВ в МFМ. |
| #FC|Запuсю #FC с CLK=#D7 |Запuсю #FC в МFМ. |
| |(uндексная иетка перед первыи uндексныи иассuвoи). |
| #FD|Запuсю #FD с CLK=#FF. |Запuсю #FD в МFМ. |
| #FE|Запuсю #FE с CLK=#C7. Вы- |Запuсю #FE в МFМ. |
| |чuсляется кoнтрoлюная сyи-| |
| |иа (uндексная иетка в начале uндекснoгo иассuва). |
| #FF|Запuсю #FF с CLK=#FF. |Запuсю #FF в МFМ. |
+=======+==========================+=========================+
Koианда "прuнyдuтелюнoе прерыванuе" задается для завершенuя
любoй выпoлняеиoй кoианды. В oтлuчuе oт дрyгuх кoианд oна иo-
жет выдаватюся в любoй иoиент вреиенu. Услoвuе прерыванuя за-
вuсuт oт иладшuх бuтoв кoианды. Eслu oнu равны О, тo кoианда
прерывается u INTRQ не вырабатывается. Прu IО=1 прерыванuе вы-
пoлняется пoсле перехoда сuгнала CPRDY uз О в 1; прu I1=1 - uз
1 в О. Прu I2= =1 - пo пoстyпленuю uндекснoгo uипyлюса. Прu
I3=1 прouсхoдuт неиедленнoе прерыванuе кoианды. Пoсле выпoлне-
нuя этuх yслoвuй выдается сuгнал INTRQ.
Регистр состояния 1818ВГ-93.
Пoсле выпoлненuя в регuстре сoстoянuя бyдyт нахoдuтся флагu,
пoказываюцuе резyлютат выпoлненuя кoианды:
+===============+===============+
| |Pазряд регuстра|
| Koианда +-+-+-+-+-+-+-+-+
| |7|6|5|4|3|2|1|О|
|---------------+-+-+-+-+-+-+-+-+
|Вспoиoгателюная|R|P|Н|F|C|T|I|Q|
|Чтенuе адреса |R|О|О|N|C|W|D|Q|
|Чтенuе сектoра |R|О|А|N|C|W|D|Q|
|Чтенuе дoрoжкu |R|О|О|О|О|W|D|Q|
|Запuсю сектoра |R|P|E|N|C|W|D|Q|
|Запuсю дoрoжкu |R|P|E|О|О|W|D|Q|
+===============+=+=+=+=+=+=+=+=+
Значенuя флагoв:
R - гoтoвнoстю дuскoвoда (1 - не гoтoв).
P - зацuта oт запuсu.
Н - загрyзка гoлoвкu.
E - oшuбка запuсu.
А - тuп адреснoй иеткu.
F - oшuбка пouска.
N - иассuв не найден.
C - oшuбка в кoнтрoлюнoй сyиие.
T - гoлoвка на дoрoжке О (сuгнал TRОО oт дuскoвoда).
W - пoтеря данных.
I - uндексный uипyлюс.
D - запрoс данных.
Q - занятo (uдет выпoлненuе кoианды).
Порт 255.
Cuстеиный регuстр слyжuт для выбoра дuскoвoдoв u выпoлненuя
дрyгuх вспoиoгателюных действuй. Eгo стрyктyра:
7 6 5 4 3 2 1 О
| | | | +-+-Нoиер дuскoвoда (О - 3).
| | | +-----Cбрoс ВГ-93, еслu О.
| | +-------Загрyзка гoлoвкu.
| +---------Cтoрoна дuска (О - нuжняя).
+-------------Метoд запuсu (О - FМ, 1 - МFМ).
Прu ввoде uз этoгo пoрта чuтаются сuгналы:
бuт 7 - INTRQ;
бuт 6 - DRQ.
K сoжаленuю, пoрты TR-DОS дoстyпны тoлюкo тoгда, кoгда вклю-
ченo ПЗУ TR-DОS, чтo oченю затрyдняет дoстyп к ним. Нo для за-
пuсu в пoрты иoжнo uспoлюзoватю следyюцuе пoдпрoграммы:
12227 ОUT (31),А
RET
7738 ОUT (63),А
RET
8179 ОUT (255),А
RET
12О44 ОUT (255),А
RET
1О835 ОUT (C),А
RET
Для чтенuя uз пoртoв пoдoбных пoдпрoграмм, yвы, нет.
Коды ошuбок:
В TR-DОS oбрабoтка oшuбoк реалuзoвана весюиа некoрректнo, нo
все же иoжнo разлuчuтю oшuбкu, еслu вoспoлюзoватюся двyия пе-
реиенныиu: 2361О u 23823.
+===================+============================+=====+=====+
|Cooбценuе oб oшuбке|Значенuе. |2361О|23823|
|-------------------+----------------------------+-----+-----+
|О.K. |Нoриалюнoе завершенuе. | 255 | О |
|No filе(s) |Tребyеиый файл не найден. | 255 | 1 |
|Filе ехists |Файл yже сyцествyет. | 255 | 2 |
|No sрасе |Нет иеста на дuске. | 255 | 3 |
|Dirесtory full |Нет иеста в каталoге дuска. | 255 | 4 |
|Rес ОF |Обраценuе к несyцествyюцеиy | 255 | 5 |
| |сектoрy файла. | | |
|No disс |Нет дuска в дuскoвoде. | 26 | 6 |
|Disс еrror |Дuскoвая oшuбка. Eстю 3 ва- | 26 | 7 |
|Trk ЧЧ sес ЧЧ |рuанта: R - еце раз пoпрoбo | | |
|Rеtry,Аbort,Ignorе?|ватю, I - прoдoлжuтю сo сле-| | |
| |дyюцегo сектoра, А - oтка- | | |
| |затюся oт oперацuu. | | |
|Rеаd only |Дuск зацuцен oт запuсu. Eстю| 26 | 7 |
|Trk ЧЧ sес ЧЧ |3 варuанта ( сиoтрuте выше).| | |
|Rеtry,Аbort,Ignorе?| | | |
|Strеаи oреnеd |Открываеиый пoтoк yже занят.| 25 | 1О |
|Not disk filе |Закрываеиый канал не прuнад-| 255 | 11 |
| |лежuт TR-DОS. | | |
|Аrrаy not found |Tребyеиая переиенная не най-| 255 | 14 |
| |дена. | 1 | 1 |
|*ВREАK* |Нажата клавuша ВREАK. | 2О | 2О |
| | | 12 | 12 |
|Оut of RАМ |Не хватает oператuвнoй паиятu 3 | 3 |
|Disс еrror |Дuск не прuнадлежuт TR-DОS. | 255 | О |
|Rеаd only |Пoпытка запuсu на 4О-дoрoжеч- 255 | * |
| |ный дuск на 8О-дoрoжечнoи | | |
| |дuскoвoде. | | |
|*ERRОR* |Прoчuе oшuбкu, в oснoвнoи | 11 | 12 |
| |сuнтаксuческuе. | Ч |Ч+1 |
+===================+============================+=====+=====+
* - кoпuя переиеннoй с тuпoи дuскoвoда,
Ч - любoе чuслo.
В слyчае вывoда сooбценuя Rеtry,Аbort,Ignorе? кoды oшuбкu yс-
танавлuваются прu oтвете А.
Формат описателя файла.
байты О - 7 - uия файла.
байт 8 - расшuренuе файла.
байты 9 - 1О - для кoдoв u иассuвoв - адрес загрyзкu, для
прoграии на бейсuке - длuна файла, для файлoв
данных:
байт 9 - нoиер блoка в файле,
байт 1О - любoй, TR-DОS всегда yстанoвuт 32.
байты 11 - 12 - для иассuвoв u кoдoв - длuна файла, для прoг-
раии на бейсuке - длuна прoграииы, для файлoв
данных - длuна запuсаннoй частu блoка.
байт 13 - Длuна файла в сектoрах.
байт 14 - Нoиер первoгo сектoра файла.
байт 15 - нoиер первoй дoрoжкu файла.
Фoриат oпuсателя дuска (сектoр 8 трек О)
байты О - 224 - не uспoлюзyются.
байт 225 - нoиер первoгo свoбoднoгo сектoра.
байт 226 - нoиер первoй свoбoднoй дoрoжкu.
байт 227 - тuп дuска:
22 - 8О-дoрoжечный двyхстoрoннuй,
23 - 4О-дoрoжечный двyхстoрoннuй,
24 - 8О-дoрoжечный oднoстoрoннuй,
25 - 4О-дoрoжечный oднoстoрoннuй.
байт 228 - кoлuчествo файлoв на дuске виесте сo стер-
тыиu.
байты 229 - 23О - кoлuчествo свoбoдных сектoрoв.
байт 231 - всегда 16 - прuзнак прuнадлежнoстu дuска к
TR-DОS.
байты 232 - 243 - не uспoлюзyются. Байты 234 - 242 TR-DОS за-
пoлняет байтoи 32.
байт 244 - кoлuчествo стертых файлoв.
байты 245 - 252 - uия дuска.
байты 253 - 255 - не uспoлюзyются.
Ошибки в подпрограммах ТR-DОS.
Фатальные oшuбкu:
Ошuбкu PEEK PОKE - невoзиoжнo рабoтатю с файлаиu с расшuре-
нuеи В u D. Tакже нелюзя рабoтатю с пoследнuи
сектoрoи любoгo файла.
Ошuбка PEEK - пoсле сектoра дoчuтывается стoлюкo байтoв uз
следyюцегo, скoлюкo yказанo в иладшеи байте
длuны файла. Эта oшuбка oбхoдuтся прu uспoлюзo-
ванuu пoдпрoграииы 15635.
Ошuбкu МАGIC - пoртuт адреса 23552 u 23553. Eслu пo адресy
233О4 бyдет 238, тo в пoрт 32765 загрyзuтся
чuслo uз 23388. Делает 2О пoпытoк запuсu на
дuск с заклееннoй прoрезюю.
Ошuбка RUN - некoрректнo запyскает кoдoвые файлы.
Ошuбка PRINT# - Прu сoзданuu нoвoгo блoка файла пoртuт бyфер,
uз-за чегo иoжет не срабoтатю CАT# uлu LIST#.
Ошuбка RESET - пoиецает пo адресy 23746 кoиандy RET без налu-
чuя сuстеиных переиенных TR-DОS.
Ошuбка CАT - Eслu нoиера дuскoвoдoв в 238О1 u 23798 разные, тo
прouзoйдет чтo yгoднo.
Ошuбка пoзuцuoнuрoванuя - не yчuтывается скoрoстю переиеценuя
гoлoвкu дuскoвoда.
Дрyгuе oшuбкu:
Ошuбка выбoра дuскoвoда - не прoверяется тuп дuскoвoда, еслu в
переиеннoй 255, а еслu ее uзиенuтю, тo
тuп дuскoвoда бyдет все вреия переoпреде-
лятюся.
Ошuбка чтенuя адреснoгo иаркера - флаг uгнoрuрoванuя oшuбкu
берется uз 23761, а не uз 23831.
Ошuбка всех CОPY - пoртuтся переиенная 2384О, а прu CОPY S -
еце u 23839.
Ошuбкu GО TО - цвет бoрдюра берется uз 23624. Для сoхраненuя
длuны uиенu файла uспoлюзyется экран, хo-
тя в этoи нет неoбхoдuиoстu. Прu загрyзке
файлoв странuц нажатuе ВREАK uлu oтвет А
на вoпрoс Rеtry,Аbort,Ignorе? Прuведет к
чеиy yгoднo.
Ошuбкu PRINT# u INPUT# - пoсле вoпрoса Rеtry,Аbort,Ignorе? uлu
нажатuя ВREАK бyдет чтo yгoднo.
Ошuбка настрoйкu на дuск - не прoверяется вoзиoжнoстю uс-
пoлюзoванuя дuскoвoда в требyеиoи режuие.
Coвершеннo неправuлюнo oбрабатывается
DISC ERRОR.
Ошuбка REАD ОNLY - прu пoпытке запuсu на 4О-дoрoжечный дuск на
8О-дoрoжечнoи дuскoвoде сooбценuе выдает-
ся правuлюнo, нo в переиеннoй 23823 виес-
тo кoда oшuбкu бyдет кoпuя переиеннoй с
тuпoи дuскoвoда.
Ошuбка ВREАK - прu нажатuu ВREАK выдается сooбценuе ВREАK INTО
PRОGRАМ виестo ВREАK-CОNT REPEАTS.
CPU для Вас - Работа с TR-DOS на уровне машинных кодов.
Текст : Ворожкин Александр.
Работа с TR-DOS на уровне машинных ко-
дов.Вход в TR-DOS обеспечивается при
чтении компьютером команды по адресам
#3D00-#3DFF.При этом основное ПЗУ компь-
ютера отключается, а на его место "подс-
тавляется" ПЗУ TR-DOS. В нем по адресам
#3D00-#3D2E (15610-15663) размещены точ-
ки входа в подпрограммы TR-DOS. Наиболее
важные из них приведены ниже.
HEX DEC Выполняемое действие
#3D00 15616 Выход в TR-DOS
#3D03 15619 Выход в TR-DOS,выполне-
ние команды TR-DOS и
возврат в SOS
#3D13 15635 Выполнение внутренних
команд TR-DOS,в зависи-
мости от содержимого
регистра C микропроцес-
сора
#3D2F 15664 Выполнение подпрограмм
TR-DOS,адресуемых через
стек.
Первые две точки входа общеизвестны и
их использование не вызывает особых зат-
руднений , две последние заслуживают от-
дельного рассмотрения.После обращения по
адресу #3D13 выполняется переход к под-
программе #283C (10300), которая в зави-
симости от кода, содержащегося в регист-
ре C, выполняет переход на необходимую
подпрограмму TR-DOS.
1.C = #01.Выбор дисковода.Его номер
указывается в регистре A.При первом
обращение к данному дисководу проис-
ходит его инициализация (определяется
количество дорожек, константа позици-
рования) и изменяются соответствующие
системные переменные.
2.C = #02.Головка устанавливается на
трек,указанный регистром A.В случае
двустороннего дисковода трекам 0
(нижний) и 1 (верхний) соответствует
первый физический трек,трекам 2 и 3 -
второй и т.д. Следует отметить,что на
диске может быть не 80 физических
треков (от 0 до 79),а 82 - 85 в зави-
симости от конкретного дисковода, что
используется некоторыми программами.
Пример использования этой команды бу-
дет дан ниже (пример.16).
3.C = #05.Чтение группы секторов. В ре-
гистре B-количество считываемых сек-
торов,D-номер начального трека, E-на-
чального сектора , HL-адрес буфера, в
который производится чтение.
пример 6.
LD C,#05 ;код команды
LD B,#10 ;16 секторов
LD D,#00 ;трек
LD E,#00 ;сектор
LD HL,#8000 ;адрес буфера
CALL #3D13
RET
Эта программа прочитает содержимое ну-
левого трека в буфер по адресу 32678.На
первый взгляд,две последние строчки про-
граммы можно заменить одной командой
JP #3D13 - ведь вызываемая подпрограмма
заканчивается RET.Однако такая замена не
всегда возможна из-за особенностей возв-
рата после выполнения подпрограмм TR-DOS
Отключение ПЗУ TR-DOS происходит при
чтении процессором кода команды по адре-
су, большему #3FFF.Если вы будете запус-
кать предлагаемые примеры из BASIC'а ко-
мандой RANDOMIZE USR adr , то возврат
происходит через точку #2D2B(STACK-BC) в
основном ПЗУ.Если вы сделаете предлагае-
мую замену, то программа будет нормаль-
но работать при вызове ее из другой про-
граммы,находящейся в ОЗУ.Если вы вызове-
те эту программу с помощью USR из BASIC,
то при возврате не произойдет переключе-
ние ПЗУ и результат будет таким, как не
хотелось бы.
4.C = #06.Запись группы секторов.Пара-
метры аналогичны операции
чтения группы секторов.
Пример 7.
LD C,#06 ;код команды
LD B,#10 ;16 секторов
LD D,#9F ;трек
LD E,#00 ;сектор
LD HL,#8000 ;адрес буфера
CALL #3D13
RET
Эта программа ззпишет содержимое буфера
по адресу 32768 на последний,159-й трек.
Так можно, например,сохранить ранее счи-
танный каталог на случай его порчи. Про-
читав его затем из 159-го трека и запи-
сав на 0-й,можно спасти ценные программы
Следует заметить еще одну особенность
команд #05 и #06 - они могут работать не
только с секторами длиной 256 байт, но и
с секторами другой длины (128,512,1024
байт).Используя особенность,несложно ор-
ганизовать обмен информацией между ZX-
SPECTRUM и другими компьтерами. так мож-
но читать и писать на диски,используемые
в компьютерах "СПЕЦИАЛИСТ" ,"ОРИОН-128",
ДВК (с контроллером MY), а также IBM.од-
хако в этих случаях нельзя сразу прочи-
тать или записать группу секторов , так
как секторы длиннее 256 байт будут нак-
ладываться в памяти компьютера один на
другой , неудача также постигнет вас при
попытке считать с диска IBM сектор с но-
нером 9 - таких там нет (при стандартном
формате). Выход из этой ситуации прост -
необходимо изучить структуру "чужого"
диска и читать (или писать) информацию
по одному сектору , каждый раз вычисляя
номер следующего трека и сектора,а также
адрес буфера.
5.C = #07. Вывод каталога диска.В реги-
стре A - номер канала для вывода ката-
лога. Предварительно должна быть выпол-
нена команда #18.
пример 8.
LD C,#18 ;код команды
CALL #3D13 ;
LD A,#02 ;номер канала
LD C,#07 ;код команды
CALL #3D13
RET
Эта программа равносильна директиве CAT
TR-DOS.Если в регистр A перед вызовом
команды занести #03 , то каталог будет
выведен на принтер.
6.C = #08.Чтение информации о файле.В
регистре A предварительно помещается по-
рядковый номер файла (0-127).Из каталога
диска 16 байт переносятся в область сис-
темных переменных TR-DOS.Не имеет значе-
ния,стерт ли этот файл или нет и сущест-
вует ли он вообще.Адреса соответствующих
системных переменных приведены ниже.
HEX DEC Длина, назначение
байт
#5CDD 23733 8 Имя файла в ASCII
... ...
#5CE4 23780
#5CE5 23781 1 Тип файла (A,B,C,
D или #) , но , в
принципе , может
быть любой сим-
вол,что открывает
дополнительные
возможности
#5CE6 23782 2 Стартовый адрес
кодового файла
или длина BASIC
программы
#5CE8 23784 2 Длина файла
#5CEA 23786 1 Объем файла в
секторах
#5CEB 23787 1 Номер первого
сектора файла
#5CEC 23788 1 Номер первого
трека файла.
7.C = #09.Запись информации о файле в
каталог.Из области системных переменных
16 байт переписываются в каталог на мес-
то информации о файле,номер которого за-
дан в A.
Пример 9.
LD A,#20
LD C,#80
CALL #3D13
LD HL,#5CDD
LD (HL),#41
LD C,#09
CALL #3D13
RET
Эта программа изменит первый символ в
имени 32-го файла "A".Если этот файл был
стертым, то он "восстановится" , но для
этого необходимо дополнительно внести
изменения также в 8-й сектор нулевого
трека.Как это сделать , будет рассказано
ниже.
8.C = #0A.Поиск файла по имени ,задан-
ному в области системных переменных.
Номер найденного файла возвращается в
регистр C.Можно искать файлы по имени
и типу (9 байт) или по любому количест-
ву первых байтов имени (от 1 до 16),сле-
дует лишь указать количество символов в
системной переменной по адресу #5D06
(23814).
9.C = #0B.Запись на диск файла.Имя и
тип файла - в области системных перемен-
ных,длина - в DE,начальный адрес - в HL.
Пример 10.
LD HL,NAME ;адрес имени файла
LD C,#13 ;код команды
CALL #3D13
LD DE,#1B00
LD HL,#4000
LD C,#0B ;код команды
CALL #3D13
RET
NAME DEFM "SCR C" ;имя и тип фай-
ла - 9 байт.
Эта программа перенесет имя и тип файла
"SCR" CODE (вообще-то переносится 16 , а
не 9 байт , но последние 7 байт в данном
случае не имеют значения) в области сис-
темных переменных (адреса 23773-23781),
затем запишет файл на диск.
10.C = #0C.Запись BASIC программы.Имя и
тип файла (при типе,отличном от "B",файл
записывается под именем "boot") - в об-
ласти системных переменных.
11.C = #0E.Чтение / проверка файла.Имя
и тип файла - в области системных пере-
менных.Адрес загрузки файла берется с
директории при A = #00 или из регистро-
вой пары DE при A = #03. При A = #FF ко-
ды загружаются с адреса HL, причем длина
загружаемого файла берется из DE.Байт по
адресу 23801 определяет тип операции:
#00 - LOAD,#FF - VERIFY.
Пример 11.
Предположим,что на диске есть экранный
файл с начальным адресом 16384 и длиной
6912 байт.
LD HL,M1
LD C,#13
CALL #3D13
LD C,#0A
CALL #3D13
LD A,C
LD C,#08
CALL #3D13
LD A,#00
LD (#5CF9),A
LD C,#0E
CALL #3D13
RET
M1 DEFM "SCR C"
Эта программа перенесет имя и тип фай-
ла "SCR" CODE (9 символов) в облать сис-
темных переменных (адреса 23773-23781),
затем отыщет этот файл в каталоге , про-
читает информацию о нем из каталога дис-
ка,а затем прочитает сам файл по данным,
взятым из каталога.
Пример 12.
Добавьте к программе из примера 11 сле-
дующие строки:
LD A,#03
LD HL,#4800
LD DE,#0800
Эти строки следует вставлять между
LD (#5CF9),A и LD C,#0E.
Запустив теперь программу, вы увидите в
центре экрана то,что раньше было в верх-
ней его части.
Пример 13.
Если мофифицировать пример 11 следующим
образом:
LD A,#FF
LD HL,#8000
То программа загрузит весь файл "SCR"
CODE по адресу 32768.
Примечание: пример 13 следует вставлять
в пример 11 между строками:
LD (#5CF9),A и LD C,#0E.
12. C = #12.Удаление файлов. Имя и тип
файла - в области системных
переменных. удаляются все
файлы с такими данными.
Пример 14.
LD HL,M1
LD C,#12
CALL #3D13
LD C,#0A
CALL #3D13
LD A,C
LD C,#18
CALL #3D13
RET
M1 DEFM "SCR C"
Эта программа найдет на диске файл
"SCR" CODE и сотрет его.
13. C = #13.По адресу 23773 переписыва-
ются 16 байт информации из
памяти,адресуемой регистро-
вой парой HL.
14. C = #14.Переписываются 16 байт ин-
формации из области систем-
ных переменных по адресу
23773 в память по адресу,
указанному в регистровой
паре HL.
15. C = #15.Проверка дорожки. Регистр D
должен содержать номер про-
веряемого физического трека
16. C = #16.Загрузка системного регис-
тра.Код - в регистре A.Пре-
дварительно к нему прибав-
ляется #3C.
17. C = #17.Выбрать нижнюю сторону.
18. C = #18.Настройка на диск. Проверя-
ется тип диска (8-й сектор директории).
Точка #3D2E открывает доступ к любой
подпрограмме TR-DOS,однако ее использо-
вание немного сложнее, чем #3D13.Для на-
чала вспомним, как микропоцессор Z80 вы-
полняет команды CALL_adr и RET. При вы-
полнении команды CALL_adr в стек записы-
ваются два байта - адрес команды,следую-
щей за CALL_adr, затем выполняется пере-
ход по адресу adr,При выполнении команды
RET из стека считываются два байта - ад-
рес, по которому происходит возврат из
подпрограммы.Причем совсем не обязатель-
но,чтобы эти два байта были записаны в
стек при выполнении команды CALL_adr-они
могут быть помещены туда, например, при
выполнении команды PUSH_dd, где dd - лю-
бая пара регистров микропроцессора.При
обращении по адресу,соответствующему
точке входа в DOS ( в данном случае
#3D2E), как уже упоминалось,происходит
подмена основного ПЗУ на ПЗУ TR-DOS.В
последнем случае по адресу #3D2E записан
код команды RET (#C9).Если предваритель-
но поместить в стек адрес какой-либо
подпрограммы TR-DOS, то будет выполнен
переход на нее. В стек также необходимо
перед этим поместить адрес возврата из
подпрограммы.
Пример 15.
LD HL,RET1 ;адрес возврата
PUSH HL ;помещаем в стек
LD HL,ADRESS ;адрес подпрограм-
мы TR-DOS
PUSH HL ;помещаем в стек
JP #3D2E ;переход
RET1 RET ;точка для возврата
Для использования точки входа #3D2E не-
обходимо знать адреса подпрограмм TR-DOS
К сожалению, TR-DOS написана очень запу-
танно и дизассемблировать ее довольно
сложно.Три приведенных ниже отрывка мож-
но использовать при разработке приклад-
ных программ,работающих с диском.Они со-
ответствуют версии 5.03, в других верси-
ях они тоже есть, но могут иметь другие
адреса.
#2A53 (10835):
OUT (C),A
RET
#3FCA (16330):
M1 IN A,(#FF) ;читаем системный
регистр
AND #C0 ;проверяем готов-
ность данных
JR Z,M1 ;еще раз,если не
готово
RET M ;возврат,если все
записано
OUTI ;вывести следующий
байт в регистр
JR M1 ;данных
#3FE5 (16357):
M2 IN A,(#FF)
AND #C0
JR Z,M2
RET M ;возврат,если все
прочитано
INI ;прочитать следую-
щий байт из ре-
JR M2 ;гистра данных
Первая из приведенных подпрограмм поз-
воляет выводить необходимые данные в лю-
бой из портов контроллера TR-DOS (это
можно сделать только через обращение к
ПЗУ TR-DOS).Таких портов пять.Первые че-
тыре из них принадлежат микросхеме
КР1818ВГ93 (табл.1).
Таблица 1.
Адрес Назначение
#1F Регистр команд/состояния
#3F Регистр дорожки
#5F регистр сектора
#7F регистр данных
Назначение регистров и структура команд
КР1818ВГ93 рассмотрены в работе [4].
Есть еще один порт, который имеет адрес
#FF и обычно реализуется на К555ТМ9.Это
системный регистр TR-DOS. При необходи-
мости узнать содержимое этого регистра
можно,воспользовавшись ячейкой 23830,ко-
торая хранит копию этого регистра.
Назначение разрядов управляющего слова
следующее:
D0 и D1 - номер НГМД (0-A,1-B,2-C,3-D);
D2 - команда сброса контроллера;
D3 - готовность головки (1 - готово);
D4 - сторона диска (0 - низ,1 - верх);
D6 - плотность записи (0 - двойная,1 -
одинарная);
При чтении из порта #FF считываются си-
гналы DRQ (готовность данных для переда-
чи, 38 вывод ВГ93, 6-й разряд порта) и
INTRQ (выполнение команды,38 вывод ВГ93,
7-й разряд порта).
Две последние подпрограммы предназна-
чены для чтения массива данных в регистр
данных.Их можно использовать так:
Пример 16.
LD A,#10 ;номер трека
LD C,#02 ;код команды
CALL #3D13 ;установить головку
LD A,#50 ;данные для систем-
ного регистра
LD C,#FF ;адрес порта
LD HL,RET0 ;адрес возврата
PUSH HL ;в стек
LD HL,#2A53 ;адрес подпрограммы
PUSH HL ;в стек
JP #3D2E
RET0 LD C,#1F ;адрес порта регис-
тра команд
LD A,#E4 ;код команды чтения
трека
LD HL,RET1 ;адрес возврата
PUSH HL ;в стек
LD HL,#2A53 ;адрес подпрограммы
PUSH HL ;в стек
JP #3D2E
RET1 LD C,#7F ;адрес порта регис-
тра данных
LD HL,RET2 ;адрес возврата
PUSH HL ;в стек
LD HL,#3FE5 ;адрес подпрограммы
PUSH HL ;в стек
LD HL,#A000 ;адрес буфера
JP #3D2E
RET2 RET
Эта программа прочитает содержимое вер-
хнего трека 16-го цилиндра диска со все-
ми метками в буфер по адресу #A000.
Если изменить две строки
LD A,#F4 ;код команды записи
трека
LD HL,#3FCA ;адрес подпрограммы
( Эту программу необходимо поместить за
место строк LD A,#E4 и LD HL,#3FE5 ), то
получим программу записи трека со всеми
метками (форматирование трека).Предвари-
тельно в буфере необходимо подготовить
массив для записи. Причем можно записы-
вать треки с секторами любой длины
(128,256,512,1024 байт) и с произвольной
нумерацией - необходимо лишь правильно
подготовить данные [4]. Можно предложить
еще одно применение этой точки входа.В
ПЗУ TR-DOS довольно много свободного
места (около 4Кбайт). Туда можно помес-
тить свои программы и запустить их, как
предложено в примере 15. Таким образом,
можно значительно расширить возможности
TR-DOS, причем такое изменение не отра-
зится на совместимости. Например, можно
добавить программу "восстановления" или
проверки диска,небольшую "boot"-програм-
му или даже драйвер принтера,хотя в пос-
леднем случае потребуются небольшие (не
более 20 байт) изменения в основном ПЗУ.
Конечно,неплохо было бы внести изменения
в некоторые подпрограммы ПЗУ TR-DOS,хотя
бы с целью исправления имеющихся ошибок,
но это не всегда приемлемо.Любое измене-
ние может привести к тому, что какая-ни-
будь программа перестанет работать на
таком компьютере. Особенно это касается
основного ПЗУ компьютера. Там даже не-
большое изменение (в том числе и запол-
нение свободного пространства, изменение
шрифта или даже надписи,которая выводит-
ся при включении компьютера) может ухуд-
шить совместимость с его "фирменным бра-
том".Так что,решайте, какой компьютер вы
хотите иметь: Sinclair - совместимый или
Sinclair - подобный.
ТR-D0S для тех кто о нем ничего не знает - К0ДЫ 0ШИБ0К ПPИ ВЫЗ0ВE ИЗ ВАSIСа.
<b>ТR-D0S для тех кто о нем ничего не знает</b> - К0ДЫ 0ШИБ0К ПPИ ВЫЗ0ВE ИЗ ВАSIСа.
TR-DOS для тех кто о нем ничего не знает. .BIG BRAIN
WARL0CK 15.02.97
Я ни разу не писал статей в HACKER и, чтобы исправить сие
недоразумение я решил написать серию статей о TR-DOSe. Начну я с
самых азов, т.е. с того как его вызвать из BASIC и из машинных
кодов и др.
----------------------------------------------------------------
КОДЫ ОШИБОК ПРИ ВЫЗОВЕ ИЗ BASICa.
Коды ошибок можно получить из переменной TR-DOS (23823) или
через переменную BASICa следующим образом:
LET err=USR 15б19:REM:команда DOS
или
RAND0MIZE USR 15б19:REM:команда DOS
LET err=РЕЕК 23823
а теперь значения кодов ошибок:
0 - нет ошибок
1 - нет файла
3 - файл с таким именем на диске уже есть
4 - каталог переполнен (кол-во файлов больше 128)
5 - переполнение номера записи
б - нет диска
7 - ошибка на диске
8 - ошибка синтаксиса
9 - я не нашел нигде данных на эту ошибку
10 - канал уже открыт
11 - диск не форматирован
12 - канал не открыт
----------------------------------------------------------------
СТРУКТУРА ЗАГОЛОВКА ФАЙЛА
0...7 8 9 В D E F
ХХХХХ Х ХХ ХХ Х Х Х
¦ ¦ ¦ ¦ ¦ ¦ L 1 байт -номер начальной дорожки
¦ ¦ ¦ ¦ ¦ L-- 1 байт -номер начального сектора
¦ ¦ ¦ ¦ L---- 1 байт -длина файла в секторах
¦ ¦ ¦ L-------- 2 байта-длина файла для C0DE и програм-
¦ ¦ ¦ ной части для BASICa,в байтах
¦ ¦ L------------ 2 байта-начальный адрес для C0DE или
¦ ¦ полная длина файла для BASICa
¦ L-------------- 1 байт -тип файла
L-------------------- 8 байт -имя файла
----------------------------------------------------------------
СТРУКТУРА СИСТЕМНОГО CEKT0PA
г==========T============================================¬
¦СМЕЩЕНИЕ ¦ НАЗНАЧЕНИЕ ¦-
¦==========+============================================¦-
¦ 225 ¦ Байт. Номер следующего свободного сектора. ¦-
¦ ¦ После форматирования равен нулю. ¦-
¦----------+--------------------------------------------¦-
¦ 22б ¦ Байт. Номер следующей свободной дорожки. ¦-
¦ ¦ После форматирования равен единице. ¦-
¦----------+--------------------------------------------¦-
¦ 227 ¦ Байт. Тип дискеты: ¦-
¦ ¦ 22 - 80 дорожек,2 стороны ¦-
¦ ¦ 23 - 40 дорожек,2 стороны ¦-
¦ ¦ 24 - 80 дорожек,1 сторона ¦-
¦ ¦ 25 - 40 дорожек,1 сторона ¦-
¦----------+--------------------------------------------¦-
¦ 228 ¦ Байт. Кол-во файлов на диске, в том числе ¦-
¦ ¦ и удаленные.После форматирования равен 0. ¦-
¦----------+--------------------------------------------¦-
¦ 229,230 ¦ Слово. Кол-во свободных секторов. ¦-
¦ ¦ После форматирования: ¦-
¦ ¦ ¦-
¦ ¦ 2544 - для 80 дорожечного двухстороннего ¦-
¦ ¦ 12б4 - для 80 дорожечного одностороннего ¦-
¦ ¦ и 40 дорожечхого двухстороннего ¦-
¦ ¦ б24 - для 40 дорожечного одностороннего ¦-
¦----------+--------------------------------------------¦-
¦ 231 ¦ Байт. Кол-во секторов на дорожке. ¦-
¦ ¦ Если не равен 1б то выдается сообщение: ¦-
¦ ¦ Disk еггог и 23823=11 ¦-
¦----------+--------------------------------------------¦-
¦ 232,233 ¦ Два байта нулей. Только вот зачем, я не ¦-
¦ ¦ знаю. ¦-
¦----------+--------------------------------------------¦-
¦ 234...242¦ Девять байт пробелов (код 32). ¦-
¦----------+--------------------------------------------¦-
¦ 243 ¦ Один байт равный нулю. ¦-
¦----------+--------------------------------------------¦-
¦ 244 ¦ Байт. Кол-во удаленных файлов. После фор- ¦-
¦ ¦ матирования равен нулю. ¦-
¦----------+--------------------------------------------¦-
¦ 245...252¦ 8 байт. Имя диска ¦-
¦----------+--------------------------------------------¦-
¦ 253...255¦ 3 байта нулей. ¦-
L==========¦============================================--
--------------------------------------------------------
----------------------------------------------------------------
ВХОДНЫЕ ТОЧКИ TR-DOS
0 входных точках TR-DOS написано не мало но я решусь
повториться, т.к. возможно у кого-то этой информации нет.
#3D00 - вход в DOS из BASICa.
#3D03 - вызов команды TD-DOS из BASICa.
#ЗDOб - канал связи с дисковым файлом-программа ввода.
#ЗDOE - канал связи с дисковым файлом-программа вывода.
#3D13 - выполнение команды заданноы в регистре С процессора.
#3D2F - переход на любой адрес DOS.
По адресу #3D2F стоят две комманды :NOP и RET.С помощью этой
точки входа можно попасть по любому адресу ПЗУ TR-DOS.
LD HL,LAB ; адрес возврата
PUSH HL ; заносим его на стек
LD HL,адрес ; адрес в ПЗУ TR-DOS
PUSH HL ; заносим его на стек
JP #3D2F ; переход на точку входа
LAB ..... ; продолжение программы
или
LD HL,адрес ; адрес ПЗУ TR-DOS
CALL DOS ; заносим на стек адрес возврата
..... ; продолжение программы
DOS PUSH HL ; заносим на стек адрес в ПЗУ TR-DOS
JP #3D2F ; переход на точку входа
Наиболее важной точкой входа является #3D13.Теперь разберем
команды, которые можно использовать через эту точку входа.
Чтобы вызвать команду нужно задать необходимые данные в
регистрах и воспользоваться данной конструкцией:
LD С,номер ;номер команды
CALL #3D13 ;переход на точку входа
..... ;продолжение программы
==========T=====================================================
Значение ¦Краткое описание команды
регистра С¦
==========+=====================================================
#00 ¦ Восстановление ВГ-93: головка отводится на нулевую
¦ дорожку и ожидает сигнала INTRQ. Ожидание можно
¦ прервать нажав BREAK.
----------+-----------------------------------------------------
#01 ¦ Инициализация дисковода, заданного в регистре А:
¦ 00-А
¦ 01-В
¦ 02-С
¦ 03-D
----------+-----------------------------------------------------
#02 ¦ Установка головки на дорожку номер, которой задан в
¦ регистре А (0...179).
----------+-----------------------------------------------------
#03 ¦ Установка номера сектора, номер которого задан в
¦ регистре А (1..1б).
----------+-----------------------------------------------------
#04 ¦ Установка адреса буффера, заданного в регистровой
¦ паре HL. Помещается в системную переменную #5D00.
----------+-----------------------------------------------------
#05 ¦ Чтение блока секторов. По адресу HL считывается В
¦ секторов с доржки D,первый из которых имеет номер E.
----------+-----------------------------------------------------
#0б ¦ Запись блока секторов. Параметры те же, что и в ко-
¦ манде #05.
----------+-----------------------------------------------------
#07 ¦ Вывод каталога диска в канал с номером А.
----------+-----------------------------------------------------
#08 ¦ Чтение заголовка (дискриптора) файла в область сис-
¦ темных переменных. Номер файла в регистре А (0..127
¦ в том числе и удаленные).
----------+-----------------------------------------------------
#09 ¦ Запись заголовка файла на диск. Номер файла в А.
----------+-----------------------------------------------------
#0А ¦ Поиск файла в каталоге по имени и типу, которые за-
¦ даны в системных переменных #5CDD...#5CE5; кол-во
¦ проверяемых байтов заносится в переменную 23814,
¦ обычно 9. Если файл был найден, то его номер в ре-
¦ гистре С и ячеыках 23838, 23823; если не найден то
¦ регистр С=255, 23823=255, 23838 не изменяется.
----------+-----------------------------------------------------
#0В ¦ Запись на диск файла типа C0DE с адреса HL и длиной
¦ DE; имя и тип файла должны находиться в системных
¦ переменны. Проверка на наличие одноименного файла не
¦ проводитяся, так что можно создать несколько файлов
¦ с одинаковым именем и типом.
----------+-----------------------------------------------------
#0С ¦ Запись BASIC программы. Имя задается в системных пе-
¦ ременных. Проверка на наличие одноименного файла не
¦ производится. В переменной #5CD1 задается номер
¦ строки автостарта. Если тип не В, то файл запишеться
¦ под именем "boot".
----------+-----------------------------------------------------
#0E ¦ Загрузка или VERIFY файлов; тип и заголовок в сис-
¦ темных переменных.При 23801=0 производится загрузка,
¦ а при 255 проверка. Для загрузки BASIC программы
¦ 23824 должна быть равна 0, если 255 то старая прог-
¦ памма стирается, а новая не загружается. Данные, в
¦ зависимости от содержимого А, задаются по разному:
¦
¦ А=0 ;адрес и длина берутся из каталога
¦ А=3 ;адрес в HL, длина DE
¦ А=255 ;адрес в HL, длина из каталога
----------+-----------------------------------------------------
#12 ¦ Стирается файл, имя и тип которого заданны в сис-
¦ темных переменных, стерты будут все файлы с такими
¦ именем и типом, их кол-во в переменной 23815.
----------+-----------------------------------------------------
#13 ¦ Перенос информации о файле (дискриптора,1б байт) с
¦ адреса HL в системные переменные #5CDD...#5CE5.
----------+-----------------------------------------------------
#14 ¦ Перенос дискриптора файла из системных переменных
¦ в адрес HL.
----------+-----------------------------------------------------
#15 ¦ Проверка дорожки, номер которой задан в регистре D.
¦ Если на дорожке будут найдены битые сектора, то в
¦ системных переменных будут данные:
¦ #5DOF код ошибки 7
¦ #SСDб кол-во битых секторов
----------+-----------------------------------------------------
#1б ¦ Устанавливает текущую сторону диска 0.
----------+-----------------------------------------------------
#17 ¦ Устанавливает текущую сторону диска 1.
----------+-----------------------------------------------------
#18 ¦ Производит настройку переменных на тип дискеты.
==========¦=====================================================
Приведу несколько примеров использования точки входа #3D13:
Загрузчик моноблок :
; в BASIC строке за REM расположена примерно такая процедура:
LD HL,#9С40 ;адрес загрузки картинки
LD ВС,#1В05 ;кол-во секторов и код команды 5
LD DE,(#5CF4) ;номер следующих дорожки и сектора
CALL 15б35 ;вызов процедуры загрузки
LD DE,#4000 ;адрес экранной области
LD HL,#9С40 ;адрес куда загрузили картинку
LD ВС,#1В00 ;длина картинки
LDIR ;переброска картинки в экран
LD HL,#7530 ;адрес загрузки основного блока
LD ВС,#б205 ;кол-во секторов и код команды 5
LD DE,(#5CF4) ;номер следующих дорожки и сектора
CALL 15б35 ;вызов процедуры загрузки
JP #7530 ;запуск загруженной программы
Пофайловый загрузчик:
; так же в строке после REM небольшая процедура:
LD HL,BL0CK1 ;адрес имени и типа первого блока
LD С,#13 ;перенос дискриптора
CALL 15б35 ;вызов процедуры переноса дискриптора
LD С,#0А ;поиск фаила на диске
CALL 15б35 ;вызов процедуры поиска
LD А,С ;сохраняем номер файла в аккумуляторе
INC С ;если файла нет,
JP Z,LABEL ; то переход на вывод сообщения
LD С,8 ;чтение дискриптора файла
CALL 15б35 ;переход на точку входа
X0R А ;загрузка
LD (23801),А ; файла
LD А,255 ;длина из каталога адрес из HL
LD HL,#9С40 ;адрес загрузки
PUSH HL ;запоминаем его
LD С,#0E ;загружаем первый блок
CALL 15б35 ;вызов процедуры загрузки
POP HL ;восстанавливаем адрес
LD DE,#4000 ;переброска
LD ВС,#1В00 ; картинки
LDIR ; в экран
LD HL,BL0CK2 ;адрес имени второго блока
LD С,#13 ;перенос дискриптора файла
CALL 15б35 ;вызов процедуры переноса
LD С,#0А ;поиск файла на диске
CALL 15б35 ;вызов процедуры поиска
LD А,С ;сохраняем номер фаила в аккумуляторе
INC С ;если файл не найден
JP Z,LABEL ; то переход на печать сообщения
LD С,#08 ;загрузка дискриптора файла
CALL 15б35 ;переход в TR-DOS
X0R А ;загрузка
LD (23801),А ; блока
LD А,255 ;адрес в HL, длина из каталога
LD HL,#7530 ;адрес загрузки
LD С,#0E ;загрузка файла
CALL 15б35 ;вызов процедуры загрузки
JP #7530 ;запуск основного блока
LABEL CALL 3435 ;очистка экрана
LD А,2 ;номер канала вывода
CALL #1б01 ;вызов процедуры открытия канала
LD DE,ТЕХТ ;DE начало сообщения
LD ВС,BL0CK1-ТЕХТ ;ВС длина сообщения
CALL 8252 ;печать сообщения
LD А,2 ;красный цвет
0UT (254),А ; бордюра
DI ;запрещаем прерывания
HALT ;вешаем комп
ТЕХТ DEFB 22,0,0 ;координаты печати
DEFM "FILE N0T FOUND!"
BL0CK1 DEFM "FILE1 С"
BL0CK1 DEFM "FILE2 С"
Раз уж разговор зашел о точках входа то я дам несколько адресов
процедур TR-DOS с адресами для версии ПЗУ 5.04Т.
==============T=================================================
адрес (НЕХ) ¦ назначение
==============+=================================================
0000 ¦ Полный рестарт системы
--------------+-------------------------------------------------
0008 ¦ RST #08, инициализация, в DE вершина памяти.
--------------+-------------------------------------------------
0010 ¦ RST #10,вывод символа из аккумулятора.
--------------+-------------------------------------------------
0018 ¦ RST #18, строки символов начиная с (HL), кончая
¦ нулем или символом больше 127.
--------------+-------------------------------------------------
0020 ¦ RST #20, вызов процедуры из ПЗУ, адрес сразу за
¦ RST #20, например:
¦
¦ RST #20
¦ DEFW #ODбВ
--------------+-------------------------------------------------
0028 ¦ RST #28, возвращает в HL адрес текущего канала
¦ с учетом сдвига в С.
--------------+-------------------------------------------------
OOбб ¦ Обработчик MAGIC кнопки.
--------------+-------------------------------------------------
01D3 ¦ Выход из DOS после выполнения команд #0D-#11
¦ точки входа 15б35. Это команды для дальнейшего
¦ расширения TR-DOS.
--------------+-------------------------------------------------
03б0 ¦ Название версии TR-DOS.
--------------+-------------------------------------------------
0405 ¦ Процедура выполнения команды #18.
--------------+-------------------------------------------------
0800 ¦ Свободная память (#FF).
--------------+-------------------------------------------------
1003 ¦ Текст "Подсоединен интерфейс 1".
--------------+-------------------------------------------------
10А5 ¦ Текст системной информации для LIST.
--------------+-------------------------------------------------
115D ¦ Печать листа (HL) в десятичной форме.
--------------+-------------------------------------------------
1бSС ¦ Выполнение команды #08.
--------------+-------------------------------------------------
1ббЧ ¦ Выполнение команды #09.
--------------+-------------------------------------------------
1CF0 ¦ Выполнение командв #0А.
--------------+-------------------------------------------------
1EЗD ¦ Выполнение команды #05.
--------------+-------------------------------------------------
1EЧD ¦ Выполнение команды #0б.
--------------+-------------------------------------------------
1FFD ¦ Форматирование дорожки
¦ в E номер дорожки
¦ в системной переменной #SСEб адрес таблицы
¦ в системной переменной #5CE8 адрес таблицы +1
¦ в системной переменной #5CD7 флаг проверки
¦ секторов #1FB9 - 0rdinary, #325А - TURB0.
--------------+-------------------------------------------------
1FEB ¦ Выполнение команды #1б.
--------------+-------------------------------------------------
1FFб ¦ Выполнение команды #17.
--------------+-------------------------------------------------
2739 ¦ Выполнение команды #15.
--------------+-------------------------------------------------
27бб ¦ Тексты сообщений TR-DOS.
--------------+-------------------------------------------------
283С ¦ Сюда переходит управление с 15б35, определение
¦ команды.
--------------+-------------------------------------------------
288С ¦ Таблица адресов команд адресуемых регистром С.
--------------+-------------------------------------------------
28D8 ¦ Выполнение команды #07.
--------------+-------------------------------------------------
28E0 ¦ Выполнение команды #13.
--------------+-------------------------------------------------
28E3 ¦ Выполнение команды #14.
--------------+-------------------------------------------------
28E5 ¦ Перенос дискриптора файла
¦ при А=0 из HL в системные переменные
¦ при А<>0 наоборот.
--------------+-------------------------------------------------
28F2 ¦ Выполнение команды #0С.
--------------+-------------------------------------------------
28FB ¦ Выплонение команды #0В.
--------------+-------------------------------------------------
290F ¦ Выполнение команды #0E.
--------------+-------------------------------------------------
292б ¦ Выполнение команды #12.
--------------+-------------------------------------------------
29В1 ¦ Тексты сообшений TR-DOS.
--------------+-------------------------------------------------
2А53 ¦ Вывод А в порт ВС.
--------------+-------------------------------------------------
2АSб ¦ Сюда TR-DOS переходит после нажатия MAGIC.
--------------+-------------------------------------------------
ЗOFD ¦ Ключевые слова TR-DOS.
--------------+-------------------------------------------------
2FFЗ ¦ Таблица адресов ключевых слов.
--------------+-------------------------------------------------
3D00 ¦ Точка входа. См. начало статьи.
--------------+-------------------------------------------------
3D03 ¦ Точка входа.
--------------+-------------------------------------------------
ЗDOб ¦ Точка входа.
--------------+-------------------------------------------------
ЗDOE ¦ Точка входа.
--------------+-------------------------------------------------
3D13 ¦ Точка входа.
--------------+-------------------------------------------------
3D24 ¦ Точка входа.
--------------+-------------------------------------------------
3D2F ¦ Точка входа.
--------------+-------------------------------------------------
3D98 ¦ Выполнение команды #00.
--------------+-------------------------------------------------
3DCB ¦ Выполнение команды #01.
--------------+-------------------------------------------------
3E44 ¦ Выполнение команды ВГ93 из В, данные в А.
--------------+-------------------------------------------------
ЗEбЗ ¦ Выполнение команды #02.
--------------+-------------------------------------------------
3F02 ¦ Выполнение команды #03.
--------------+-------------------------------------------------
ЗFOб ¦ Выполнение команды #04.
--------------+-------------------------------------------------
ЗEF5 ¦ Ожидание выполнения последней команды ВГ93.
-------------+-------------------------------------------------
ЗFE5 ¦ Здесь находится процедура приема группы байт
¦ от ВГ93 (INI).
-------------+-------------------------------------------------
3FCA ¦ Здесь находится процедура блочной записи (0UTI).
==============¦=================================================
Самый простой и надежный способ определения версии ПЗУ TR-DOS
это через команду #13:
LD HL,#03б0 ;в HL помещаем адрес версии TR-DOS
; (для 5.04Т)
LD С,#13 ;перенос из HL в системные переменные
; 1б байт
CALL 15б35 ;переход в TR-DOS
..... ;процедура сравнения стрингов
Я думаю, что для первой статьи в HACKER этого хватит. Ну чтож я
заканчиваю. Большинство информации я взял из книжки "TR-DOS для
это через команду #13:
LD HL,#03б0 ;в HL помещаем адрес версии TR-DOS
; (для 5.04Т)
LD С,#13 ;перенос из HL в системные переменные
; 1б байт
CALL 15б35 ;переход в TR-DOS
..... ;процедура сравнения стрингов
Я думаю, что для первой статьи в HACKER этого хватит. Ну чтож я
заканчиваю. Большинство информации я взял из книжки "TR-DOS для
професионалов и любителей" Ю. Поморцева.
.SCL
SCL (*.scl) - формат для хранения TR-DOS-файлов,
используемый многими эмуляторами ZX Spectrum.
Отличается от формата TRD хранением только необходимых
файлов, также может хранить больше файлов, чем
допускает система. Разработан Павлом
Неделиным (Paul Pavlov). Стал популярным благодаря
хранению большинства ПО на сайте Virtual TR-DOS в этом формате.
Расширение SCL является сокращением слова SINCLAIR.
Описание формата
Заголовок:
+0, 8 байт - сигнатура SINCLAIR
+8, 1 байт - число блоков
Данные:
Заголовки файлов (14 байт из каталога без track/sec):
Заголовок 1-го файла
Заголовок 2-го и последующих файлов (если есть)
Данные файлов:
Кодовый блок 1-го файла
Кодовый блок 2-го и последующих файлов (если есть)
Последние 4 байта - контpольная сyмма (арифметическая сумма всех предыдущих байт)
Порты
Для управления интерфейсом BETA DISC используются следующие порты:
#1f, #3f, #5f, #7f, #ff.
регистр состояния #1f [in]
регистр команд #1f [out]
in a,(#1f) - выдает состояние контроллера, где A:
.-------------------------------------------------------
|bit | commands |
|-------------------------------------------------------
| |вспомо |запись |чтение |чтение |запись |чтение |
| |гательные|сектора|сектора|адреса |дорожки|дорожки|
| |--------------------------------------------------
|0 |занято, идет выполнение команды |
|-------------------------------------------------------
|1 |идент. | запрос |
| |импульс | данных |
|-------------------------------------------------------
|2 |головка | потеря |
| |на 0 тр.| данных |
|-------------------------------------------------------
|3 | ошибка црц | 0 |
|-------------------------------------------------------
|4 |ошибка | сектор | 0 |
| |поиска | не найден | |
|-------------------------------------------------------
|5 |загрузка|ошибка |тип adr| 0 |ошибка | 0 |
| |головки | записи | метки | | записи| |
|------------------------------------------------------|
|6 |защита записи | 0 |защита | 0 |
| | | |записи | |
|-------------------------------------------------------
|7 | готовность дисковода (1 - не готов) |
'----------------------------------------------------snd
out (#1f),a - выполнение команды, где A - код команды (см. раздел команды).
Регистр дорожки #3f - in/out
in a,(#3f) - выдает номер текущего физического трека.
out (#3f),a - задание нового физического трека.
Регистр сектора #5f - in/out
in a,(#5f) - выдает номер текущего сектора.
out (#5f),a - задание нового сектора (нумерация с 1!).
Регистр данных #7f - in/out
in a,(#7f) - чтение байта
out (#7f),a - задание номера логического трека,
для команды позиционирования или запись байта если
была какая-либо команда на запись.
ini - последовательное считывание данных.
outi - последовательная запись данных.
Регистр #ff состояния сигналов intrq и drq - [in]
Системный регистр #ff - [out]
in a,(#ff) - отражает состояние сигналов intrq (d7), drq (d6) контроллера, где A:
bit
7 - intrq (сигнал окончания выполнения команды)
6 - drq (запрос данных микроконтроллером)
5 - x
4 - x
3 - x
2 - x
1 - x
out (#ff),a - вспомогательные функции, где A:
bit
7 - x
6 - метод записи (1 - fm, 0 - mfm)
5 - x
4 - выбор магнитной головки (0 - верх, 1 - низ)
3 - загрузка головки (всегда должен быть в 1)
2 - аппаратный сброс микроконтроллера (если 0)
1 - номер дисковода (00 - a, 01 - b, 10 - c, 11 - d)
0 - -/-
Порты TR-DOS доступны только тогда, когда включено ПЗУ TR-DOS!
Включить ПЗУ TR-DOS можно через специальные точки входа.
Точки входа
Аппаратное включение дискового интерфейса происходит в
момент выполнения какой-либо команды, записаной по адрес
у в диапазоне #3d00 - #3dff. Проще говоря, чтобы
попасть в ПЗУ TR-DOS нужно запомнить на стеке адрес под
программы в ПЗУ TR-DOS, куда вы хотите передать управ
ление и выполнить команду RET, расположенную в этом
диапазоне. Либо, если адрес подпрограммы лежит
непосредственно в пределах #3d00 - #3dff, можно просто
передать на нее управление, например, обычным CALL NN.
Чаще всего в качестве точки входа используют две:
самая распространенная точка входа:
#3d2f nop
ret
Редко используемая точка входа (не «переваривается» некоторыми отечественными
версиями интерфейса: GRM Pentagon и т.п.):
#3d30 ret
Но если хорошенько «покопаться» в ПЗУ TR-DOS, то
можно найти еще несколько (недокументированных):
#3d2c, #3d4b, #3d7f, #3d93, #3d96, #3d97, #3dac
По всем этим адресам находится команда ret, за исключением точки 3.
Недокументированная точка входа и кроме меня никем неиспользуемая:
#3d96 nop
ret
По идее, возможно использование любого адреса в
качестве точки входа из выше перечисленных, но
гарантию работоспособности на всяких «левых»
интерфейсах я не даю! Поэтому я настоятельно рекомендую
использовать либо первую, либо третью точку входа.
Используя эти точки входа можно добраться до
подпрограмм расположенных в ПЗУ TR-DOS.
Подпрограммы
С помощью обрывок подпрограмм TR-DOS можно писать
свои собственные процедуры работы с диском.
Для начала приведу адреса доступа к портам ввода данных:
#2fc3 out (#1f),a ;регистр команд
ret
.....
#1e3a out (#3f),a ; регистр дорожки
ret
.....
#1ff3 out (#ff),a ; системный регистр
ret
.....
#2f0c out (#ff),a ; системный регистр
ret ; (практически не используется)
.....
#2a53 out (c),a ; универсальный адрес
ret
Тот же самый эффект можно получить воспользовавшись
процедурой по адресу #20b8:
#20b8 out (c),d
djnz #20b1
ret
Или процедурой по адресу #3fd1:
#3fca in a,(#ff) ; ожидание intrq или drq
and %11000000 ; bit7 - intrq/bit6 - drq
jr z,#3fca ; для drq условие заведомо не выполнится
ret m ; выход по intrq
#3fd1 outi ; запись байта
jr #3fca
Как видите с вводом данных все просто и понятно, чег
о не скажешь об их получении. К сожалению в ПЗУ
TR-DOS не предусмотрено такой возможности. Я не говорю
о том, чтобы была такая же возможность считывать со
стояние каждого регистра в отдельности.
В принципе, хватило бы одной простой команды
типа in a,(c), но т.к. таковой нет, приходится
прибегать к различного рода ухищрениям:
Так, например, чтобы прочитать значения регистров можно
воспользоваться следующей процедурой по адресу #3ef3:
#3ef3 in h,(c)
#3ef5 in a,(#ff) ; ожидание intrq или drq
and %11000000 ; bit7 - intrq/bit6 - drq
jr z,#3ef5 ; для drq условие заведомо не выполняется
ei ; !
ret m ; выход по intrq
На выходе в H будет значение состояния нужного вам регистра.
Единственный недостаток в том, что на выходе разрешаются
прерывания, что в некоторых случаях совсем не нужно.
Можно воспользоваться другой процедурой по адресу #3fec:
#3fe5 in a,(#ff) ; ожидание intrq или drq
and %11000000 ; bit7 - intrq/bit6 - drq
jr z,#3fe5 ; для drq условие заведомо не выполняется
ret m ; выход по intrq
#3fec ini ; чтение байта
jr #3fe5
На выходе в (HL) будет значение состояния нужного вам регистра.
Таким образом можно считать состояние всех регистров
кроме регистра состояния #1f, т.к. произойдет зацикливание
при чтении #ff. Поэтому чтобы все же заполучить
значение регистра состояния, нужно выполнить следующие действия:
;(c) Andrew MOA Larchenko
in_1f out (#3f),0
out (#5f),#a
ld d,#01
jp #3f33
На выходе в B будет находиться значение регистра состояний #1f,
т.е. таким способом мы проэмулировали
команду in b,(#1f). Однако при выполнении данной процедуры
были запорчены регистры дорожки и сектора, поэтому,
при необходимости, следует восстановить в них исходные значения.
Команды
Прежде чем перейти непосредственно к командам,
необходимо сказать, что выполнение каждой последующей
команды возможно только тогда, когда завершена предыдущая,
т.е. поступит сигнал intrq. Отследить этот сигнал можно
воспользовавшись процедурой:
#3fe5 in a,(#ff) ; ожидание intrq или drq
and %11000000 ; bit7 - intrq/bit6 - drq
jr z,#3fe5 ; для drq условие заведомо не выполнится
ret m ; выход по intrq
Или любой другой аналогичной процедурой по другим адресам...
Также надо отметить, что выбор дисковода и др.
для выполнения команд осуществляется с помощью
инструкции out (#ff),a (см. раздел порты).
Микроконтроллер может выполнять 11 команд:
1. Восстановление - %0000hvrr - +
2. Поиск/Позиционирование - %0001hvrr |
3. Шаг в предыдущем направлении - %001thvrr + -
4. Шаг вперед - %010thvrr |
5. Шаг назад - %011thvrr - +
6. Чтение сектора - %100mseca
7. Запись сектора - %101msec0
8. Чтение адреса - %11000e00
9. Чтение дорожки - %11100e00
10. Запись дорожки/форматир-е - %11110e00
11. Принудительное прерывание - %1101iiii
где rr - скорость позиционирования головки:
00 - 6мс, 01 - 12мс, 10 - 20мс, 11 - 30мс (1Mhz).
v - проверка номера дорожки после позиционирования.
h - загрузка головки (всегда должен быть в 1!).
t - изменение номера дорожки в регистре дорожки после каждого шага.
a - тип адресной метки (0 - #fb,
стирание сектора запрещено 1 - #F8, стирание сектора разрешено).
c - проверка номера стороны диска при идентификации индексной области.
e - задержка после загрузки головки на 30мс (1Mhz).
s - сторона диска (0 - верх/1 - низ).
m - мультисекторная операция.
i - условие прерывания:
i0 - после перехода сигнала cprdy из 0 в 1 (готов);
i1 - после перехода сигнала cprdy из 1 в 0 (не готов);
i2 - при поступлении индексного импульса;
i3 - немедленное прерывание команды, при:
i=0 - intrq не выдается
i=1 - intrq выдается.
Восстановление - осуществляет позиционирование головки на
дорожку 0. Если через 256 импульсов сигнал tr00 не появится,
то команда прекращает работу. Выполняется независимо
от готовности дисковода.
to_tr0 out (#1f),#8 ; %00001000
Поиск/позиционирование - перемещает головку на нужный трек.
Перемещение происходит до тех пор, пока не совпадут номера
физической дорожки в #3f и логической дорожки в #7f.
Номер трека указывается без учета стороны (/2)!
jump out (#7f),trk/2 ; номер нужного трека/2
out (#1f),#18 ; %00011000
jp #3fe5 ; ожидание выполнения команды
Шаг (пункт 3) - двигает головку на один шаг в предыдущем направлении.
step out (#1f),#38 ;%00111000
jp #3fe5 ; ожидание выполнения команды
Шаг вперед - двигает головку на один шаг вперед.
step_f out (#1f),#58 ; %01011000
jp #3fe5 ; ожидание выполнения команды
Шаг назад - двигает головку на один шаг назад.
step_b out (#1f),#78 ; %01111000
jp #3fe5 ; ожидание выполнения команды
Чтение сектора - чтение сектора с текущей дорожки.
При мультисекторной операции читаются все сектора с заданного до конца трека.
load_s out (#5f),sec ; номер считанного сектора (нумерация с 1)
out (#1f),#80 ; %10000000
ld hl,address ; адрес загрузки
ld c,#7f
jp #3fe5 ; чтение сектора
Запись сектора - запись сектора на текущую дорожку.
При мультисекторной операции записываются все сектора с
заданного до конца трека.
save_s out (#5f),sec ; номер записываемого сектора (нумерация с 1)
out (#1f),#a0 ; %10100000
ld hl,address ; адрес записи
ld c,#7f
jp #3fca ; запись сектора
Чтение адреса - считывает 6 байтов идентификатора сектора, включая crc (контрольная сумма).
l_addr ld hl,buffer ; адрес загрузки идентификатора
out (#1f),#c0 ; %11000000
jp #3fe5 ; чтение идентификатора сектора
На выходе в (HL) по смещению:
+0 - номер трека (0 - 128)
+1 - номер стороны (0-верх, 1-низ)
+2 - номер сектора (0 - 128)
+3 - длина сектора (0-128b, 1-256b, 2-512b, 3-1024b)
+4 - контрольная сумма (?)
+5 - -/-
Чтение дорожки - считывает всю дорожку целиком.
Синхронизация отсутствует (crc не проверяются),
т.е. нельзя отследить правильность считанной информации.
load_t ld hl,address ; адрес загрузки трека
out (#1f),#e0 ; %11100000
jp #3fe5 ; чтение трека (>6Kb)
Запись дорожки/форматирование - форматирование дорожки.
format ld hl,address ; адрес расположения данных дорожки
out (#1f),#f0 ; %11110000
Принудительное прерывание - завершение выполняемой на
данный момент команды. В отличие от других команд она
может выдаваться в любой момент.
inter out (#1f),i ; %1101iiii (значение i см. выше)
Вот еще две подпрограммки из ПЗУ TR-DOS.
#20af ld b,#01
#20b1 in a,(#ff) ; ожидание intrq или drq
and %11000000 ; bit7 - intrq/bit6 - drq
jr z,#20b1
ret m ; выход если intrq
#20b8 out (c),d ; запись байта если drq
djnz #20b1 ; повтор пока b<>0
ret
#3ef3 in h,(c)
#3ef5 in a,(#ff) ; ожидание intrq или drq
and %11000000 ; bit7 - intrq/bit6 - drq
jr z,#3ef5
ei ; !
ret m ; выход если intrq
#3efd di ; !
#3efe in a,(#7f) ; чтение байта если drq
jr #3ef5 ; повтор пока не поступит intrq
Вот, собственно и все.
Адрес для связи: Юдин Александр Владимирович (faster),
432044, г. Ульяновск, ул. Героев Свири 16а-24.
© 2004-2013 Perspective group
Я не буду в этой статье повторять описания tr-dos различных
изданий, показывать пример использования её из basica и пере-
числять номера подпрограмм пзу '#3d13'. Моя цель состоит в опи-
сании тонкостей и сложностей, с которыми я сам встретился, пыта-
ясь раскрутить мотор дисковода, написать свой загрузчик и нако-
нец свой командера. Я попытаюсь доходчиво объяснить 'как' рабо-
тать с ВГ 93 и дисководом и приведу примеры полезных процеду-
рок.
ВГ 93.
Как Вам должно быть известно, общение с ВГ происходит через
порты, обращаться к которым можно _только_ из пзу tr-dos (от-
дельные экземпляры speccy позволяют выводить/читать из портов
хоть из озу, но то является только нежеланием разработчика
привести контроллер дисковода кстандарту).
Порты (регистры) ВГ 93 чтение:
#1f - содержит информацию о состоянии микросхемы в процессе
выполнения команды или о наличии ошибок исполнения команды.
#3f - содержит номер дорожки (физический), на которой, по
мнению ВГ, стоит головка дисковода. Если сдвинуть головку
вручную, значение регистра не изменится!
#5f - содержит номер сектора.
#7f - информация, передаваемая микросхемой в процессе выполне-
ния команды (очередной считанный с диска байт).
#ff - бит 7 =1 означает конец выполнения команды, бит 6 =1 оз-
начает просьбу ВГ передать ей следующий байт, например, при
записи сектора, или взять прочитанный ею с диска байт, который
она поместила в регистр #7f.
Практического применения чтение из портов #3f и #5f не имеет, да
из них и невозможно прочитать (см. 'чтение из портов').
запись:
#1f - код команды.
#3f и #5f - тоже, что чтение.
#7f - сюда следует записывать очередной байт после просьбы ВГ
для записи его на диск, или номер дорожки для команды позицио-
нирования.
#ff - бит 6 - плотность записи (fm/mfm). В некоторых контролле-
рах (а может и во всех) этот бит не подключен, поэтому не стоит
пытаться им воспользоваться. бит 3 отвечает за остановку мотора.
Если он =1, то мотор через некоторое время после выполнения ко-
манды остановится, иначе будет крутиться вечно (по-моему так).
Бит 2 (небезизвестная книга Ларченко и Родионова даёт о нём не-
верную информацию) - сбросив его в 0 Вы добьётесь мгновенного
прекращения выполнения команды и остановки мотора. Порт #ff имеет
ещё три бита, но внимание! ВГ об их существовании не знает! Они
управляют непосредственно дисководом и контроллером (в частнос-
ти, ВГ не имеет понятия, с каким дисководом (a,b...) она работа-
ет). Бит 4 - номер стороны дискеты (1 - нижняя), биты 1,0 -
номер дисковода. Некоторые контроллеры (а может почти все)
работают только с битом 0 и позволяют выбрать только 2 дисково-
да.
Как программировать ВГ 93.
Как я уже сказал, в наших программах нельзя непосредственно
обратиться к регистрам ВГ - для чтения/записи в порты контролле-
ра необходимо использовать соответствующие участки пзу DOS, ко-
торые удобно искать с помощью sts 4.1 (следует заметить, что
sts 4.1 неверно трассирует операции ld r,(hl) в пзу tr-dos,
где r<>a и 0<=hl<=#3fff). К найденным участкам затем можно об-
ращаться такой последовательностью команд (adr - адрес
участка):
ld hl,l01
push hl
ld hl,adr
push hl
jp #3d2f
l01 ...
Удобнее так:
ld ix,adr
call dos
...
dos push ix
jp #3d2f
Не следует вместо #3d2f ставить #3d30, т.к. существуют особо
тормозные контроллеры (?), которые не успевают подключить пзу
tr-dos и требуют для этого нескольких тактов процессора.
Полезные подпрограммы пзу DOS:
запись в любой порт:
#2a53 out (c),a
ret
запись на диск до конца операции из hl:
#3fca in a,(#ff)
and #c0
jr z,#3fca
ret m; bit 7,a=1 - конец
outi
jr #3fca
чтение в hl с диска до конца операции:
#3fe5 тоже, только ini вместо outi.
ожидание выполнения команды (используется при позиционирова-
нии):
#3ef5 in a,(#ff)
and #c0
jr z,#3ef5
ei
ret m
di
in a,(#7f)
jr #3ef5
Везде в статье адреса даны для tr-dos 5.03/04t. Для 5.01 адреса
другие.
Теперь у нас есть всё для того чтобы заставить ВГ выполнить ко-
манду. Однако надо бы ещё проверить успешность этого выполне-
ния, т.е. прочитать значение регистра #1f. Разработчики tr-dos
видимо не предполагали, что возможности их системы могут не
удовлетворять программеров, отчасти поэтому такой последова-
тельности как in a,(#1f) или in r,(c): ret в пзу нет, поэтому
прочитать значения регистров #3f и #5f невозможно (хотя и не нуж-
но), а #1f - затруднительно. Есть несколько способов (2 или
3). На мой взгляд самый удобный и надёжный такой: сразу после
выполнения команды необходимо выполнить следующие дейстия:
ld d,номер дорожки, на которой стоит головка дисковода (его
надо знать)
ld (#5cd8),не 0
ld ix,#2740
call dos
ld a,(#5ccd)
здесь a будет содержать значение регистра #1f. Следует учесть,
что во время отработки процедуры #2740 осуществляется опрос кла-
виши break, поэтому во избежание недоразумений следует перехваты-
вать попытку выхода в пзу basic (см. ниже).
Теперь средство есть, объясню наконец, как дать ВГ команду
прочитать, например, группу секторов. Замечу, что команды чте-
ния/записи не раскручивают мотор!, это делают команды позици-
онирования. Команду можно подавать только когда микросхема не
занята выполнением предыдущейкоманды.
Позиционирование:
(Объясню понятия физической и логической дорожек: физ. показы-
вает только положение обеих головок, вне зависимости от сторо-
ны. Можно привести в соответствие:
номер !физ:0 0 1 1 ... 80 80
дорожки !лог:0 1 2 3 ... 160 161
Зачем это надо? дело в том, что ВГ 93 работает с физ. номерами
дорожек, ведь она не знает номер стороны (см. порт #ff), а мы
привыкли к лог. номеру. Т.о. лог=физ*2+номер стороны (0 -
нижняя)
di
ld a,d ;d - логический но-
;мер дорожки
srl a ; вычисляем физич.
; номер (a/2)
ld c,#7f; сюда надо помес-
; тить номер до-
; рожки,на которую
; я хочу отьехать.
call outc
ld a,#3c; выбор стороны
bit 0,d
jr z,l01
ld a,#2c
l01 ld c,#ff
call outc; устанавливаем
; нужные для
; нормальной ра-
;боты биты порта #ff и номер
;стороны регистр #3f устанавли-
;вать не надо, он должен сам по
;себе содержать номер текущей
;дорожки
ld a,#18; код команды пози-
;цион.
call out1
ld ix,#3ef5
call dos; ожидание выполне-
;ния
di ; команды.
jp cont
...
out1 ld c,#1f
outc ld ix,#2a53
dos push ix
jp #3d2f
Что делает ВГ 93: при получении в регистр #1f кода #18, она дает
сигнал на раскрутку диска. Затем сравнивает значения регистров
#3f и #7f, определяет направление и количество шагов головки и
шагает, изменяя значение рег. #3f. При этом значение регистра
#3f может и не совпадать с реальным положением головок. Но об
этом поговорим потом. Когда значения #7f и #3f совпали, ВГ ус-
танавливает bit 7 порта #ff =1 и готова к приёму следующей коман-
ды.
За время позиционирования мотор, вращающий диск, мог не ус-
петь раскрутиться до требуемой скорости 5 об/сек, поэтому тео-
ретически следует после выдачи команды позиционирования и перед
чтением/записью сделать паузу (паспортное время раскрутки мо-
тора 0.7 сек.). Задержка также нужна на время успокоения голо-
вок после позиционирования (по паспорту 15 мс). Однако опыт по-
казывает, что перед чтением задержку можно не делать вообще,
перед записью же - тут Вам решать, скажу лишь, что в уже два
года существующем командере fpm задержка перед записью делается
только при первом позиционирова нии перед записью первого секто-
ра из всей группы.
Итак, ездить по диску мы уже умеем и мотор уже крутится. Те-
перь можно читать сектора:
- даём команду "чтение сектора"
- читаем сектор
- проверяем наличие ошибки затем повторяем для остальных
секторов на данной дорожке. При переходе на следующую дорожку
надо опять дать команду позицио нирования. Надо сказать, что оп-
ределение наличия ошибки по #2740 нерационально при работе с
группой секторов. Существует другой способ. Процедура #2090
позволяет прочитать сектор, про верив затем, была ли ошибка. Для
её вызова необходимо:
поместить в стек:
1) адрес возврата.
2) адрес,указывающий на байт #01
3) bc=#17f
4) в (#5cd6) записать число n (любое)
5) поместить в регистр #5f номер сектора
6) войти в пзу по адресу #2090 В hl должен быть адрес, куда
читать или откуда писать сектор.
cont ld bc,exit
push hl
push bc
ld bc,cont; адрес, указыва-
;ющий на 1
push bc
xor a
ld (#5cd6),a
ld a,номер сектора
ld c,#5f
call outc
ld bc,#2090
push bc
ld bc,#17f
jp #3d2f
exit di ; процедура #2090 разре-
; шает прерывания
pop hl
ld a,(#5cd6)
Здесь a будет равно записанному ранее в (#5cd6) числу, если
ошибки не было, или на 1 больше, если была. Если ошибка была, то
следует либо повторить операцию, либо, если действия Вашей прог-
раммы зависят от конкретной ошибки, то воспользоваться #2740
для определения кода ошибки. Если ошибок не было, то следует
перерассчитать значения hl и 'номера сектора' для следующего
сектора и повторить операцию.
Подпрограмма для записи сектора выглядит несколько иначе:
cont ld bc,exit
push hl
push bc
ld bc,cont;адрес,указываю-
;щий на 1
push bc
xor a
ld (#5cd6),a
ld a,номер сектора
ld bc,#15f
call outc
push bc; в стек надо запи-
;сать #01xx
ld bc,#2099
push bc
ld bc,#3fca
push bc
ld a,#a0; команда 'запись
;сектора'
call out1
ld bc,#17f
jp #3d2f
exit di ; процедура #2090 разре-
; шает прерывания
pop hl
ld a,(#5cd6)
Дальше аналогично загрузке.
Какие могут быть ошибки? (коды из #1f)
При чтении:
bit 7 =1 дисковод не готов (не крутится мотор.
bit 5 =1 не обращайте на него внимания. На всех 'нормальных'
дисках он =0.
'& bit 4 =1 нет сектора. Обычно что-то где-то не читается.
bit 3 =1 сектор читается с ошибкой.
bit 0 =1 Вы дали ВГ команду в тот момент, когда она уже выпол-
няла что-то (?). Вероятность такой ошибкипрактически равна нулю.
При записи:
bit 7 - аналогично чтению.
bit 6 =1 на дискете заклеена прорезь 'защита записи'.
bit 5 =1 ошибка записи. Не знаю, что сие означает.
bit 4 и 0 - аналогично чтению.
При корректном обращении к контроллеру вероятность появле-
ния ошибок bit 2 и 1 также равна нулю.
Ну вот, на сегодня всё.
Ждите продолжения...
(C) Макс Петров
Системные переменные TR-DOS
Дисковой операционной системе также необходимо где-то
хранить информацию о дисководах и режимах работы, файлах и
т.п.
Для этого она используется область начиная с адреса
#5CB6 (23734)
Для системных переменных TR-DOS еще не сложились имена в
технической литературе (точнее говоря их вообще никто не
давал), поэтому я здесь наберусь наглости дать эти имена
сам.
UNIT_F, байт, #5CB6, X, 23734
Используется при наличии INTERFACE 1. Если значение
равно #F4, то область остается на старом месте. Если байт
равен 0, то происходит проверка байта #5D18 и
соответствующая операция.
S_RET, байт, #5CC2, , 23746
Содержит код команды RET. Используется TR-DOS для вызова
подпрограмм Бесйик-ПЗУ.
MODE_A, байт, #5CC8, ,23752
Код режима работы дисковода А.
бит 7=1, если дисковод 80-ти дорожечный.
бит 1=1, если дисковод двухсторонний
бит 0=0, если 80-ти дорожечный дисковод используется как 40
дорожечных
MODE_B, байт, #5CC9,X, 23753
Код режима работы дисковода В. См MODE_A
MODE_C, байт, #5CCA,X, 23754
Код режима работы дисковода С. См MODE_A
MODE_D, байт, #5CCB,X, 23755
Код режима работы дисковода D. См MODE_A
C_SERD, байт, #5CCC, X, 23756
Текущий сектор при чтении каталога
READY, байт, #5CCD, X, 23757
Содержит #80 при готовности текущего дисковода.
TRMODE, байт, #5CCE,X, 23758
Режим работы:0 чтение/#FF запись
DOS_ER, байт, #5CD6, X, 23766
Содержит #FF, если команда не выполнена
X_BYTE, слово, #5CD7, X, 23767
Для файлов типа BASIC или BYTE - адрес старта. Также
содержит число дорожек сразу после тестирования дисковода.
CHADD2, слово, #5CD9, X, 23769
Внутренний аналог CH_ADD. Также примежуточная длина файла
типа BYTES или BASIC
LENPRG, слово, #5CDB, X, 23771
Длина бейсик программы
NAME_F, 8 байт, #5CDD, , 23773
Имя файла
TYPE_F, байт, #5CE5, , 23781
Тип файла
PAR_F, слово, #5CE6, , 23782
Параметры файла:
Для Бейсика - длина бейсик программы без переменных
Для кодов - стартовый адрес
LEN_F, слово, #5CE8, , 23784
Для файла
SEC_F, байт, #5CEA, , 23786
Размер файла в секторах
FIR_SE, байт, #5CEB, , 23787
Номер первого сектора файла
FIR_TR, байт, #5CEC, , 23788
Номер первой дорожки файла
A_UNIT, байт, #5CEF, X, 23791
Содержит 1 если есть INTERFACE 1
CUR_SEC, байт, #5CF4, X, 23796
Номер текущего сектора
T_DRV, байт,#5CF6, , 23798
Дисковод для временной операции (от 0 до 3). Например LOAD
"A:HELP.ASM" CODE
DOS_ON, слово, #5CF7, , 23799
Обнуляется при возврате из TR-DOS
DBLDRV, байт, #5CF8, , 23800
Дисковод при операциях с двумя дисководами, в
противном случае #FF.
DBLMOD, байт, #5CF9, , 23801
Режим работы при работе с двумя дисководами (признак
операции READ/VERIFY). #FF - VERIFY, 00 READ. Сюда также
заноситься номер дискова при внутренней операции 7 (CAT).
TIME_A, байт, #5CFA, , 23802
Время перемещения головки дисковода А.
TIME_B, байт, #5CFB, ,23803
Время перемещения головки дисковода В.
TIME_C, байт, #5CFC, , 23804
Время перемещения головки дисковода С.
TIME_D, байт, #5CFD, ,23805
Время перемещения головки дисковода D.
WG_CODE, байт, #5CFE, X, 23806
Код команды для контроллера дисковода ВГ93
SEC_WRK, байт, #5CFF, X, 23807
Номер сектора для подпрограмм записи, чтения
BUF, слово, #5D00, X, 23808
Текущий адрес буфера
HID_HL,слово,#5D02, X,23810
Временно хранит HL при необходимости
HID_DE, слово, #5D04, X, 23812
Временно хранит DE при необходимости
CH_NAME, байт, #5D06, , 23814
Количество символов, по которым осущесвляется поиск имени
файла. Нормальное значение - 9, но вы можете его уменьшить.
Увеличивать не рекомендую.
DELF, байт, #5D07, X, 23820
Количество удаленных файлов
DELN,байт,#5D08,X,23816
Первый символ удаленного файла
WRKBUF, слово,#5D0C,X,23820
Флаф наличия рабочего буфера TR-DOS (257 байт с адреса
23846):
FF - отсутствует
00 - существует
FL_COM,байт,#5D0E,X,23822
Флаг принадлежности команд бейсика
FF - бейсик,
любое другое значение - TR-DOS
ER_FL,байт,#5D0F,X,23823
Код ошибки TR-DOS. При равенстве 0 выводит пустую строку,
иначе пустую строку (подпрограмма по адресу #20EF)
ER_HIGH,байт,#5D10,X,23824
Старший байт ошибки. При переходе в TR-DOS обнуляется. При
вызове подпрограмм TR-DOS следует обнулять принудительно.
COMADD,слово,#5D11,X,23825
Адрес строки команды TR-DOS. При вызове:
15616 - хранит E_LINE
15619 - хранит CH_ADD
ERRSP2, слово,#5D13,X,23827
Копия ERR_SP.При равенстве старшего байта АА выполняется
команда RUN "boot" и в #5D18 заноситься #FE
MES_ON,байт,#5D15,X,23829
При равенстве 00 печатает сообщения TR-DOS, иначе не
печатает.
SYSREG,байт,#5D16,,23830
Копия системного регистра (Микросхема 555ТМ9)
X_FL,байт,#5D17,,23831
При не равенстве АА рисует заставку, при равенстве FF не
попадает на ошибку при чтениее неверного адресного маркера.
INT_ON,байт,#5D18,X,23832
Используется при подключенном INTERFACE1. Если равно FF, то
меняются блоки в памяти по адреса 23747 и 23959 длиной в 45
байт. При вызове TR-DOS заноситься FF
DRVDEF,байт,#5D19,,23833
Дисковод по умолчанию (0-3)
RETADD,слово,#5D1C,X,23834
Адрес процедуры завершения. Нормальное значение #0201
HID_SP,слово,#5D1C,X,23836
Временно сохраняет регистр SP
TRLINE,3 байта,#5D20,,23840
Первые три символа введенной строки
Из журнала ZX-Guide#1, Рязань, 28.11.1998
TR-DOS
Level 1: 15635
Alone Coder
Функции TR-DOS, вызываемые из Бейсика,
обеспечивают использование далеко не всех
возможностей системы. Разработчики предус-
мотрели ряд специальных функций работы с
дисководом, обращение к которым может про-
исходить исключительно из программ в ма-
шинных кодах. Это так называемая <работа с
диском на высоком уровне>, в отличие от
файлового уровня и низкого, т.е. прямого
указания контроллеру.
Все вышеупомянутые функции выполняются
одной программой,переход к которой осущес-
твляется по адресу 15635. На самом деле в
ячейке памяти с указанным адресом содержи-
тся NOP/JR, но есть данные, что переход к
адресу 15636 срабатывает не на всех видах
Speccy. Параметром для этой программы яв-
ляется регистр C процессора,в котором дол-
жен содержаться номер команды согласно ни-
жеследующего списка:
#5 - чтение секторов.
Считать с текущего дискодава подряд B
секторов по 256 байт и запомнить их по ад-
ресу HL. Начальная дорожка(трек)должна со-
держаться в D,начальный сектор на этой до-
рожке - в E. Обычно эта функция используе-
тся в загрузчиках моноблочных программ. В
этом случае следует сделать LD DE,(23796),
то есть вспомнить номер сектора вслед за
последним считанным/записанным блоком дан-
ных,т.е. в данном случае сектор аккурат за
бейсиковой частью.
Как сделать моноблочный загрузчик? пи-
шете сначала вот такую бейсик-программу:
10 REM 16 штук пробелов
20 CLEAR 24575:RANDOMIZE USR 23872
Потом с адреса 23872 (если обращения к TR-
DOS со времени последнего сброса не было,
то с 23760) любым приятным вам способом
заносите кодовую программу:
LD DE,(23796)
LD BC,#NN05;где NN=длина кодовой части,
LD HL,ADDR;а это - адрес загрузки.
CALL 15635
JP START;адрес запуска кодовой части.
Она, очевидно,занимает весь REM. Это самый
простой случай - когда кодовый блок один и
запускается без распаковки.В более сложных
случаях добавьте пробелов после REM и
вставляйте всё, что вам заблагорассудится.
Запишем нашу бейсиковую прогу на диск:
RANDOMIZE USR 15619:REM:SAVE"IGRA"LINE 10
За ней впритык спишем кодовый блок, после
этого загрузим Disk Doctor и исправим дли-
ну бейсик-файла в секторах с 01 на суммар-
ную длину бейсика и кодов,в первом символе
кодового файла запишем код 00,а в 8-м сек-
торе уменьшим на число кодовых файлов байт
номер #E4. Загрузчик готов,и он даже копи-
руется.Конечно,лучше был бы загрузчик типа
0 INK USR .<а дальше бред>
#6 - запись секторов.
Записать B секторов подряд по тем же
правилам.Следует заметить,что эти процеду-
ры останавливаются по Break и выдают сооб-
щение Disk error и т.д. с очисткой экрана.
Если вам угодно это предупредить, запишите
предварительно по адресу 23746 вместо RET
команду вызова обработчика ошибок.
#7 - показ каталога.
Эквивалентно команде CAT с тем исключе-
нием,что вывод производится в любой поток.
Номер потока задается в A(стандартно - 2).
Желательно записать номер дисковода(23798)
также по адресам(23800)и(23801). Абсолютно
бесполезная команда.
#0 - команда восстановления.
Головка дисковода возвращается на нуле-
вую дорожку.
#1 - смена дисковода и тестирование.
В регистре A должен быть номер дисково-
да:0="A",1="B",2="C",3="D". Тестирование -
определение количества дорожек (40 или 80)
и скорости перемещения магнитной головки -
не производится, если дисковод уже был от-
тестирован.
Следует отметить, что перед использова-
нием этих команд нужно открыть область си-
стемных переменных TR-DOS.Сама по себе она
после сброса не открывается, отчего виснут
все известные мне ассемблеры.Можно войти и
сразу же выйти из DOS, можно также вызвать
процедуру 15649.
Еще неприятной особенностью этих проце-
дур является то,что при чтении/записи сек-
торов подряд они всякий раз делают лишний
оборот диска при переходе на следующую фи-
зическую дорожку.Этот недостаток исправля-
ют многочисленные TURBO-LOADER'ы,использу-
ющие непосредственное обращение к контрол-
леру дисковода КР1818ВГ93.
Программа 15635 имеет и другие функции,
но они мало относятся к нашей теме.Коснусь
напоследок только одной:
#13 - копирование дескриптора.
Копирует 16 байт из адреса HL по адресу
23773 (область дескриптора в системных пе-
ременных TR-DOS). Этой командой можно про-
сматривать коды TR-DOS из программы вне
зависимости от версии системы, не включая
дисковода.
Из журнала ZX-Guide#2, Рязань, Ноябрь 1999
(в текст внесены исправления из Errata IG#8)
TR-DOS
Level 2: КР1818ВГ93
Программирование контроллера.
AlCo
Микросхема ВГ93 - контроллер дисковода.
Она является сердцем интерфейса Beta-Disk,
на основе которого работает всеми нами лю-
бимый TR-DOS;)) (Хотя почему ";)"? Если вы
думаете, что это самая глючная и неудобная
система, то вы и представить себе не може-
те,насколько глючны программы Windows'95 и
Microsoft Office! Одно дело - слышать о их
глюках, и совсем другое - ежедневно стра-
дать от них...)
Сей контроллер в сочетании с Beta disk
интерфейсом поддерживает запись на диск в
двух форматах: FM и MFM (последний исполь-
зуется шире) на не более чем 4 дисковода,
содержащие до 256 дорожек и до двух голо-
вок.Для ламеров нелишне добавить,что обыч-
но используются двухсторонние пятидюймовые
дисководы с числом дорожек от 83 до 87 и
скоростью передачи информации около 30k в
секунду (5 оборотов в секунду при длине
дорожки 6300 байт).
Микросхема занимается управлением дви-
гателями и головками дисководов, снятием
сигналов с датчиков на дисководах и пере-
дачей данных туда-сюда по последовательно-
му порту (не более чем на одном дисководе
сразу).
Для всего этого контроллер имеет неко-
торое количество буферных регистров и до-
статочно сложную систему команд.
Взаимодействие с процессором компьютера
в интерфейсе Beta Disk осуществляется че-
рез следующие порты,соответственно отожде-
ствляющиеся с регистрами контроллера:
31=#1F на запись - Регистр команд
31=#1F на чтение - Регистр состояний
63=#3F двунаправленный - Регистр дорожки
95=#5F двунаправленный - Регистр сектора
127=#7F двунаправленный - Регистр данных
а также особый порт с адресом #FF, пре-
доставляемый самим Beta Disk интерфейсом;
Этот порт называется Системный регистром.
Начнём с него (прямо по книге А.Ларченко и
Н.Родионова "TR-DOS для ..."):
При чтении из системного регистра имеют
смысл только старшие два разряда, которые
отражайт состояние сигналов микроконтрол-
лера DRQ^ (D6) и INTRQ^ (D7). DRQ^ - сиг-
нал,отражающий запрос данных микроконтрол-
лером,INTRQ^ - сигнал окончания выполнения
команды.
Для записи в системный регистр доступны
пять разрядов.
D0,D1 выбор дисковода.Установив соответст-
вующий код,можно выбрать один из четырёх
возможных дисководов:00 для дисковода A,
01 для B, 10 для C и 11 для D;
D2 аппаратный сброс микроконтроллера...
(Если сбросить этот бит, то выполнение
текущей команды прекращается, мотор дис-
ковода глохнет и лампочка гаснет.)
D3 этот разряд блокирует сигнал HLT микро-
контроллера, для нормальной работы в нём
должна быть записана единица; (С другой
стороны, если записать туда единицу, то
вертушка дисковода,заведённая какой-либо
командой, через некоторое время плавно
остановится. А это чаще всего приводит к
зависанию программ чтения с диска в слу-
чае ошибки чтения.Яркий пример:загрузчик
Magic-файла в TR-DOS.)
D4 выбор магнитной головки.Содержимое это-
го разряда напрямую транслируется в дис-
ковод. 0 соответствует первой магнитной
головке или нижней стороне дискеты, 1 -
второй магнитной головке или верхней
стороне;
D6 выбор плотности записи. Сброс разряда
заставляет микроконтроллер работать по
методу модифицированной частотной моду-
ляции (MFM), установка - по методу ча-
стотной модуляции (FM). (Есть данные,что
FM большинством Beta disk'ов не поддер-
живается.)
Команды контроллеру передаются через
регистр команд (31). Команда (кроме команд
принудительного прерывания) будет выполне-
на, только если контроллер не занят выпол-
нением другой команды.Самый простой способ
записать команду в регистр команд - вызва-
ть процедуру TR-DOS 5.03 с адресом 12227:
LD A,<команда>
OUT_31 LD IX,12227
DOS PUSH IX
JP 15663 ;15664 работает не везде!
Для записи в прочие порты контроллера
следует использовать подпрограмму с адре-
сом 10835, содержащую OUT (C),A : RET.
1. Команда восстановления.
Магнитная головка текущего дисковода
отъезжает на нулевую дорожку. Код команды:
%0000hvxx, где h=положение головки (0=под-
нять, 1=опустить, причём при h=0 двигатель
дисковода не заводится), v=режим проверки
номера дорожки (если v=1,то с диска считы-
вается номер дорожки и сравнивается с со-
держимым регистра дорожки),xx=скорость пе-
ремещения головки (x=00 - максимальная).
Рекомендуемый код команды: 8.
Адрес подпрограммы TR-DOS 5.03: 15768
(выход после успешного окончания операции,
либо по BREAK).
2. Принудительное прерывание.
Прерывает выполнение текущей операции
при достижении одного из заданных условий
(условия задаются установкой соответствую-
щих битов в коде команды: %1101jjjj): D0 -
после перехода сигнала CPRDY из низкого
уровня в высокий; D1 - наоборот; D2 - пос-
ле прихода индексного импульса (т.е. обна-
ружено начало дорожки либо просто диск от-
сутствует); D3 - немедленно. Остановка та-
кже произойдёт немедленно, если сбросить
все перечисленные биты.
Рекомендуемый код команды: #D0.
Адрес подпрограммы TR-DOS 5.03: 12225.
3. Команды позиционирования.
Перемещают головку дисковода. Важной их
чертой является то, что они при этом раск-
ручивают вертушку дисковода (что, впрочем,
делает и команда восстановления).
Шаг к центру: %010ihvxx,где h-положение
головки, xx-скорость,v-проверка,i-будет ли
изменяться регистр дорожки. Рекомендуемый
код команды: #58.
Шаг от центра: %011ihvxx. Рекомендуемый
код команды: #68.
Шаг в текущем направлении %001ihvxx.Ре-
комендуемый код команды: #38.
Поиск дорожки: %0001hvxx.Переход на за-
данную дорожку.Номер требуемой дорожки ну-
жно поместить в регистр данных(перед запи-
сью команды,иначе не сработает на турбиро-
ванных ВГ).Рекомендуемый код команды: #18.
4. Команды чтения.
Чтение сектора(-ов): %100msec0, где m=0
(иначе будут обрабатываться все сектора до
конца дорожки. При этом номер каждого сле-
дующего сектора нужно заносить в регистр
сектора. Соответствующего участка подпрог-
рамм TR-DOS найти не удалось),s=номер сто-
роны (0-нижняя,1-верхняя. Принципиально не
важно,используется для проверки),e=задерж-
ка 15 мс между установкой головки в рабо-
чее положение и началом операции, c=прове-
рка указанного номера стороны с номером,
считанным из заголовка сектора(лучше сбро-
сить, поскольку как правило в заголовке
указана неверная сторона). Рекомендуемый
код команды:#80. Адрес подпрограммы TR-DOS
5.03, передающей данные из регистра данных
в память: 16341 (HL=addr,C=#7F,предварите-
льно нужно записать команду в регистр ко-
манд. Если по выходе из программы B=0, это
указывает на ошибку чтения).
Чтение заголовка сектора: %11000e00,где
e=задержка. Считывается 6 байт из первого
попавшегося заголовка сектора:
номер дорожки (0Ў86)
номер стороны (0)
номер сектора (1Ў16)
длина сектора (0,1,2,3 соответствен-
но означает 128,256,512 или 1024 байта)
2 байта Контрольного Кода.
При выполнении этой команды содержимое ре-
гистра дорожки пересылается в регистр сек-
тора.Рекомендуемый код команды: #C0.
Чтение дорожки: %11100e00,где e=задерж-
ка. С дорожки считывается вся информация,
включая пробелы, служебные байты и контро-
льные коды. Таким образом,считанная инфор-
мация не годится для команды "запись доро-
жки" (см.ниже).Ещё один маленький трабл: в
процессе выполнения команды происходит по-
теря синхронизации данных,поэтому несколь-
ко сот байт в конце дорожки остаются неп-
рочитанными(каждый раз разное количество).
Рекомендуемый код операции: #E0.
5. Команды записи.
Запись сектора(-ов): %100mseca,где m=0,
s=0,e=задержка,c=0, a=указывает на один из
двух возможных форматов сектора.В дальней-
шем при считывании этот формат будет инди-
цироваться в 5 бите регистра состояний.
Обычно этот бит обнуляют, при этом в поле
заголовка сектора формируется специальный
байт #FB,в противном случае - байт #F8.Ад-
рес подпрограммы TR-DOS 5.03, передающей
данные из памяти в регистр данных: 16314
(HL=addr, C=#7F,предварительно нужно запи-
сать команду в регистр команд).
Запись(форматирование)дорожки %11110e00
(где e=задержка, которая здесь совершенно
ни к чему). Записывает на текущую дорожку
около 6k разнообразной информации, которая
последовательно передаётся через регистр
данных. Для успешного использования этой
команды нужно знать формат дорожки. Для
примера можно посмотреть подпрограмму фор-
матирования TR-DOS по адресу 8189. А для
начала цитата из книжки:
Эта команда предназначена для разметки
дискеты,то есть для её форматирования. Ин-
формация,посылаемая в микроконтроллер,дол-
жна полностью соответствовать выбранному
формату.Запись автоматически начинается по
приходу индексного импульса, то есть с на-
чала дорожки. В отличие от записи сектора,
количество записываемых байтов не фиксиро-
вано, оно определяется конкретным форматом
дискеты. Часть байтов будет просто записы-
ваться,однако несколько из них интерпрети-
руются в этой команде специальным образом.
Они предназначены для формирования служеб-
ных отметок,таких как адресные маркеры или
контрольные коды (КК).
байт FM MFM
#F5 Не допускается Поле A1 (иниц-я КК)
#F6 Не допускается Синхроимпульс C2
#F7 Зап.двух байт КК \
#F8ЎFB Инициализация КК |-обычные байты
#FC Зап.индексной метки /
#FE Иниц-я КК Адр.метка заголовка
Формат дорожки включает в себя несколь-
ко полей, для правильного формирования ко-
торых необходимо придерживаться определён-
ных стандартов...
Рассмотрим формат дорожки более подроб-
но. Между информационными полями находятся
области пробелов, служащие для синхрониза-
ции внитренних схем микроконтроллера. Чем
больше поля пробелов,тем лучше синхронизи-
руется контроллер, тем меньше сбоев проис-
ходит при передаче информации. В таблице
приведены рекомендуемые значения длин про-
белов, а также значения байта, которым они
заполняются. Уменьшив длину пробелов,можно
получить некоторый выигрыш в объёме за
счёт потери надёжности.(Величина последне-
го пробела (последнего потому,что он пред-
шествует формированию дорожки)особенно ва-
жна.Небольшая длина,установленная авторами
TR-DOS, не позволяет, кроме всего прочего,
считывать первый сектор дискеты на больши-
нстве IBM-совместимых компьютеров.) (Не на
большинстве,а на всех - это ограничение пц
'шного контроллера;причём это никак не за-
висит от копировщика (AMD, Hobeta и т.п.),
который вы используете! Порядочную длину
последнего пробела проставляют программы:
FUT,ADS,DCU 2.21 и некоторые другие.)
Каждый сектор логически состоит из двух
информационных полей: поля заголовка и не-
посредственно следующего за ним поля дан-
ных. В заголовке записана служебная инфор-
мация о секторе,для этого использованы че-
тыре байта. Номер дорожки и номер сектора
идентифицируют конкретный сектор. Принято,
что дорожки считаются с нуля, а сектора с
единицы.. (Логический № сектора получается
вычитанием единицы.)
Каждое информационное поле заканчивает-
ся двумя байтами контрольного кода.
В качестве данных при форматировании
дорожки можно использовать любой не служе-
бный байт, но обычно применяют коды 0 или
#FF.
Порядок следования секторов на дорожке
может быть произвольным. Обычно он кратен
некоторому целому числу,называемому inter-
leaving - чередование.Если это число равно
единице, то сектора расположены последова-
тельно: 0,1,2... Для двух сектора распола-
гаются через 1, для трёх - через 2 и так
далее. Чередование секторов влияет на ско-
рость обращения к диску. После окончания
записи или считывания очередного сектора
программа обычно должна выполнить какие-
либо действия, а на это затрачивается вре-
мя,за которов дискета успевает повернуться
на определённый угол.И к моменту очередно-
го обращения к диску под магнитной голов-
кой может оказаться сектор, далеко отстоя-
щий от предыдущего.Если секторы расположе-
ны последовательно,то при обращении к сле-
дующему сектору программе придётся ждать,
пока дискета сделает целый оборот. При со-
ответствующем чередовании секторов время
ожидания очередного сектора можно свести к
минимуму. (Оптимальный интерлив, обеспечи-
вающий наибольшую скорость через операции
TR-DOS - это единица (сектора расположены
последовательно). Но не стоит думать, что
это единственно возможный или наилучший
вариант. Например, загрузчики с музыкой во
многих демонстрациях рассчитаны под интер-
лив, равный 2 (как раз так форматирует TR-
DOS),а на видеодиске LOCOMOTION (Speed Co)
сектора расположены вообще в обратном по-
рядке ("пессимальный" интерлив).Существует
также константа межтрекового интерлива: на
сколько секторов сдвигается последователь-
ность секторов от дорожки к дорожке (речь
идёт о физических дорожках, которых є87).
Обычно трековый интерлив равен нулю, но на
дисководах Teac, благодаря встроенному в
них искусственному интеллекту, для быстрой
работы турбо-лоадеров необходим трековый
интерлив не меньше двойки. А для подпрог-
рамм TR-DOS (15635=#3D13) оптимальное зна-
чение - около 5 (так форматирует CONSUL).)
Формат дорожки для MFM.
Последний пробел: 80x#4E
~12x0
Поле C2: 3x#F6 \
Индексная метка: 1x#FC |-необязательно
Первый пробел: ~50x#4E|
~12x0 /
Поле A1: 3x#F5
Адр.метка заголовка:1x#FE
Номер дорожки
Номер стороны
Номер сектора
Длина сектора
Формирование КК: 1x#F7
Второй пробел: ~22x#4E (не меньше 12)
~12x0 (не меньше 6)
Поле A1: 3x#F5
Адр.метка данных: 1x#FB
Данные NNxNN
Формирование КК: 1x#F7
Третий пробел: ~54x#4E (не меньше 12)
Четвёртый пробел: NNx#4E
Регистр состояний #1F.
D7 - готовность дисковода
D6 - защита записи (устанавливается всеми
командами, кроме команд чтения)
D5 - в командах записи - индицирует ошибку
записи (честно скажу, штука крайне ред-
кая); в команде чтения сектора - повто-
ряет значение бита a (см. запись секто-
ров)
D4 - ошибка позиционирования (если исполь-
зовалась проверка) или сектор не найден
D3 - ошибка в контрольном коде (либо диск
защищён, либо 1-2 бита прочитались неу-
дачно)
D2 - потеря данных
D1 - в командах позиционирования - индекс-
ный импульс. В других командах - сигнал
на запрос данных либо указание принять
байт из регистра данных
D0 - занято, идёт выполнение команды.
Считать его состояние можно несколькими
обходными способами (специальной подпрог-
раммы чтения портов в TR-DOS 5.03 нет, и
если вы нашли её у себя, значит, у вас не-
стандартная версия ПЗУ).
Способ Н.Родионова:
- Записать 0 в регистр дорожки;
- Записать #0A в регистр сектора;
- Записать 1 в регистр D;
- Вызвать подпрограмму по адресу 16179;
- Взять из рег. B значение рег. состояний;
- Восстановить в регистрах дорожки и сек-
тора исходные значения.
Способ М.Петрова:
- Записать в D номер дорожки;
- Записать в (#5CD8) число, отличное от 0;
- Вызвать подпрограмму #2740;
- Прочитать из (#5CCD) регистр состояний.
В общем, данные сугубо энциклопедичес-
кие. Но продвинутый читатель поймёт. Неко-
торые детали иллюстрирует исходник прог-
раммы MonoLoad.H, помещённый в Барахло.
Подпрограммы TR-DOS проще всего иссле-
довать в STS 5.1 (только не 6.2), выбрав в
Setup:ROM=DOS. Только учтите глюк в STS: в
DOS неверно трассируются команды LD r,(HL)
и им подобные.
Литература.
1. А.Ларченко,Н.Родионов. TR-DOS для поль-
зователей и программистов(Питер)
2. Макс Петров о TR-DOS'е(ZX-Format#5)
3. PAUL ATRIDES."doc_help"(Spectrophoby#6)
Из журнала ZX-Guide#3, Рязань, 03.12.2000
TR-DOS: Level 3.
Basil & AlCo
------------------------------------------
#3D13 с обработкой ошибок.
AlCo
На этот раз я поведаю вам о некоторых
тонкостях TR-DOS, связанных с написанием
системных программ (в которых вроде как
обязательно должна использоваться станда-
ртная точка входа TR-DOS по адресу #3D13).
А Basil напишет о тонкостях, связанных с
написанием boot'ов.
1. Смена дисководов.
Следует ориентироваться на версию 5.03
как на низшую из распространённых (сейчас
у меня она прошита).
Способ таков: записываем во все систем-
ные переменные, связанные с номером диско-
вода, нужное нам значение, и обнуляем для
всех дисководов параметр "время перемещения
головки"(в TR-DOS 5.03 этот параметр поче-
му-то не инициализируется). Способ также
хорош тем, что позволяет использовать 26
дисководов (от A до Z).Только я ещё не ви-
дел версию TR-DOS, которая бы поддерживала
более 4 дисководов...
Короче,вот к чему я клоню (это подпрог-
рамма из AC Edit v0.48):
2. Собственно драйвер
#3D13 с обработкой ошибок.
SYSBUF EQU #6000 ;килобайтный буфер
INIT ... ;должно быть В САМОМ НАЧАЛЕ
LD HL,ONERROR ;?
LD (23747),HL ;? по адресу 23746
... ?стояло RET, а мы
?ставим JP ONERROR
EM15635 LD A,195 ;?(чтобы перехваты-
LD (23746),A ;? вать ошибки)
LD (EMSP+1),SP
PUSH BC
PUSH DE
PUSH HL
DRIVE LD A,"A"
SUB 65
LD (23833),A ;"д-вод по умолчанию"
LD (23798),A ;"д-вод для временной
;операции"
OR #3C
LD (23830),A ;"копия системного
;регистра"
LD HL,#5C00 ;придётся запомнить
LD DE,SYSBUF ;сис. переменные в
LD BC,#400 ;буфере, иначе после
;No Disk будет облом
DI
LDIR
LD H,L
LD (23823),HL ;"код ошибки TR-DOS"
;- рекомендуют обнулять
LD (23802),HL ;"время перемещения
LD (23804),HL ;головок"
POP HL
POP DE
POP BC
LD SP,#5F00
CALL 15635
EMSP LD SP,0
EMQ LD HL,HL4
LD (23746),HL ;снова ставится RET
RET
ONERROR
HL4 EQU $*256+201
LD (ERRDE+1),DE
EX (SP),HL ;что это такое хотел
LD DE,8020 ;вызвать TR-DOS?
OR A
SBC HL,DE
JR NZ,ERRSP
POP HL ;если это вызывалась
ERRDE LD DE,0 ;проверка на BREAK,
SCF ;то возвращаем ему,
RET ;что BREAK не нажат
ERRSP LD SP,0 ;это уже не BREAK,
LD HL,SYSBUF ;а кое-что похлеще...
LD DE,#5C00
LD BC,#400
DI
LDIR
EI
CALL DEPKFNT ;производятся всякие
LD HL,SAVERR ;действия,связанные с
CALL WINDTXT ;обработкой ошибок
CALL IYPWINQ ;(вывод окошка
CALL EDUPAGE ;с ошибкой
OR A ;и т.д.)
RET
Пользуются ею так же,как и обычным CALL
15635.Только в (ERRSP+1) перед вызовом ну-
жно записать регистр SP.Или,возможно,SP+2.
Или SP-2. Зависит от уровня вложенности и
от того,куда вы хотите попасть после обра-
ботки ошибки.
3. Командная строка.
В журнале Adventurer #5 писали, что ко-
мандную строку TR-DOS нельзя использовать,
поскольку она затирается...
TR-DOS при запуске BASIC-файла стирает
первые 7 символов командной строки и заме-
няет их на 0D,80,00,00,40,5D,00.(При запу-
ске кодовых файлов она не затирается сов-
сем). Для использования в программах я бы
рекомендовал следующий формат командной
строки:
RUN "name",param
После имени файла (кстати, не менее 3
букв) следует запятая - потому что так бу-
дет больше похоже на формат остальных ко-
манд TR-DOS, и,к тому же,системе наплевать
на всё то,что написано после запятой. Опыт
использования командной строки без разде-
лителей был произведён Бобом Клубовым в
программе "VIEW ZXW" (1996 г, приложение к
журналу ZX-News #3). Это не есть рулез,так
как, встретив после имени файла один из
следующих символов:
", #, -, +, *, /, (, <, =, >, CODE, DATA,
TR-DOS отказывается исполнять командную
строку.
Впервые командная строка использовалась
вроде бы в Z00LOOK'овской версии ECHOLOGY.
То,что сделано в INSULT,трудно назвать оп-
росом командной строки.
------------------------------------------
Пишем boot...
Basil
Как включить 48-ой Basic
со 128-ой памятью?
Это по правде делается очень просто:
DI
LD IY,23610
LD HL,4867
PUSH HL
LD (23613),SP
LD HL,(23631)
LD DE,15
ADD HL,DE
LD DE,5566
EX DE,HL
LD BC,4
LDIR
RES 4,(IY+1)
LD HL,7030
PUSH HL
IM 1
EI
LD HL,10072
EXX
RET
Теперь смело можете выходить в Basic (род-
ной - 48-ой) и радоваться 128-ой памяти.
Выход в 128-меню Basic'а:
XOR A
LD BC,32765
OUT (C),A
RST 0
Очистка 48-ой памяти и запуск boot'а:
LD HL,71
PUSH HL
LD H,L
JP 15663
или
LD HL,0
PUSH HL
JP 15663
А теперь уникальная процедура, которые
многие ищут,теряют,находят вновь...да нет,
ребята, я не про любовь, а про процедуру
запуска Basic файлов (для 48-128кб) из ма-
шинных кодов:
ORG #XXXX
LD HL,#YYYY ;16 BYTES INFO
LD DE,23773 ;ABOUT
LD BC,16 ;BASIC FILE.
LDIR
LD A,#C9
LD (23746),A
LD HL,RNBASIC
PUSH HL
LD DE,23448
LD BC,END_COD-RNBASIC
LDIR
LD HL,10072 ;ДЛЯ
EXX ;ВЫХОДА
LD IY,23610 ; В
IM 1 ;BASIC.
EI
RET
RNBASIC
DISP 23448
LD HL,23867
LD DE,23868
LD (HL),0
LD BC,41668
LDIR ;ОЧИСТКА 48-Й ПАМЯТИ
LD A,#38 ;PAPER 7
LD (23693),A ;& INK 0.
LD (23624),A ;И ДВЕ НИЖНИЕ СТРОКИ
;ЭКРАНА ТАКИЕ ЖЕ.
LD BC,65167 ;CLEAR 65167 ДЛЯ
CALL 7863 ;МАКСИМАЛЬНО ДЛИННЫХ
;ФАЙЛОВ.
LD HL,4867 ;ПОДКЛЮЧЕНИЕ
PUSH HL ;
LD (23613),SP ;
LD HL,(23631) ;48-ГО
LD DE,15 ;
ADD HL,DE ;
LD DE,5566 ;BASIC'А
EX DE,HL ;
LD BC,4 ;
LDIR ;СО 128-ОЙ
RES 4,(IY+1) ;
LD HL,7030 ;
PUSH HL ;ПАМЯТЬЮ.
LD HL,RNBAS2
PUSH HL
LD HL,10608 ;
PUSH HL ;
LD HL,10528 ;
PUSH HL ;ЗАГРУЗКА.
LD HL,10570 ;
PUSH HL ;
JP 15663 ;
RNBAS2 LD HL,(23649)
LD A,(HL)
DEC HL
LD L,(HL)
LD H,A
LD (23618),HL ;СТРОКА АВТОСТАРТА
XOR A ;ВЫПОЛНЯЕТСЯ С
LD (23620),A ;1-ОЙ КОМАНДЫ.
RET
ENT
END_COD
P.S. Эта процедура сама загрузит и запус-
тит Basic файл. Для ускорения процесса
нужно перед запуском этой проги передви-
нуть головку на трек запускаемого Basic'а
(через #3D2F).
P.P.S. Эта процедура запустит любую 48-ю
прогу, кроме той,которая проверяет наличие
страниц памяти или защелку порта #7FFD и
назло не запускается,или той,которая,непо-
нятно для чего, использует порт #FD.
Проверка диска в дисководе:
DISK_CP LD A,#C3
LD (23746),A
LD HL,DSK_CP2
LD (23747),HL
LD HL,#2FC1
CALL DOS
LD A,8
LD L,#C3
CALL DOS
LD (STK),SP
LD D,0
LD HL,#2740
CALL DOS
DSK_CP2 LD SP,0
STK EQU $-2
LD HL,#1FF3
CALL DOS
LD L,#EB
CALL DOS
LD HL,#2FBC
CALL DOS
LD A,(23757)
AND 64
LD (VARPROT),A
RET
DOS PUSH HL
JP #3D2F
Объясняю: вообще-то проверка диска в
дисководе - это проверка на наличие защиты
у диска. (Ред: можно комбинировать с про-
веркой индексного отверстия)
Проверка для незащищенного диска проис-
ходит так:
1) Диск в дисководе - защита отсутствует;
2) Диск вынимается (индикатор определяет)
- диск защищен;
3) Диск вынут - диск не защищен;
4) Диск вставляется - диск защищен;
5) Диск вставлен - диск не защищен.
У защищенного так:
1) Диск в дисководе и диск вынимается -
определяется как одна фаза: диск защищен;
2) Диск вынут - диск не защищен;
3) Диск вставляется и диск вставлен -
определяется как одна фаза: диск защищен.
Сразу, как только загрузилась с диска
ваша прога с этой проверкой, надо опреде-
лить - сколько фаз требуется для этого ди-
ска (диск может быть защищенным или неза-
щищенным).
Вообще-то, вы, наверное, и сами видели
программы с некорректной проверкой диска в
дисководе. По правде сказать, здесь есть
только (только!!) два правильных варианта:
1) Считывание каталога с диска сразу при
первом же изменении для защищенного и при
втором для незащищенного диска (чтобы счи-
тывание у обоих дисков происходило одина-
ково - когда диск полностью отсутствует в
дисководе);
2) Считывание каталога при втором измене-
нии для защищенного или при третьем для
незащищенного диска (каталог считывается,
как только начинают вставлять новый диск).
Вариант 1:
( вариант 2 - то же самое, только вместо
"LD A,2" должно стоять "LD A,3" )
INIT CALL READING CAT.
CALL DISK_CP
LD A,(VARPROT) ;В СЛУЧАЕ ЗАЩИТЫ.
LD (VARPROT+2),A
AND A
LD A,2
JR Z,NO_PROT ;Z - НЕ ЗАЩИЩЕН.
DEC A
NO_PROT LD (COLFAZ),A ;КОЛ-ВО ФАЗ.
ZAMOK CALL DISK_CP
VARPROT EQU $+1
LD A,0
CP 0
JR NZ,DALEE
LD (VARPROT+2),A
COLFAZ EQU $+1
LD A,0
DEC A
LD (COLFAZ),A
JR NZ,DALEE
JP INIT
DALEE Это может быть опрос клавиш или
что-то еще, в конце переходит на "ZAMOK"
...
...
...
JP ZAMOK
TurboMonoLoader & Loader with Int.
ORG 25000
DI
LD B,160
LD HL,26000
R_FROM LD DE,0
NXT_TRA LD C,D
LD A,(23833)
AND 3
OR #2C
SRL C
JR C,$+4
OR 16
LD IX,8179 ;OUT (#FF),A
CALL DOS
LD A,C
LD C,#7F
LD IX,10835 ;OUT (C),A
CALL DOS
LD A,#18
LD C,#1F
CALL DOS
LD A,E
INC A
LD C,#5F
CALL DOS
PUSH BC
PUSH DE
READSEC PUSH HL
LD A,#80
LD C,#1F
LD IX,10835 ;OUT (C),A
CALL DOS
LD BC,#017F
LD IX,16343
CALL DOS
POP HL
DJNZ READSEC
POP DE
INC H
LD A,E
INC A
AND #0F
LD E,A
JR NZ,$+3
INC D
POP BC
DJNZ NXT_TRA
LD (R_FROM+1),DE
RET
DOS PUSH IX
JP 15663
В данном варианте этот loader будет ра-
ботать как turbo, если диск форматирован с
нужным для вашего дисковода смещением (у
Teac - 02, у Robotron - 00, а у вашего ти-
рана может быть до 04),а прерывания запре-
щены.Но если разрешить прерывания - ничего
страшного,эта прога будет и с ними коррек-
тно работать,как и процедуры по прерывани-
ям, только, правда,турбы вы не увидите,тем
более, чем больше тактов занимает прерыва-
ние, тем медленнее считывание.
P.S. Если этой проге встретился ERROR'ис-
тый сектор,то она будет его считывать,пока
не считает. Если считать невозможно, то ее
можно остановить сбросом компьютера с ка-
кой-нибудь вышки или отключением электри-
чества в вашем районе.
------------------------------------------
Иван Рощин
Дополненная версия. Дата последнего редактирования: 10.11.2002.
В TR-DOS каждому файлу ставится в соответствие 16-байтовый описатель,
находящийся в каталоге диска. Как видно из табл. 1, в этом описателе
для расширения файла предусмотрен лишь один символ.
Табл. 1
Смещение от начала Длина Комментарий
0 8 Имя файла
8 1 Расширение файла
9 2 Стартовый адрес файла
(для BASIC — длина программы и переменных)
11 2 Длина файла в байтах
(для BASIC — длина программы без учёта длины переменных)
13 1 Длина файла в секторах
14 1 Номер сектора начала файла
15 1 Номер дорожки начала файла
Но в последнее время всё шире используется более информативное
трёхсимвольное расширение файла (как в MS-DOS):
Табл. 2
Смещение от начала Длина Комментарий
0 8 Имя файла
8 3 Расширение файла
11 2 Длина файла в байтах
13 1 Длина файла в секторах
14 1 Номер сектора начала файла
15 1 Номер дорожки начала файла
Видно, что длина расширения увеличилась за счёт использования
двух байтов, в которых раньше хранился стартовый адрес файла.
Если вы пишете какую-либо программу для работы с файлами,
неплохо было бы предусмотреть в ней вывод трёхсимвольных расширений.
Но тут появляется проблема: заранее не известно, с какими файлами
придётся иметь дело, а расширение у файлов может быть как
трёхсимвольным, так и односимвольным. Таким образом, для
каждого конкретного файла ваша программа должна правильно
определить длину расширения. Если же считать расширение
всегда трёхсимвольным, то каталог диска будет выглядеть
неаккуратно:
boot B±
ZX_WIN ZIP
******** ZIP
S_PLAY2d BБ
BACKUM GTR
BUZZ16_1 MPS
FL_SH_EI MPS
KL_F_CUT m!ћ
SPY MPS
VIVID MEљ
NOSTALGY Y ђ
s_play2d txt
Попробуем сделать так: определим, какие последовательности
из трёх байтов являются допустимыми трёхсимвольными
расширениями, и если в описателе файла окажется
недопустимая последовательность, то будем считать,
что у этого файла односимвольное расширение.
Сначала перечислим список требований,
которым должно удовлетворять трёхсимвольное расширение:
Все три символа — коды в диапазоне #20..#7F.
В расширении не может быть одновременно прописных
и строчных латинских букв.
Несмотря на пункт 2, расширения XAS, xAS, XaS, xaS
являются допустимыми
(файлы с такими расширениями создаёт ассемблер XAS).
Если первый символ — «B», то расширение не может быть трёхсимвольным,
потому что у BASIC-файлов следующие два байта содержат
длину программы и переменных и не могут являться
символами расширения.
Теперь рассмотрим процедуру, определяющую длину расширения
файла в соответствии с этими требованиями.
Процедура рассчитана на компиляцию в
ассемблере ZX ASM 3.10, и при компиляции в другом
ассемблере вам может потребоваться заменить
команду «PUSH HL,DE,BC» на три команды:
«PUSH HL», «PUSH DE» и «PUSH BC». Соответственно,
команду «POP BC,DE,HL» тоже нужно будет заменить
на три команды: «POP BC», «POP DE» и «POP HL».
Входные параметры: HL указывает на первый символ
расширения. Регистр H не должен быть равен нулю.
Выходные параметры: регистры остаются без изменений
(кроме аккумулятора), а флаг Z несёт информацию о
длине расширения: если флаг установлен — расширение
трёхсимвольное; если сброшен — односимвольное.
Длина процедуры всего семьдесят два байта, она
максимально оптимизирована, и, по-видимому, сделать
её короче без ущерба для функциональности не получится.
Текст процедуры:
ANALYS_EXT PUSH HL,DE,BC
;Проверка на BASIC:
LD A,(HL)
CP "B"
JR Z,ANALYS_NO
;Проверка на XAS, xAS, XaS, xaS:
RES 5,A
CP "X"
JR NZ,ANALYS_EX1
INC HL
LD A,(HL)
RES 5,A
CP "A"
JR NZ,ANALYS_EX2
INC HL
LD A,(HL)
CP "S"
JR Z,ANALYS_YES
DEC HL
ANALYS_EX2 DEC HL
;Изначально D=#FF, E=0. Как только
;встречаем символ A..Z или a..z,
;добавляем его к D по AND и к E по OR.
;В пятом бите 0, если буква прописная,
;и 1, если буква строчная. Допустимы
;лишь варианты 0,0,0 и 1,1,1. Если
;вариант 0,0,0 (все буквы прописные), то
;5-й бит D = 0 и 5-й бит E = 0. Если
;вариант 1,1,1 (все буквы строчные), то
;5-й бит D = 1 и 5-й бит E = 1. Для
;других вариантов (когда есть и 0, и 1)
;5-й бит D = 0 и 5-й бит E = 1. Видим,
;что для вариантов 0,0,0 и 1,1,1 5-й бит
;от D XOR E будет равен 0, а для других
;вариантов он будет равен 1, таким
;образом, можно отличать допустимые
;варианты от недопустимых.
ANALYS_EX1 LD DE,#FF00
LD BC,3*256+%00100000
ANALYS_EX3 LD A,(HL)
CP C ;=CP #20
JR C,ANALYS_NO
CP #80
JR NC,ANALYS_NO;не #20..#7F
OR C
CP "a"
JR C,ANALYS_EX4
CP "z"+1
JR NC,ANALYS_EX4
LD A,(HL)
AND D
LD D,A
LD A,(HL)
OR E
LD E,A
ANALYS_EX4 INC HL
DJNZ ANALYS_EX3
LD A,D
XOR E
AND C
JR Z,ANALYS_YES
ANALYS_NO OR H ;Сбрасываем флаг Z
;(т.к. H<>0, то и
;результат операции
;OR будет <> 0).
;При передаче управления на ANALYS_YES
;по JR - флаг Z всегда будет установлен!
ANALYS_YES POP BC,DE,HL
RET
Вот, например, как выглядит каталог диска,
где для каждого файла длина расширения
определялась с помощью этой процедуры:
boot B
ZX_WIN ZIP
******** ZIP
S_PLAY2d B
BACKUM GTR
BUZZ16_1 MPS
FL_SH_EI MPS
KL_F_CUT m
SPY MPS
VIVID M
NOSTALGY Y
s_play2d txt
Теперь хотелось бы сказать несколько слов об имени диска.
В TR-DOS под имя диска отводится восемь символов, а само
имя расположено по смещению #F5 в служебном (девятом)
секторе нулевой дорожки.
Как видим, в этом секторе остаются свободными ещё три
байта, а значит, имя диска может быть увеличено до
одиннадцати символов. Действительно, 11-символьное
имя довольно часто используют. Но тут есть один нюанс…
Если вы хотите присвоить диску 11-символьное имя, то
старший бит последнего байта обязательно должен быть
установлен. Дело в том, что подпрограмма печати
строки в TR-DOS (которая используется для вывода
имени диска при выполнении команды CAT) заканчивает
печатать строку или при встрече нулевого байта, или
когда старший бит последнего напечатанного символа
был установлен (при печати символа старший бит сбрасывается).
Если печатается обычное имя из восьми символов, всё
проходит нормально, так как за ним следуют три
нулевых байта, а вот при печати 11-символьного имени,
если старший бит последнего байта не установлен,
то процедура не остановится, напечатав имя,
а будет печатать дальше и дальше, заполняя экран мусором.
Соответственно, если вы пишете программу для работы
с диском и хотите поддержать 11-символьное имя диска,
то при печати последнего символа имени
ваша процедура печати должна сбрасывать его старший бит.
Дополнение
Как выяснилось, одно из требований к трёхсимвольному расширению
(ранее сформулированное так: все три символа — коды в диапазоне #20..#7F)
нуждается в некотором уточнении.
Во-первых, символ с кодом 96 (обратная кавычка)
не является допустимым и, следовательно,
должен быть исключён из данного диапазона.
Во-вторых (об этом мне сообщил Max Vasilyev),
трёхсимвольное расширение не должно начинаться с пробела
(например, « ab») или содержать вторым символом пробел («a b»).
Иначе говоря, пробелом может быть лишь последний символ расширения.
Соответственно, процедура определения длины
расширения файла теперь должна выглядеть так:
ANALYS_EXT PUSH HL,DE,BC
LD BC,3*256+%00100000
;Проверка на BASIC:
LD A,(HL)
CP "B"
JR Z,ANALYS_NO
;Проверка: первый или второй символ - пробел?
INC HL
LD A,(HL)
CP C ;=CP #20
JR Z,ANALYS_NO
DEC HL
LD A,(HL)
CP C ;=CP #20
JR Z,ANALYS_NO
;Проверка на XAS, xAS, XaS, xaS:
RES 5,A
CP "X"
JR NZ,ANALYS_EX1
INC HL
LD A,(HL)
RES 5,A
CP "A"
JR NZ,ANALYS_EX2
INC HL
LD A,(HL)
CP "S"
JR Z,ANALYS_YES
DEC HL
ANALYS_EX2 DEC HL
ANALYS_EX1 LD DE,#FF00
ANALYS_EX3 LD A,(HL)
CP C ;=CP #20
JR C,ANALYS_NO
CP #80
JR NC,ANALYS_NO ;не #20..#7F
CP 96
JR Z,ANALYS_NO ;"`"
OR C
CP "a"
JR C,ANALYS_EX4
CP "z"+1
JR NC,ANALYS_EX4
LD A,(HL)
AND D
LD D,A
LD A,(HL)
OR E
LD E,A
ANALYS_EX4 INC HL
DJNZ ANALYS_EX3
LD A,D
XOR E
AND C
JR Z,ANALYS_YES
ANALYS_NO OR H
ANALYS_YES POP BC,DE,HL
RET
В регистровой паре HL, напомню, должен задаваться
адрес первого символа расширения (H<>0!),
а результат работы процедуры возвращается во флаге Z
(установлен — расширение трёхсимвольное, сброшен — односимвольное).
(C) Иван Рощин, Москва
TR-DOS: как не допустить ошибки?
(«ZX-Ревю» 5-6/1997)
Дата последнего редактирования: 12.11.2002.
Вы удивитесь, если узнаете, сколько программ не
распознают ошибки при работе с диском, неправильно
распознают их, а также зависают или сбрасываются
при их возникновении. Разумеется, при написании
собственных программ таких ситуаций следует избегать,
хотя это не так-то просто. Дело в том, что корректная
обработка ошибок становится возможной лишь при работе
напрямую с микроконтроллером дискового интерфейса,
а это доступно далеко не каждому. Несмотря на это,
надёжность программ можно значительно увеличить,
если использовать наряду с обычными функциями TR-DOS
специальные процедуры для распознавания ошибочных
ситуаций.
В этой статье пойдёт речь об одной из самых
распространённых ошибок ввода-вывода — отсутствии
диска в дисководе. Также будет сказано несколько
слов о прямом программировании ВГ93 и о программах,
контролирующих смену диска.
Известно, что перед началом любой дисковой
операции, связанной с чтением или записью данных,
необходимо, чтобы диск был установлен в дисковод
и дверца дисковода была закрыта. Если это условие
не выполняется, программа (если она грамотно написана)
обычно выдаёт одно из следующих сообщений:
No disk.
Disk not present.
Disk not ready.
Специальная процедура для определения наличия
диска в дисководе была бы очень полезной. Приведу
лишь один из возможных примеров её использования:
если ваша программа использует интерпретатор
системных функций TR-DOS и не осуществляет
перехват ошибок, то при их возникновении
(в частности, при отсутствии диска) может
произойти сброс компьютера (что очень нежелательно).
Но если с помощью указанной процедуры обнаружить
отсутствие диска ещё до вызова интерпретатора, можно
избежать неприятных последствий
(конечно, если не произойдёт какой-нибудь другой ошибки).
Рассмотрим несколько возможных способов
реализации такой процедуры:
Пробуем прочитать какой-нибудь сектор
(естественно, не с помощью вызова интерпретатора системных функций,
а при прямом программировании контроллера).
Так как считанная информация не понадобится, чтение можно
проводить, скажем, в область ПЗУ.
В случае неудачи считаем, что диска нет.
Недостатки: неудачное считывание сектора ещё не значит,
что диск отсутствует. Вполне может быть, что сектор
был записан с ошибкой, или сектора с таким номером
вообще нет на дорожке (т.к. в общем случае формат
диска может быть произвольным).
Достоинства: при работе с диском
фиксированного формата этот способ вполне подходит.
Делаем попытку прочитать первый встреченный
заголовок сектора.
В случае неудачи считаем, что диска нет.
Недостатки: в общем случае диск может
быть и неформатированным, при этом ничего
прочитать не удастся, и мы получим неверный результат.
Достоинства: этот способ годится для
любого форматированного диска и достаточно
прост в реализации.
Используем регистр состояния микроконтроллера.
Прежде чем говорить о достоинствах и недостатках
этого метода, вспомним, что такое регистр состояния и
что можно узнать с его помощью.
Регистр состояния отражает корректность заданной
команды, а также состояние микроконтроллера при её выполнении.
Каждый бит в нём указывает на определённый параметр и связан
с выполнением конкретной команды. При выполнении команд
восстановления и позиционирования биты регистра состояния
имеют следующее назначение:
0 — занято, идёт выполнение команды.
1 — индексный импульс.
2 — магнитная головка находится в исходном положении.
3 — ошибка в контрольном коде.
4 — ошибка позиционирования.
5 — магнитная головка находится в рабочем положении.
6 — защита записи.
7 — указывает на готовность дисковода к выполнению команды.
Нас будут интересовать лишь первый и шестой биты
регистра состояния, поэтому поговорим о них подробнее.
Если посмотреть на пятидюймовую дискету, можно увидеть
большое отверстие в центре, а рядом с ним — маленькое круглое
отверстие в корпусе дискеты и отверстие чуть меньшего
диаметра на самом магнитном диске. Это так называемое
индексное отверстие, которое служит для ориентации магнитной
головки дисковода на дискете. Когда отверстия в корпусе и
на самом диске совпадают (а это происходит при каждом обороте диска),
контроллер считает, что магнитная головка находится в начале дорожки.
Значение первого бита регистра состояния
определяется состоянием индексного отверстия:
Табл. 1 Ситуация Значение первого бита
Диска нет. 1
Диск есть, отверстия в корпусе и на самом диске совпадают. 1
Диск есть, отверстия в корпусе и на самом диске не совпадают. 0
Шестой бит регистра состояния отражает
состояние прорези для защиты записи:
Табл. 2 Ситуация Значение шестого бита
Диска нет. 0
Диск есть, прорезь открыта. 0
Диск есть, прорезь закрыта. 1
Ясно, что если первый бит равен 0 или шестой бит равен 1,
диск присутствует.
Но, во-первых, при наличии диска эти биты могут принимать,
вообще говоря, любые значения, а во-вторых, надо ещё узнать,
закрыта ли дверца дисковода. Как же определить её состояние?
Если она закрыта и двигатель дисковода включен,
то диск будет вращаться.
Если же дверца открыта, диск не будет вращаться.
Ну а как определить, вращается диск или нет, мы уже знаем:
нужно циклически проверять значение первого
бита регистра состояния.
При каждом обороте диска этот бит меняет своё значение с 0
(отверстие на корпусе диска не совпадает с отверстием на диске)
на 1 (отверстия совпадают), и далее — опять на 0.
Если этот бит не будет менять своего значения — либо
в дисководе нет диска, либо дверца открыта.
Теперь мы легко можем указать искомый алгоритм:
прочитать значение регистра состояния. Пусть s1 —
значение первого бита, s2 — значение шестого бита.
пусть s3=1, если диск вращается, и 0,
если диск не вращается.
определить ситуацию по таблице:
Табл. 3 s1 s2 s3 Номер ситуации
0 0 0 2
0 0 1 3
0 1 0 2
0 1 1 3
1 0 0 1
1 0 1 3
1 1 0 2
1 1 1 3
Расшифровка номера ситуации:
1 — в дисководе нет диска.
2 — диск есть, но дверца дисковода не закрыта.
3 — диск есть и дверца дисковода закрыта.
Примечание: с вероятностью примерно 3% ситуация 2 может
быть распознана как ситуация 1 — это происходит,
если индексное отверстие на диске случайно оказывается
как раз напротив отверстия на корпусе дискеты.
Ну что же, осталось только привести текст процедуры на ассемблере:
;***********************************************
;Процедура D_READY определяет, вставлен ли
;диск в дисковод и закрыта ли дверца дисковода.
;В регистре A возвращается число 1, 2 или 3,
;обозначающее номер ситуации:
;
; 1 - в дисководе нет диска.
; 2 - диск есть, но дверца дисковода не
; закрыта.
; 3 - диск есть и дверца дисковода закрыта.
;
; Примечание: с вероятностью примерно 3%
;ситуация 2 может быть распознана как
;ситуация 1.
D_READY XOR A ;Устанавливаем один и тот же
LD C,#3F ;номер цилиндра
CALL TO_WG93 ;в регистре дорожки
LD C,#7F ;и в регистре данных.
CALL TO_WG93
LD A,#18 ;Позиционирование. Головка не будет
CALL TO_1F ;двигаться, но двигатель включится.
CALL READY ;Ждем выполнения...
CALL STATUS ;Читаем регистр состояния
LD B,A ;и сохраняем в регистре B.
;Теперь читаем регистр состояния в цикле,
;чтобы определить, вращается ли диск.
;Если #300 раз будет считано одно и то же
;значение, значит, диск не вращается.
;
; Примечание: число #300 подобрано опытным путем,
;исходя из максимального времени работы цикла
;(это чуть больше времени одного оборота диска,
;или 200 ms). Если процессор работает быстрее или
;диск вращается медленнее, число нужно увеличить.
LD HL,#300 ;счетчик
LOOP_D PUSH HL
PUSH BC
CALL STATUS
POP BC
POP HL
DEC HL
CP B ;Сравниваем считанное и ранее
;полученное значения.
LD A,1 ;Формируем 0-й бит регистра A.
JR NZ,DISK_R ;Если диск крутится.
LD A,H
OR L
JR NZ,LOOP_D ;Продолжаем считывать значение...
;Формируем в регистре A байт с таким содержимым:
;
; Бит 0: 0 - диск не крутится, 1 - крутится.
; Бит 1: то же, что было в 6-м бите регистра состояния.
; Бит 2: то же, что было в 1-м бите регистра состояния.
;
;В результате в регистре A получаем число от 0 до 7,
;и далее определяем номер ситуации по таблице.
DISK_R BIT 6,B
JR Z,READY1
SET 1,A
;Установлено значение 1-го бита.
READY1 BIT 1,B
JR Z,READY2
SET 2,A
;В A получено нужное значение.
READY2 LD (THIS_B+2),A ;Модифицируем команду.
;Но сначала отключаем дисковод:
XOR A ;Эти команды,
CALL TO_1F ;в принципе,
LD A,#D0 ;можно
CALL TO_1F ;выбросить.
;Эта команда соответствует LD A,(IX+0)...LD A,(IX+7):
LD IX,TABL
THIS_B LD A,(IX) ;Получили номер ситуации.
EI
RET
;Таблица для определения номера ситуации:
TABL DB 2,3,2,3,1,3,2,3
;***************************************
;Вспомогательные процедуры:
TO_1F LD C,#1F
TO_WG93 LD IX,#2A53
JR TO_DOS
READY LD IX,#3EF5
TO_DOS PUSH IX
JP #3D2F
;***************************************
;Процедура STATUS возвращает содержимое
;регистра состояния.
;Вход: A - содержимое регистра дорожки,
; B - содержимое регистра сектора,
; которые будут установлены после
; выхода из процедуры.
;Выход: A - значение, считанное из порта #1F.
;Прерывания после выхода запрещены!
STATUS DI
LD C,#7F ;A=N цилиндра.
CALL TO_WG93 ;В регистр данных.
LD (RG_D+1),A ;дорожка
LD A,B
LD (RG_S+1),A ;сектор
;Сохраняем содержимое ячеек, которые
;могут быть испорчены:
LD A,(#5D0E)
LD (ST1+1),A
LD A,(#5D0C)
LD (ST2+1),A
LD A,(#5CB6)
LD (ST3+1),A
LD A,(#5D1F)
LD (ST4+1),A
LD A,(#5C3A)
LD (ST5+1),A
LD A,(#5D17)
LD (ST6+1),A
LD HL,(#5D1A)
LD (ST7+1),HL
LD HL,(#5D1C)
LD (ST8+1),HL
LD HL,(#5CF8)
LD (ST9+1),HL
;Устанавливаем содержимое некоторых
;ячеек для правильной работы:
LD A,#FF
LD (#5D0C),A
LD (#5D1F),A
DEC A
LD (#5D0E),A
LD A,#F4
LD (#5CB6),A
LD HL,S_SPEC
LD (#5D1A),HL
LD HL,0
ADD HL,SP
LD DE,-12
ADD HL,DE
LD (#5D1C),HL
LD A,0 ;0 в регистр
LD C,#3F ;дорожки.
CALL TO_WG93
LD A,#0A ;#A в регистр
LD C,#5F ;сектора.
CALL TO_WG93
LD D,1
LD IX,16179
CALL TO_DOS ;Определили #1F.
;Теперь восстанавливаем содержимое
;регистров дорожки и сектора:
RG_D LD A,0
LD C,#3F
CALL TO_WG93
RG_S LD A,0
LD C,#5F
CALL TO_WG93
;Восстанавливаем ранее запомненное
;содержимое ячеек:
ST1 LD A,0
LD (#5D0E),A
ST2 LD A,0
LD (#5D0C),A
ST3 LD A,0
LD (#5CB6),A
ST4 LD A,0
LD (#5D1F),A
ST5 LD A,0
LD (#5C3A),A
ST6 LD A,0
LD (#5D17),A
ST7 LD HL,0
LD (#5D1A),HL
ST8 LD HL,0
LD (#5D1C),HL
ST9 LD HL,0
LD (#5CF8),HL
LD A,B
RET
;Сюда будет передано управление, если
;0-й бит регистра состояния равен 1:
S_SPEC POP BC ;Содержимое порта.
LD HL,(#5D1C)
LD DE,12 ;Восстанавливаем
ADD HL,DE ;указатель
LD SP,HL ;стека.
JR RG_D
* * *
Вам наверняка встречались программы, контролирующие
смену диска в дисководе (например, Jemmini Commander).
В них проверяются именно указанные выше биты системного
регистра (в принципе, достаточно следить за изменением лишь
какого-нибудь одного из двух битов).
Вот простейший алгоритм работы такой программы:
Прочитать значение регистра состояния.
Если первый бит равен 1, перейти к шагу 4.
N="Диск есть".
Прочитать значение регистра состояния.
Если первый бит равен 0, перейти к шагу 3.
N="Диска нет".
Прочитать значение регистра состояния.
Если первый бит равен 1, перейти к шагу 5, иначе к шагу 2.
Примечание: программа работоспособна при условии,
что индексное отверстие на диске не находится напротив
соответствующего отверстия на корпусе дискеты
(это связано с использованием первого бита регистра состояния).
Кстати, у такой программы есть одна особенность,
связанная с чтением регистра состояния.
Чтобы прочесть его значение, нужно, чтобы двигатель
дисковода работал. Поэтому используется следующий приём:
выполняется команда позиционирования на дорожку,
номер которой уже записан в регистр дорожки.
При этом головка не будет двигаться, но двигатель дисковода включится.
После окончания выполнения этой команды читается регистр состояния.
Сразу после этого двигатель выключается, например,
с помощью записи 0 в порт #FF (или в порт #1F).
Между двумя последовательными чтениями регистра состояния делается пауза, обычно 1/50 секунды.
При этом двигатель не успевает раскрутиться, и лампочка дисковода не загорается.
Но если внимательно присмотреться, видно,
что лампочка всё же чуть-чуть светится
(яркость свечения обратно пропорциональна длине паузы).
Так вот, на некоторых дисководах (а именно — на ЕС 5323.01)
было замечено, что при работе программ, контролирующих смену диска,
лампочка постоянно горит и двигатель работает.
Привожу текст небольшой демонстрационной программы,
контролирующей смену диска по вышеуказанному алгоритму.
Программа выводит на экран сообщения «DISK PRESENT» и «DISK NOT PRESENT».
Выход — по нажатию любой клавиши (когда диск присутствует).
CALL 3435 ;CLS
LD A,2
CALL 5633
M_1 CALL READ_S
BIT 1,A
JR NZ,M_4
M_2 LD A,1
LD (N),A
LD DE,TEXT1
LD BC,23
CALL 8252 ;"DISK PRESENT"
M_3 XOR A
IN A,(254)
AND 31
CP 31
RET NZ ;Если нажата клавиша, выходим.
CALL READ_S
BIT 1,A
JR Z,M_3
M_4 XOR A
LD (N),A
LD DE,TEXT2
LD BC,23
CALL 8252 ;"DISK NOT PRESENT"
M_5 CALL READ_S
BIT 1,A
JR NZ,M_5
JR M_2
N DB 0 ;1 - есть диск, 0 - нет.
TEXT1 DB 22,0,0,16,7,17,0,"DISK PRESENT "
TEXT2 DB 22,0,0,16,7,17,0,"DISK NOT PRESENT"
;***************************************
;Процедура READ_S осуществляет чтение
;регистра состояния. Перед чтением
;производится включение двигателя
;дисковода, а после чтения - выключение
;и пауза в 1/50 секунды.
READ_S XOR A ;Устанавливаем один и тот же
LD C,#3F ;номер цилиндра
CALL TO_WG93 ;в регистре дорожки
LD C,#7F ;и в регистре данных.
CALL TO_WG93
LD A,#18 ;Позиционирование. Головка не будет
CALL TO_1F ;двигаться, но двигатель включится.
CALL READY ;Ждем выполнения...
CALL STATUS ;Читаем регистр состояния.
EI ;Разрешаем ранее запрещенные прерывания.
PUSH AF
XOR A
CALL TO_1F ;Выключаем двигатель.
LD A,#D0
CALL TO_1F
HALT ;Пауза в 1/50 секунды.
POP AF
RET
;***************************************
;Вспомогательные процедуры:
TO_1F LD C,#1F
TO_WG93 LD IX,#2A53
JR TO_DOS
READY LD IX,#3EF5
TO_DOS PUSH IX
JP #3D2F
<Далее следует текст процедуры STATUS>
3.4.4 Системные переменные TR-DOS
Дисковой операционной системе также необходимо где-то
хранить информацию о дисководах и режимах работы, файлах и
т.п.
Для этого она используется область начиная с адреса
#5CB6 (23734)
Для системных переменных TR-DOS еще не сложились имена в
технической литературе (точнее говоря их вообще никто не
давал), поэтому я здесь наберусь наглости дать эти имена
сам.
UNIT_F, байт, #5CB6, X, 23734
Используется при наличии INTERFACE 1. Если значение
равно #F4, то область остается на старом месте. Если байт
равен 0, то происходит проверка байта #5D18 и
соответствующая операция.
S_RET, байт, #5CC2, , 23746
Содержит код команды RET. Используется TR-DOS для вызова
подпрограмм Бесйик-ПЗУ.
MODE_A, байт, #5CC8, ,23752
Код режима работы дисковода А.
бит 7=1, если дисковод 80-ти дорожечный.
бит 1=1, если дисковод двухсторонний
бит 0=0, если 80-ти дорожечный дисковод используется как 40
дорожечных
MODE_B, байт, #5CC9,X, 23753
Код режима работы дисковода В. См MODE_A
MODE_C, байт, #5CCA,X, 23754
Код режима работы дисковода С. См MODE_A
MODE_D, байт, #5CCB,X, 23755
Код режима работы дисковода D. См MODE_A
C_SERD, байт, #5CCC, X, 23756
Текущий сектор при чтении каталога
READY, байт, #5CCD, X, 23757
Содержит #80 при готовности текущего дисковода.
TRMODE, байт, #5CCE,X, 23758
Режим работы:0 чтение/#FF запись
DOS_ER, байт, #5CD6, X, 23766
Содержит #FF, если команда не выполнена
X_BYTE, слово, #5CD7, X, 23767
Для файлов типа BASIC или BYTE - адрес старта. Также
содержит число дорожек сразу после тестирования дисковода.
CHADD2, слово, #5CD9, X, 23769
Внутренний аналог CH_ADD. Также примежуточная длина файла
типа BYTES или BASIC
LENPRG, слово, #5CDB, X, 23771
Длина бейсик программы
NAME_F, 8 байт, #5CDD, , 23773
Имя файла
TYPE_F, байт, #5CE5, , 23781
Тип файла
PAR_F, слово, #5CE6, , 23782
Параметры файла:
Для Бейсика - длина бейсик программы без переменных
Для кодов - стартовый адрес
LEN_F, слово, #5CE8, , 23784
Для файла
SEC_F, байт, #5CEA, , 23786
Размер файла в секторах
FIR_SE, байт, #5CEB, , 23787
Номер первого сектора файла
FIR_TR, байт, #5CEC, , 23788
Номер первой дорожки файла
A_UNIT, байт, #5CEF, X, 23791
Содержит 1 если есть INTERFACE 1
CUR_SEC, байт, #5CF4, X, 23796
Номер текущего сектора
T_DRV, байт,#5CF6, , 23798
Дисковод для временной операции (от 0 до 3). Например LOAD
"A:HELP.ASM" CODE
DOS_ON, слово, #5CF7, , 23799
Обнуляется при возврате из TR-DOS
DBLDRV, байт, #5CF8, , 23800
Дисковод при операциях с двумя дисководами, в
противном случае #FF.
DBLMOD, байт, #5CF9, , 23801
Режим работы при работе с двумя дисководами (признак
операции READ/VERIFY). #FF - VERIFY, 00 READ. Сюда также
заноситься номер дискова при внутренней операции 7 (CAT).
TIME_A, байт, #5CFA, , 23802
Время перемещения головки дисковода А.
TIME_B, байт, #5CFB, ,23803
Время перемещения головки дисковода В.
TIME_C, байт, #5CFC, , 23804
Время перемещения головки дисковода С.
TIME_D, байт, #5CFD, ,23805
Время перемещения головки дисковода D.
WG_CODE, байт, #5CFE, X, 23806
Код команды для контроллера дисковода ВГ93
SEC_WRK, байт, #5CFF, X, 23807
Номер сектора для подпрограмм записи, чтения
BUF, слово, #5D00, X, 23808
Текущий адрес буфера
HID_HL,слово,#5D02, X,23810
Временно хранит HL при необходимости
HID_DE, слово, #5D04, X, 23812
Временно хранит DE при необходимости
CH_NAME, байт, #5D06, , 23814
Количество символов, по которым осущесвляется поиск имени
файла. Нормальное значение - 9, но вы можете его уменьшить.
Увеличивать не рекомендую.
DELF, байт, #5D07, X, 23820
Количество удаленных файлов
DELN,байт,#5D08,X,23816
Первый символ удаленного файла
WRKBUF, слово,#5D0C,X,23820
Флаф наличия рабочего буфера TR-DOS (257 байт с адреса
23846):
FF - отсутствует
00 - существует
FL_COM,байт,#5D0E,X,23822
Флаг принадлежности команд бейсика
FF - бейсик,
любое другое значение - TR-DOS
ER_FL,байт,#5D0F,X,23823
Код ошибки TR-DOS. При равенстве 0 выводит пустую строку,
иначе пустую строку (подпрограмма по адресу #20EF)
ER_HIGH,байт,#5D10,X,23824
Старший байт ошибки. При переходе в TR-DOS обнуляется. При
вызове подпрограмм TR-DOS следует обнулять принудительно.
COMADD,слово,#5D11,X,23825
Адрес строки команды TR-DOS. При вызове:
15616 - хранит E_LINE
15619 - хранит CH_ADD
ERRSP2, слово,#5D13,X,23827
Копия ERR_SP.При равенстве старшего байта АА выполняется
команда RUN "boot" и в #5D18 заноситься #FE
MES_ON,байт,#5D15,X,23829
При равенстве 00 печатает сообщения TR-DOS, иначе не
печатает.
SYSREG,байт,#5D16,,23830
Копия системного регистра (Микросхема 555ТМ9)
X_FL,байт,#5D17,,23831
При не равенстве АА рисует заставку, при равенстве FF не
попадает на ошибку при чтениее неверного адресного маркера.
INT_ON,байт,#5D18,X,23832
Используется при подключенном INTERFACE1. Если равно FF, то
меняются блоки в памяти по адреса 23747 и 23959 длиной в 45
байт. При вызове TR-DOS заноситься FF
DRVDEF,байт,#5D19,,23833
Дисковод по умолчанию (0-3)
RETADD,слово,#5D1C,X,23834
Адрес процедуры завершения. Нормальное значение #0201
HID_SP,слово,#5D1C,X,23836
Временно сохраняет регистр SP
TRLINE,3 байта,#5D20,,23840
Первые три символа введенной строки
TRDOS MORE...
trdos
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
┌──────────────────────────────┐
│ │
│ TR-DOS ДЛЯ НАЧИНАЮЩИХ │
│ │
└──────────────────────────────┘
В.Сироткин.
Продолжение. Начало см. в ZX
РЕВЮ 1996 NN 1-2, 4-5, 6, 7-8,
ZX РЕВЮ 1997 NN 1-2.
ПРОГРАММИРОВАНИЕ КОНТРОЛЛЕРА.
─────────────────────────────
Из предыдущего материала мы
узнали, что компьютер общается с
контроллером дисковода через
специально выделенные порты. Так
как с этими портами нам и пред-
стоит в дальнейшем работать,
здесь еще раз приводится крат-
кая табличка назначения портов
ввода/вывода/управления контрол-
лера ТРДОС.
┌─────────────────────────────────────────────────┐
│ ПОРТ #1F - Регистр Состояния - ЧТЕНИЕ │
│ ПОРТ #1F - Регистр Команд - ЗАПИСЬ │
│ ПОРТ #3F - Регистр Дорожки - ЗАПИСЬ / ЧТЕНИЕ │
│ ПОРТ #5F - Регистр Сектора - ЗАПИСЬ / ЧТЕНИЕ │
│ ПОРТ #7F - Регистр Данных - ЗАПИСЬ / ЧТЕНИЕ │
│ │
│ ПОРТ #FF - Регистр Управления -ЗАПИСЬ │
│ │
│ - 0,1 биты - номер дисковода ('A'= 0,0) │
│ - 2 бит - сброс ВГ93 (при=0) │
│ - 3 бит - подготовиться (при=1) │
│ │
│ - 4 бит - сторона диска (при=1 верх) │
│ - 5 -//--------------------//--------- │
│ - 6 бит - плотность (при=0 двойная) │
│ - 7 -//--------------------//--------- │
│ │
│ ПОРТ #FF - Регистр Управления - ЧТЕНИЕ │
│ │
│ - 6 бит - строб байта данных (при =1 есть │
│ данные) │
│ - 7 бит - готовность (при =1 готов) │
└─────────────────────────────────────────────────┘
Как нам уже известно, прямым
путем записать или считать ин-
формацию по этим портам команда-
ми OUT или IN нам не удастся,
так как эти порты подключаются к
компьютеру только тогда, когда
включена ПЗУ ТРДОС и, соответ-
ственно, блокированы все порты
компютера.
Возникает закономерный воп-
рос: "А что же делать ?". Дело в
том, что в адресном простран-
стве ПЗУ ТРДОС, которое совпа-
дает с адресами, задействованны-
ми на перехват и включение ТРДОС
контроллера (а как мы помним из
первой главы - это промежуток
адресов с #3D00 по #3DFF), есть
точка входа, как раз предназна-
ченная для таких случаев. (Спа-
сибо программистам - хоть ее
предусмотрели !)
* * *
ЭТО ТОЧКА С АДРЕСОМ
#3D2F или #3D30.
* * *
А выглядит эта точка в ПЗУ ТРДОС
вот так:140.
#3D2F NOP
#3D30 RET2
"Ну и что? Как она может нам
помочь? Как за то время включе-
ния ПЗУ ТРДОС нам дать команду
портам?" - воскликните вы! Очень
просто! Перед вызовом этой точ-
ки командой 'JP' мы занесем в
стек сначала адрес возврата, за-
тем адрес подпрограммы ПЗУ ДОС,
где есть нужная нам подпрограм-
ма. По команде 'RET' в точке
#3D30 процессор возьмет из стека
адрес и исполнит подпрограмму
ПЗУ ТРДОС, т.к. в этот момент
она будет подключена.
Единственное условие: эта
подпрограмма в ПЗУ должна окан-
чиваться командой 'RET' для то-
го, чтобы все вернулось на тот
адрес возврата, который мы за-
несли в стек первым.
Короче говоря, если нам надо
исполнить какую-нибудь подпрог-
рамму в ПЗУ ТРДОС, то наши дей-
ствия:
1) Занести в стек адрес, куда
вернется программа после выхода
из ПЗУ ДОС.
2) Занести в стек адрес нуж-
ной подпрограммы ПЗУ ДОС.
3) Дать команду JP #3D2F (или
#3D30).
Такой способ вызова называет-
ся "Косвенная адресация через
стек".
Если нам необходимо, чтобы в
ПЗУ исполнились две - три под-
программы, то в стек загружают-
ся (после адреса возврата) два -
три адреса подпрограмм. Послед-
ним в стек должен быть помещен
адрес подпрограммы, которая бу-
дет выполняться первой!
Вроде бы ничего сложного, но
вот тут-то и начинаются неприят-
ности...
Дело в том, что если содержи-
мое точки вызова #3D30 для всех
версий ТР ДОС одинаково, то АД-
РЕСА ПОДПРОГРАММ-УТИЛИТ ТРДОС
ДЛЯ РАЗЛИЧНЫХ ВЕРСИЙ РАЗЛИЧНЫ !
Для версии 5.01 это одни адреса,
а для 5.03 это другие адреса.
И вот, если Вы составите свою
программу, основываясь на адре-
сах версии, которая зашита в ПЗУ
Вашего контроллера, то это не
значит, что программа будет ра-
ботать с контроллером приятеля,
у которого другая версия ТР ДОС.
Может статься, что в 50% слу-
чаев Ваша программа просто за-
виснет или, того хуже, - запор-
тит Вашему приятелю данные на
диске.
Пример из практики: автору
этой книги попалась адаптирован-
ная под диск программа 'WORD'-
текстовый редактор, которая была
рассчитана на версию ТРДОС-5.03.
(Загрузка самого редактора тоже
производилась через подпрограммы
ПЗУ ТРДОС).
Запущенная с контроллером
версии 5.01, эта программа уже в
процессе загрузки "успешно" фор-
матировала нулевую и первую до-
рожки диска.
После чего так же успешно за-
висала. Представьте себе: Вы бе-
рете диск с целым пакетом прог-
рамм, запускаете редактор, а по-
том...диск приходится форматиро-
вать заново.
И самое главное, что никаких
надписей, с какой версией ТРДОС
этот редактор работает... Хоро-
шая адаптация - нечего сказать !
При ближайшем рассмотрении
оказалось, что адреса в ПЗУ
5.03 подпрограммы "чтения секто-
ра" в версии 5.01 лежат по дру-
гим адресам, а по этим адресам в
версии 5.01 расположена подпрог-
рамма "форматирование".
Так вот правило НОМЕР ОДИН:
Если Вы программируете на са-
мом низком уровне, т.е. с выхо-
дом на подпрограммы ТРДОС, то
Ваша программа должна опреде-
лять (или запрашивать) версию
ТРДОС и затем корректировать
адреса вызовов подпрограмм ПЗУ.
В крайнем случае Ваша програм-
ма должна выводить на экран
НОМЕР ВЕРСИИ ТРДОС, на какую
программа адаптирована.
Это правило хорошего тона !!!
Далее в примерах будут при-
сутствовать адреса и версии ТР
ДОС 5.01, и версии 5.03.
За основу приняты адреса вер-
сии 5.01 !!! А адреса версии
5.03 будут даваться рядом, в
угловых скобках: < адрес >.
Итак, точка входа у нас есть,
остались только адреса подпрог-
рамм, которые бы работали с нуж-
ными портами.
Таких подпрограмм в ПЗУ мно-
жество. Но они так тесно увяза-
ны друг с другом, что если выде-
лить только те, которые после
команд 'IN' или 'OUT' сразу
имеют команду 'RET', то таких
подпрограмм окажется совсем нем-
ного...140.
1. Запись в порт #1F (регистр команд).
Адреса в ПЗУ Мнемоника
──────────────────────────────────────────────
#2F79 < #2FC3 > OUT (#1F),A ; подать команду
RET ; вернуться
2. Запись в порт #1F (регистр команд).
#3ED5 < #3EDF> OUT (#1F),A ; подать команду
LD A,(#5CD1) ; опросить ОЗУ
CP #FF ; вернуться, если
RET Z ; там байт #FF
3. Запись в порт #3F (регистр дорожки).
#1DFE < #1E3A > OUT (#3F),A
RET
4. Запись в порт #3F (регистр дорожки).
#3E8B < #3E95 > OUT (#3F),A ; номер дорожки
LD A,(#5CCD) ; опросить ОЗУ
OR A ; венуться, если
RET Z ; там байт 00
5. Запись в порт #FF (управление).
#1FB7 < #1FF3 > OUT (#FF),A
RET
6. Запись в порт #FF (управление).
#2EC2 < #2F0C > OUT (#FF),A
RET
7. Запись в любой порт по регистру 'C'.
#2A09 < #2A53 > OUT (C),A ; номер порта в 'C'
RET2
Вы, наверное, заметили, что
многих портов в этих подпрограм-
мах не хватает, а чтения из пор-
тов нет вообще! Чтобы заставить
контроллер что-то делать, необ-
ходимо выполнить ряд действий в
совокупности. Одной командой, в
большинстве случаев, не обой-
тись.
Весь процесс обмена ДИСК <->
ПРОЦЕССОР состоит из 2,3,4 под-
программ ПЗУ, которые выполняют
ряд взаимосвязанных действий
сразу с несколькими портами. Но
даже с теми подпрограммами, ко-
торые у нас уже есть, можно вы-
полнить определенные действия.
Например: воздействовать на
порт управления (#FF) и дать ко-
манду первого типа (адреса в
примерах - для версии 5.01).147.
1. Сброс микросхемы ВГ93 и переход на 0 дорожку.
START LD IX,ENDE ; адрес возврата -> в стек
PUSH IX
LD IX,#1FB7 ; адрес п/п ДОС
; (запись в порт #FF)
PUSH IX ; занести в стек
LD A,0 ; управляющая команда СБРОС
JP #3D2F ; исполнить п/п в ПЗУ
ENDE RET ; выйти вообще
2
!!! Кстати, если дать такую ко-
манду, то потом, при нажатии на
'МАГИК' кнопку, вместо 'магик'
файла Вы получите испорченный
диск, т.к. микросхема будет пос-
тоянно НЕ ГОТОВА по порту #FF и,
чтобы вывести ее из этого сос-
тояния, необходимо не только по-
дать в порт #FF 'готовность', но
и подать команду ПРЕРЫВАНИЕ, ко-
торая начисто отсутствует в под-
программе обработки 'МАГИК'
кнопки !!!147.
2. Выбор номера диска, плотности, подача готовности
и выбор верхней стороны диска...
START LD IX,ENDE ; адрес возврата - в стек
PUSH IX
LD IX,#1FB7 ; адрес п/п ДОС (запись в
; порт #FF)
PUSH IX ; в стек
LD A,%00111100 ; управляющая команда:
; диск 'A', и т.д
JP #3D2F ; исполнить п/п в ПЗУ
ENDE RET ; выйти вообще
3. Поиск нужного цилиндра на диске.
START LD C,#FF ; порт #FF в регистр 'C'
LD A,#3C ; готовность , диск A и тд.
CALL TRDOS ; записать в порт #FF
LD C,#7F ; регистр данных
LD A,5 ; команда - найти 5-й цилиндр
CALL TRDOS ; записать в регистр данных
LD C,#1F ; регистр команд
LD A,#1C ; команда ПОИСК ЦИЛИНДРА с
; опущенными головками, с провер-
; кой и с минимальной задержкой
CALL TRDOS ; дать команду на поиск
ENDE RET ; выход из программы
;- - - - - подпрограмма записи данных в порт - - -
TRDOS LD IX,#2A09 ; адрес п/п в ПЗУ 'запись в
; порт по регистру 'C' байта
; из регистра 'A'
PUSH IX
JP #3D2F ; исполнить и вернуться потом,
; как из подпрограммы2
Таким методом можно записы-
вать данные в управляющий порт
#FF, в регистры ДОРОЖКИ, СЕКТО-
РА и подавать команды для ВГ93,
но организовать диалог (т.е. об-
мен данными и проверку) процес-
сор <-> контроллер очень затруд-
нительно.
Вообще, обмен данными между
контроллером и процессором дол-
жен идти по следующему алгорит-
му:
1. Даем байт управления в
порт #FF. Так как этот порт под-
ключен к триггеру с защелкой, то
эта информация сохраняется до
прихода следующего байта в этот
порт (выбираем дисковод, даем
подготовительную готовность,
сторону диска и плотность).
2. Даем в РЕГИСТР команд,
порт #1F, байт КОМАНДЫ и обяза-
тельно самая первая команда пе-
ред командами ЧТЕНИЕ - ЗАПИСЬ
должна быть команда ПЕРВОГО
ТИПА, с модификатором 'опустить
головку на диск' (это связано со
схемотехническими особенностями
всего контроллера ТРДОС).
3. Начинаем опрашивать порт
#FF на 7-й бит (бит готовности
микросхемы ВГ93) до тех пор, по-
ка там не установится ЕДИНИЦА,
т.е. наша команда выполнена.
4. Если команда была связа-
на с ЧТЕНИЕМ или ЗАПИСЬЮ, т.е.
команды типа 2 и типа 3, то сов-
местно с опросом бита 7 порта
#FF необходимо перед каждым бай-
том ДАННЫХ, идущим с диска или
на диск, опрашивать 6-й бит пор-
та #FF (строб данных). И если
бит 6 установился в ЕДИНИЦУ, то
принять или передать байт в порт
#7F (регистр данных).
5. Как только бит 7 порта
#FF установится в ЕДИНИЦУ, можно
приступать к записи следующей
команды (перед подачей следующей
команды можно дать небольшую за-
держку пустым циклом).
6. Опросить регистр СОСТОЯ-
НИЯ ВГ93 (порт #1F) и определить
правильность выполненной коман-
ды. Если команда выполнена без
ошибок, и все в норме, то из ре-
гистра СОСТОЯНИЯ считается байт
#80.
7. Если в регистре СОСТОЯНИЯ
устанавливаются биты, то в зави-
симости от того, какой бит уста-
новился, принимаются соответ-
ствующие действия.
Например: при команде 'за-
пись':
- Если установлен бит 6, то
придется прервать операции пода-
чей команды 'ПРЕРЫВАНИЕ' и вы-
вести надпись на экран: "ДИСК
ЗАЩИЩЕН".
- Если выставился бит 2 (поте-
ря данных), то придется или пов-
торить операцию заново, или вый-
ти с надписью на экране "ошибка
записи".
- Если бит 0 установлен в еди-
ницу, то это значит, что диско-
вод занят и придется ждать, пока
он не освободится.
Поначалу данная последова-
тельность кажется длинной и
очень сложной, но так как в ПЗУ
ТРДОС эти подпрограммы уже есть
в совокупности, то весь процесс
выльется в вызов 2-х или 3-х
соответствующих подпрограмм че-
рез стек из ПЗУ.
Необходимо запомнить еще од-
но правило: правило последова-
тельности подачи команд.
ПРАВИЛО НОМЕР ДВА:
Из-за схемотехнической осо-
бенности контроллера ТРДОС вклю-
чение двигателя дисковода и при-
жатие головки к диску произво-
дится только командами ПЕРВОГО
ТИПА, с модификатором МАГНИТНАЯ
ГОЛОВКА В РАБОТЕ (т.е. когда го-
ловка прижата к диску и диск
раскручен).
Третий бит кода команды дол-
жен быть равен 1. Так как диско-
вод является медленнодействующим
устройством, то после выполне-
ния команды первого типа остает-
ся достаточно времени для подачи
следующих команд ЧТЕНИЯ ИЛИ ЗА-
ПИСИ.
Давайте рассмотрим несколько
примеров дисковых процедур из
ПЗУ.
ПРИМЕР 1.
Подпрограмма ЧТЕНИЯ из порта.
Номер порта в регистре 'C'.
Перед вызовом данной процеду-
ры НЕОБХОДИМО: если считывание
производится из регистра данных
(т.е. считывание с диска), то в
регистр КОМАНД (порт #1F) нужно
записать команду, а в порт #FF -
записать готовность.
Естественно, головки диска
должны быть установлены на нуж-
ную дорожку и прижаты к диску.
(Адреса в примерах для ТРДОС
5.01). В регистр 'HL' поместить
адрес ОЗУ, куда будем считывать
информацию. В регистр 'C' - но-
мер порта.140.
#3FDB < #3FE5 > IN A,(#FF) ; опрос выполнения и
; строба данных
AND #C0
JR Z,#3FDB ; если не выполнено и нет
; строба, то опять читаем
; порт #FF
RET M ; выполнено - выход
INI ; чтение байта из порта
; в адрес M (HL)
JR #3FDB ; повторим, если групповая
; операция
2
Вообще-то эта процедура пред-
назначена для чтения массива
данных с диска (из СЕКТОРа или
ДОРОЖКИ), но ее можно использо-
вать и для чтения из регистров
ДОРОЖКИ, УПРАВЛЕНИЯ и СЕКТОРА,
если в регистр 'C' занести соот-
ветствующий порт. В этом случае
подпрограмма сработает ОДИН раз,
и у Вас в адресе M (HL) окажется
считанный байт.
ВНИМАНИЕ! РЕГИСТР СОСТОЯНИЯ
(ПОРТ #1F) ЭТОЙ ПРОЦЕДУРОЙ ОПРА-
ШИВАТЬ НЕЛЬЗЯ. Так как при его
опросе бит ГОТОВНОСТЬ порта #FF
сбросится в 0 и все зациклится !
ПРИМЕР 2.
Подпрограмма ЗАПИСЬ В ПОРТ. Но-
мер порта в регистре 'C'.
Эта процедура является обрат-
ной копией предыдущей процедуры.
Перед вызовом данной процедуры
НЕОБХОДИМО:
Если запись производится в
регистр данных (т.е. запись на
диск), то в регистр КОМАНД (порт
#1F) нужно записать команду, а в
порт #FF - записать готовность.
Естественно, головки диска дол-
жны быть установлены на нужную
дорожку и прижаты к диску. В ре-
гистр 'HL' поместить адрес ОЗУ,
ОТКУДА будем записывать информа-
цию. В регистр 'C'- номер порта.140.
#3FC0 < #3FCA > IN A,(#FF) ; опрос выполнения и
; строба данных
AND #C0
JR Z,#3FC0 ; если не выполнено и
; нет строба,то опять
; читаем порт #FF
RET M ; выполнено - выход
OUTI ; запись байта в порт
; из адреса M (HL)
JR #3FDB ; повторим, если группо-
; вая операция
2
Этой процедурой, конечно,
можно записать байт в любой
порт, но для этого есть другие,
менее длинные процедуры (смотри
выше).
Сразу же возникает законный
вопрос: "А как все же опросить
порт #1F, т.е. Регистр Состоя-
ния ?"
Дело в том, что программисты,
которые писали ТРДОС, не предус-
мотрели возможности без помех
опросить этот регистр из прог-
рамм пользователя. Все опросы
порта #1F в ПЗУ тесно увязаны с
другими подпрограммами чтения-
записи.
Есть, правда, точка по адресу
#3F28 < #3F32 >, вызвав которую,
можно опросить Регистр Состоя-
ния. Но там сразу же происходит
и проверка битов с переходом на
адреса печати сообщений об ошиб-
ках и сбоях диска.
Эту точку входа можно исполь-
зовать в программах, в которых
не нарушена системная область
BASIC'а и TRDOS, так как при
возникновении каких-либо ошибок
система будет вызывать программу
печати сообщений на экран.
А если область системных пе-
ременных нарушена ??? Есть два
способа выйти из этого положе-
ния.
Первый - если в программе про-
исходит ЧТЕНИЕ с диска, то после
прочтения сектора (секторов)
произвести подсчет контрольной
суммы считанного блока и срав-
нить с той суммой, которая из-
вестна (заранее просчитана).
Если же происходит ЗАПИСЬ на
диск, то тут придется или запи-
сывать 2 раза один и тот же сек-
тор (для уверенности), или тут
же считать этот сектор обратно в
какое-нибудь свободное место в
ОЗУ и сравнить считанный блок с
тем, который пытались записать.
И если они не идентичны, то про-
бовать записать еще раз. Как Вы
понимаете, это не лучший выход
из положения !
Второй способ - довольно эк-
зотический, но очень эффективный
и позволяет опросить Регистр
Состояния в любое время из прог-
раммы пользователя. Этот способ
основан на методе ВТОРОГО ПРЕРЫ-
ВАНИЯ! ("Вот так-так" - восклик-
нет внимательный читатель и нач-
нет искать те страницы книги,
где написано, что ТРДОС не любит
прерывания ДВА. Все верно, не
ищите и не торопитесь с выводами
и руганью в адрес автора. Дело в
том, что действительно, большин-
ство подпрограмм ТРДОС "боятся"
второго прерывания. Это те под-
программы, которые ответственны
за чтение/запись информации на
диск.
Мы же не будем обращаться к
диску, а опросим всего лишь порт
Состояния ВГ93). Итак... В ПЗУ
ТРДОС по адресу #2D3D < #2D87 >
есть следующая последователь-
ность команд.140.
;--подпрограмма 'опрос порта #1F' #2D3D < #2D87 >
#2D3D < #2D87 > IN A,(#1F) ; прочитать порт #1F
AND #7F ; выделим все уста-
; новленные биты
RET Z ; вернемся, если НЕТ
; ошибок
DEC D ; уменьшить регистр D
PUSH HL ; HL в стек
PUSH DE ; DE в стек
JR NZ,#2D31 ; если регистр
; 'D' <> 0 ,то
; перейти выше,
; а нам это не надобно
HALT ; Ждать прихода преры-
; вающих импульсов INT
... ; Далее
; нас не интересует..
2
Воспользуемся тем, что в этой
процедуре есть команда HALT.
Если вспомнить, то, встретив эту
команду, процессор как бы приос-
танавливается и ждет прихода
сигнала прерывания на вход INT,
а потом уходит на исполнение
программы прерывания. В СПЕКТРУ-
МЕ импульсы прерывания следуют с
частотой 50 Герц, и 50 раз в се-
кунду опрашивается клавиатура по
прерыванию 1. Мы же, установив
ПРЕРЫВАНИЕ 2, перехватим выход
из этой процедуры на свою прог-
рамму.
Единственное, не забудем пе-
ред вызовом процедуры из ПЗУ за-
нести в регистр 'D' ЕДИНИЦУ, а
на выходе, в прерывающей проце-
дуре, продвинуть указатель СТЕКА
вверх по ОЗУ на 3 СЛОВА (напри-
мер, три раза дать команду 'POP
AF').
140.
; Программа вызова процедуры опроса порта #1F...
START LD A,#FD ; занесем вектор прерывания,
; равный,
LD I,A ; например, #FD (полный адрес
; равен #FDFF)
LD HL,VARIABLE ; адрес прерывающей процедуры
LD (#FDFF),HL ; занесем в адрес вектора
; прерывания адрес программы
LD D,1 ; регистр должен=1 для того,
; чтобы программа в ПЗУ отра-
; ботала ОДИН раз
LD IX,#2D3D ; адрес подпрограммы ТРДОС
; (для версии ТРДОС 5.01 !)
EI ; прерывания разрешить
HALT ; приостановить процессор и
IM 2 ; включить прерывание ДВА
CALL TRDOS ; вызвать программу из ТРДОС с
; последующей отработкой прог-
; раммы прерывания
DI ; запрет прерываниям
IM 1 ; прерывание ОДИН
EI ; разрешить прерывания
RET ; выйти из программы
;- - - - - - - прерывающая программа - - - - -
VARIABLE DI ; запретим прерывание
POP HL ; извлечем из стека адрес
; возврата на
POP HL ; программу в ПЗУ после HALT,
POP HL ; а также еще два слова
IM 1 ; прерывание в 1
RET ; вернуться
;- - - - - - - - - - - -
TRDOS PUSH IX ; вызов программы из ПЗУ ТРДОС
JP #3D2F ;
;- - - - - - - - - - - -
2
После запуска программы с
метки START она отработает, и мы
получим на выходе в регистре 'A'
НОЛЬ - если все в порядке (пре-
рывающая процедура не включит-
ся).
Если же Регистр Состояния со-
держал установленные биты, то в
этом случае включится наша пре-
рывающая программа, и в регис-
тре 'A' мы получим байт, кото-
рый можно в дальнейшем проанали-
зировать любым способом.
Единственным недостатком та-
кого метода является резервиро-
вание ячеек ОЗУ под вектор - где
содержится адрес прерывающей
программы (в нашем случае это
ячейки ОЗУ с адресами #FDFF и
#FE00), а также требование сох-
ранять 'старый' регистр 'I', ес-
ли переделываемая программа ра-
ботала с прерыванием 2.
Так как младший байт вектора
прерывания всегда равен #FF, то
таких адресов в адресном прос-
транстве компьютера насчитывает-
ся всего 255. Если исключить ад-
реса, падающие на ПЗУ (а их 63),
то нам останется 255 минус 63,
всего 192 произвольных адреса. А
это достаточно много.
В ПЗУ ТРДОС есть еще несколь-
ко подпрограмм опроса порта #1F,
которые можно вызвать таким же
способом. Одна из них находится
по адресу #3DAB < #3DB5 >.140.
#3DAB < #3DB5 > IN A,(#1F) ; прочитаем порт
AND 02 ; выделим бит 'ЗАПРОС
; ДАННЫХ'
; при операциях чтения
; -записи
; или бит 'ИНДЕКС'
; при остальных опера-
; циях
LD B,A ; спрячем полученное
; значение
LOOP1 IN A,(#1F) ; еще раз прочитаем
; порт
AND 02 ; выделим бит
CP B ; сравним с прежним
RET NZ ; если бит установлен
; - выйдем
INC DE ; увеличим значение
LD A,E ; если попытки не
; кончились,
OR A ;
JR NZ,LOOP1 ; то повторить чтение
; порта
2
Эту подпрограмму удобно вызы-
вать в случаях:
- когда надо засинхронизировать
выполнение программы на прохож-
дение Индексного отверстия, т.е.
на новый оборот диска, или уз-
нать, вставлен ли диск и готов
ли он?
- при операциях чтения/записи -
ожидая сигнала ЗАПРОС ДАННЫХ.
Как мы видим, в этой подпрог-
рамме отсутствует команда HALT,
по которой ВТОРЫМ прерыванием
точно перехватывается управле-
ние на программу пользователя.
Но в любом случае при появлении
прерывающего импульса INT про-
цессор будет отрабатывать цикл
LOOP1, и перехват все равно сос-
тоится, а в регистре 'B' или в
'A' будет нужный нам байт с вы-
деленным ВТОРЫМ битом.
Как и в предыдущем случае,
сначала надо установить Вектор
прерывания, а в прерывающей про-
грамме передвинуть Указатель
стека на ДВА байта вверх (чтобы
вернуться не в ПЗУ, а в програм-
му пользователя). Перед вызовом
подпрограммы ПЗУ в регистры 'DE'
необходимо занести 00 для макси-
мального цикла опроса. Ну, и ес-
тественно, дать команды EI и
IM2.
140.
; Программа вызова процедуры #3DAB < #3DB5 >
start LD A,VECTOR ; установим вектор
; прерывания
LD I,A
LD HL,VARIABLE ; занесем по адресу
; вектора
LD (VECTOR *256+255),HL ; адрес прерывающей
; программы
EI
HALT
LD DE,00 ; число попыток
LD IX,#3DAB ; программа в ПЗУ (для v.5.01)
IM 2 ; второе прерывание
CALL TRDOS ; исполнить программу В ПЗУ и
; прерывающую подпрограмму
IM 1 ;
RET ; вернуться, в регистре 'A' и
; 'B' будет значение из
; порта #1F
;- - - - - - - - - -
TRDOS PUSH IX
JP #3D2F
;- - - - - - - - - -
VARIABLE DI
POP HL
RET
;- - - - - - - - - -
2
Для полноты изложения можно
еще привести адрес подпрограммы
опроса порта #1F с выделением
ЧЕТВЕРТОГО бита "ПОТЕРЯ ДАННЫХ",
если была операция ЧТЕНИЯ/ЗАПИ-
СИ. При остальных операциях -
бит "ГОЛОВКА в ИСХОДНОМ СОСТОЯ-
НИИ".
Вызывают эту подпрограмму
ОБЫЧНЫМ способом, без всяких
ухищрений. Единственное, в ре-
гистр 'C' перед вызовом надо за-
нести число 1 - для отработки
подпрограммы всего один раз.
140.
;... процедура чтения порта #1F с выделением бита 4
;... перед вызовом дать комманду LD C,1
#3E30 < #3E3A > IN A,(#1F) ; опросить порт
AND 04 ; выделить бит 4
RET NZ ; если он установлен
; - выйти
INC B ; увеличить 'B'
DEC C ; уменьшить 'C'
RET Z ; если 'C'=0, то выйти
...
2
Теперь, когда мы научились
опрашивать порт Состояния, нам
надо решить, что же должна де-
лать программа пользователя в
случае установки какого-либо би-
та в этом порту, т.е. при ошиб-
ке операций.
При операциях ЧТЕНИЯ/ЗАПИСИ:
- Установлен
2-й бит - 'ПОТЕРЯ ДАННЫХ'
и/или
3-й бит - 'ОШИБКА КОНТРОЛЬНОГО
КОДА'
и/или
4-й бит - 'МАССИВ НЕ НАЙДЕН'
и/или
5-й бит - 'ОШИБКА ЗАПИСИ'
В этом случае Вам необходимо
повторить операцию определенное
количество раз, и если бит все
так же будет установлен, то это
значит, что у Вас сбойный диск.
Обрываете операцию командой
'ПРИНУДИТЕЛЬНОЕ ПРЕРЫВАНИЕ', а
дальше поступаете так, как счи-
таете нужным.
- Установлен 6-й бит - 'ЗАЩИТА
ЗАПИСИ' (т.е. диск защищен).
В этом случае также прерывае-
те операцию подачей команды
'ПРЕРЫВАНИЕ' и любезно просите
снять защиту с диска, или не
просите...
- Сброшен 7-й бит - 'ГОТОВНОСТЬ
ДИСКОВОДА'
А в этом случае Ваша програм-
ма должна просто подождать го-
товности дисковода пустым цик-
лом. А теперь давайте продолжим
и посмотрим, что же еще полезно-
го есть в ПЗУ ТРДОС.
Процедура определения НОМЕРА
ЦИЛИНДРА под головками.
~~~~~~~~~~~~~~~~~~~~~~~
Адрес #1DFA < #1E36 >.
После вызова этой подпрограм-
мы в регистре 'A' возвращается
НОМЕР ЦИЛИНДРА; этот же номер
сразу заносится в Регистр Дорож-
ки контроллера. Также при своей
работе подпрограмма опрашивает
клавишу 'BREAK' = C/SH+SPACE и
печатает сообщение, если клавиша
нажата.
Эта подпрограмма является за-
конченной, т.е. не нуждается в
подготовительных действиях. К
сожалению, при ее работе затра-
гивается ряд адресов в ОЗУ Сис-
темных переменных.
Условия вызова:
- в регистр IY занести значение
#5C3A;
- в регистре 'B' должен быть
НОЛЬ;
- в адресе ОЗУ #5C3A должно быть
значение #FF.
Купируются, т.е. изменяются в
процессе работы подпрограммы
адреса ОЗУ #5D16 и #5CCD.
Процедура 'Поиск нужной ДОРОЖКИ'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Адрес #3E59 <#3E63>.
При работе подпрограмма нахо-
дит на диске нужную ДОРОЖКУ (вы-
ходит на нужный ЦИЛИНДР и прижи-
мает ВЕРХНЮЮ или НИЖНЮЮ головку
исходя из заданного номера
ДОРОЖКИ). Также опрашивает кла-
вишу 'BREAK' = C/SH+SPACE. Под-
программа является законченной и
работает даже на неформатирован-
ном диске.
Условия вызова:
- в регистр IY занести значение
#5C3A;
- в регистре 'A' должен быть
НОМЕР требуемой ДОРОЖКИ. (Если
номер 0 или четный, то дорожка
верхняя, если нечетный - ниж-
няя);
- в адресе ОЗУ #5C3A должен быть
байт #FF;
- в адресах #5CF6 И #5CF7 должны
быть НУЛИ;
- в адресе #5CC8 должно быть
значение #83;
- в адрес #5CFA занести 08 или
00.
Купируется, т.е. уничтожается
содержимое ячеек памяти по ад-
ресам #5D16 и #5CCD.
Процедура 'ПОИСК нужной ДОРОЖКИ'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Адрес вызова #2EF0 < #2F3A >
Это вторая процедура ПОИСКА.
Она более приемлема в програм-
мах с дефицитом места в памяти.
Во время своей работы процедура
портит всего лишь одну ячейку
памяти по адресу #5C00. При ра-
боте подпрограмма находит на
диске нужную ДОРОЖКУ (выходит на
нужный ЦИЛИНДР и прижимает ВЕР-
ХНЮЮ или НИЖНЮЮ головку, исходя
из заданного номера ДОРОЖКИ).
Подпрограмма является закончен-
ной и работает даже на неотфор-
матированном диске.
Условия вызова:
- в адресе ОЗУ #5C00 должен быть
НОЛЬ;
- в регистр 'C' заносится номер
ДОРОЖКИ.
В эту программу можно войти еще
по адресу:
#2EFB < #2F45 >
В этом случае ячейка ОЗУ #5C00
не затрагивается, но перед вызо-
вом подпрограммы необходимо в
порт #FF подать БАЙТ 'ГОТОВ-
НОСТЬ, ВЕРХНЯЯ СТОРОНА ДИСКА и
т.д.', а в регистр 'C' перед вы-
зовом занести Номер ДОРОЖКИ.
Если кого-то не устраивают
данные методы поиска ДОРОЖКИ на
диске, то можно написать проце-
дуру 'ПОИСК ДОРОЖКИ', основыва-
ясь на команде микросхемы 'ШАГ'.
Правда, это будет занимать
больше места в ОЗУ, и по быстро-
действию она будет намного мед-
леннее всех подпрограмм ПОИСКА в
ПЗУ ТРДОС.
А выглядит это приблизительно
так:
- подать команду 'ВОСТАНОВЛЕНИЕ'
- подать в порт #FF биты ГОТОВ-
НОСТИ и ВЕРХНЕЙ ПОВЕРХНОСТИ
(и др.);
- занести в регистр (например, в
'C') НОМЕР ДОРОЖКИ;
- скорректировать номер дорожки
в НОМЕР ЦИЛИНДРА И в повер-
хность диска и давать команду
контроллеру 'ШАГ ВПЕРЕД' (с
установленными модификаторами
'головку прижать, изменять ре-
гистр дорожки') полученное
число раз...
140.
; Подпрограмма поиска дорожки командой 'ШАГ'.
ld c,#20 ; номер ДОРОЖКИ
ld a,0 ; КОМАНДА ВОССТАНОВЛЕНИЕ
call trdos ; ЗАНЕСТИ В ПОРТ #1F
ld a,#3C ; готовность, ВЕРХ
call trdos ; занести в регистр #FF
ld a,c ; сдублировать номер дорожки
or a ; сбросить регистр флагов
rra ; разделить Номер дорожки на 2
; в 'A' теперь номер ЦИЛИНДРА
ld b,a ; поместить в счетчик цикла
jr nc ,STEP ; число 'Номер Дорожки' было
; четное ? Если да - переход.
ld a,#2C ; нет - значит поверхность НИЗ
call trdos ; занести в порт #FF
STEP ld a,#5b ; команда 'ШАГ ВПЕРЕД'
call trdos ; исполнить команду
djnz STEP ; повторять 'B' чило раз.
..........
; Метка "trdos" в этом случае является подпрограммой
; вызова процедур ТРДОС, которые напрямую работают с
; портами через точку #3D2F.
2
Как мы видим из примера, та-
кой метод довольмо неудобен и
громоздок...
Процедура'ЗАПИСЬ ОДНОГО СЕКТОРА'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Адрес #3F00 < #3F0A>
Процедура записывает один сек-
тор на текущую ДОРОЖКУ (на кото-
рой стоят головки дисковода) и
выбранную поверхность.
Условия вызова:
- головка дисковода должна быть
выведена на нужную дорожку и
прижата к диску;
- в адресах #5D00/#5D01 должен
быть помещен адрес ОЗУ, из ко-
торого будут записываться 256
байтов в сектор;
- в адрес #5CFF должен быть по-
мещен ЛОГИЧЕСКИЙ НОМЕР СЕКТО-
РА, (т.е. нумерация секторов
начинается с 0, подпрограмма
потом скорректирует этот номер
до физического).
- сохранить данные из адреса
#5CFE, так как этот адрес при
работе купируется, т.е. ис-
пользуется при работе.
!!! Самым, пожалуй, сложным в
условии вызова процедуры являет-
ся условие ПРИЖАТЬ ГОЛОВКИ К
ДИСКУ. Решается это простыми ме-
тодами. Так как запись сектора
является как бы продолжением
действия программы пользователя
в цепочке действий, то данная
процедура должна следовать за
процедурой ПОИСК НУЖНОЙ ДОРОЖКИ.
А так как 'ПОИСК' является
командой, которая и прижимает
головки к диску, вызвав програм-
му 'ЗАПИСЬ' сразу же после окон-
чания действия 'ПОИСКА', мы без
труда выполним нужное условие.
Если же в программе пользова-
теля вызов процедуры 'ПОИСК' от-
стоит от вызова 'ЗАПИСИ' далеко
по времени, и программа не успе-
вает подать команду 'ЗАПИСЬ' на
прижатые головки, то, вызвав
повторно подпрограмму 'ПОИСК' с
теми же параметрами, мы загрузим
(прижмем) головки. Можно также
дать команду дисководу 'ШАГ НА-
ЗАД' и следом 'ШАГ ВПЕРЕД' с мо-
дификаторами кода 'ПРИЖАТЬ ГО-
ЛОВКИ'.
Процедура'ЧТЕНИЕ одного СЕКТОРА'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Адрес #2ED1 <#2F1B>
Условия вызова:
- в регистре HL должен быть ад-
рес ОЗУ, куда будет считы-
ваться сектор;
- в регистре 'E' должен быть но-
мер СЕКТОРА (номер ЛОГИЧЕСКИЙ,
т.е. нумерация с НУЛЯ).
Подпрограмма вызывается пос-
ле вывода головок на нужный ци-
линдр и после прижатия головок к
нужной поверхности.
Подпрограмма в своей работе
опрашивает порт СОСТОЯНИЯ (#1F)
и повторяет свои действия, если
был сбой в чтении.
Процедура'ЧТЕНИЕ ОДНОГО СЕКТОРА'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Адрес #3F04 <#3F0E>
Это вторая подпрограмма чте-
ния сектора.
Условия вызова:
- головка дисковода должна быть
выведена на нужную дорожку и
прижата к диску;
- в адресах #5D00/#5D01 должен
быть помещен адрес ОЗУ, в ко-
торый будут считываться 256
байтов из сектора;
- в адрес #5CFF должен быть по-
мещен ЛОГИЧЕСКИЙ НОМЕР СЕКТОРА
(т.е. нумерация секторов начи-
нается с 0, подпрограмма по-
том скорректирует этот номер
до физического);
- Сохранить данные из адреса
#5CFE, так как этот адрес при
работе купируется, т.е. ис-
пользуется при работе.
Процедура 'Подача команд ПЕРВОГО
типа с ожиданием их исполнения'.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Адрес #2F0D <#2F57 >
Эту подпрограмму удобно вызы-
вать, когда требуется исполнить
команды 'ВОСТАНОВЛЕНИЕ', 'ШАГ',
'ПОИСК'.
Подпрограмма при своей рабо-
те записывает код команды в порт
#1F и затем, опрашивая порт #FF,
ждет, когда эта команда испол-
нится.
Условия вызова:
- в регистре 'A' должен быть код
команды.
Процедура 'ЦИКЛИЧЕСКАЯ ЗАПИСЬ
В ПОРТ'.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Адрес #2075 <#20B1>
Обычно эта подпрограмма ис-
пользуется в процессе ФОРМАТИРО-
ВАНИЯ ДОРОЖКИ. Но ее можно ис-
пользовать для нестандартной за-
писи информации на дорожку (на-
пример, создание юстировочной
дорожки, или для полного уничто-
жения информации на дорожке). А
если исхитриться, то можно запи-
сывать информацию и в СЕКТОР.
Условия вызова:
- головки должны быть выведены
на нужный цилиндр и прижаты к
нужной поверхности диска;
- Подать команду 'ЗАПИСЬ' (до-
рожки или сектора). Если запи-
сывается СЕКТОР, то в регистр
СЕКТОРА поместить номер секто-
ра;
- В регистре 'D' должен быть
байт для записи"
- В регистре 'B' должно быть
число: сколько раз записать
байт из регистра 'D';
- В регистре 'C' должно быть #7F
(порт данных).
Процедура "ФОРМАТИРОВАНИЕ ОДНОЙ
ДОРОЖКИ".
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Адрес #1FC1 <#1FFD>
Подпрограмма форматирует одну
дорожку стандартным способом.
Опрашивает порт состояния (#1F)
и выдает надпись на экран, если
диск защищен от записи.
Условия вызова:
- головки должны быть выведены
на нужный цилиндр и прижаты к
нужной стороне;
- В ЯЧЕЙКЕ ОЗУ с адресом #5CD8
должно быть значение, отличное
от НУЛЯ;
- В регистр 'E' необходимо по-
местить номер ЦИЛИНДРА (от 0
до 79), на котором стоят го-
ловки. Если у Вас позволяет
дисковод, то, выведя головки
на цилиндр, больший чем 79, Вы
можете разметить 80, и 81,и 82
и т.д. цилиндры до тех пор,
пока у Вас головки не упрутся
в ограничитель.
Если же Вы хотите отформати-
ровать нестандартно - пожалуй-
ста. Вы можете, например, 1-й
трек отформатировать с систем-
ным номером 255, но работать с
таким диском в ТРДОС СТАНДАРТНЫ-
МИ командами будет трудновато!!!
Для форматирования дорожки
нестандартным образом можно ис-
пользовать точку входа этой под-
программы:
#1FC9 <#2005 >
В этом случае, кроме всех
предыдущих условий, необходимо:
- в регистре 'HL' должен быть
помещен адрес дампа данных,
где последовательно размещены
номера СЕКТОРОВ диска (для
нормального выхода ПОСЛЕДНИМ
НОМЕРОМ в дампе должен быть
номер #10!). ТРДОС использует
адрес дампа данных в ПЗУ по
адресу #1F7D <#1FB9>;
- в порт КОМАНД (#1F) должна
быть занесена команда ФОРМАТИ-
РОВАНИЕ (например, байт #F4).
Подробнее о процессе ФОРМАТИ-
РОВАНИЯ будет рассказано чуть
позднее.
Процедуры 'ЦИКЛ ЗАДЕРЖКИ'.
~~~~~~~~~~~~~~~~~~~~~~~~~~
Адрес 1. #3DF3 <#3DFE>
Адрес 2. #3E96 <#3EA0>
Это подпрограмма задержки для
ожидания исполнения команд дис-
ководом. Иногда ее полезно вызы-
вать, чтобы быть уверенным в
том, что последующая команда
дисководу поступит на него тог-
да, когда он свободен.
Первая подпрограмма: задер-
жка составляет приблизительно
0.3 секунды, а вторая задержка -
приблизительно 1.2 секунды.
Процедура 'ПЕРЕСЫЛКА ДАМПА'.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Адрес 1. #17DD <#180D>
Адрес 2. #28A5 <#2FEB>
По этому адресу в ПЗУ ТРДОС
находятся команды:
LDIR
RET
Вызов этой подпрограммы бы-
вает полезен тогда, когда надо
заморочить голову хаккерам или
для перекачки информации из ПЗУ
ТРДОС в ОЗУ: например, части ка-
кой-либо процедуры.
Условия вызова такие же, как
и обыкновенной команды 'LDIR'.
Можно было бы привести еще
множество полезных подпрограмм
из ПЗУ.
Но все вышеизложенное доста-
точно для того, чтобы написать
какую угодно программу работы с
диском, и мы ограничимся этими
подпрограммами, которые являют-
ся главенствующими.
В процессе написания своих
программ или при разборе других
аналогичных программ Вы можете
обнаружить, что необязательно
вызывать подпрограммы ТРДОС по
указанным выше адресам. В конце
этой главы, в приложении, будут
приведены листинги наиболее важ-
ных подпрограмм ПЗУ ТРДОС с ука-
занием адресов, и вам самим ре-
шать, по какому адресу вызывать
ту или иную процедуру.
TR DOS ДЛЯ НАЧИНАЮЩИХ
ГЛАВА 2
ВНУТРЕННЯЯ ОРГАНИЗАЦИЯ ДИСКА
------------------------------
Как вы уже знаете, существует множество систем, форматов и способов
записи на диски информации в разных компьютерах.
В системе ТР ДОС Спектрума основной стандарт разметки дисков таков:
256 БАЙТОВ НА 1 СЕКТОР;
16 СЕКТОРОВ НА ДОРОЖКЕ.
Как уже было сказано, в ТР ДОС используются диски DD/DS (2D/2S).
Так вот, эти диски ТР ДОС форматирует таким образом:
160 ДОРОЖЕК НА ВСЕМ ДИСКЕ, по 80 ДОРОЖЕК НА КАЖДОЙ СТОРОНЕ. Дорожки нумеруются
от 0 до 159.
Нулевая дорожка расположена у внешнего края диска - на верхней стороне
поверхности 0. Далее - 1-я дорожка на нижней стороне диска.
Две дорожки, расположенные на разных поверхностях, но находящиеся одна
над другой, называют ЦИЛИНДРОМ. На каждой дорожке расположено по 16 СЕКТОРОВ,
которые нумеруются с 1 по 16.
Физически сектора на диске располагаются не по порядку возрастания
номеров , а с пропуском в один сектор (см. РИС.1).
Это было сделано, видимо, из опасения, что процессор, обработав один
сектор, не успеет приготовиться и сразу считать следующий сектор. Пришлось бы
ждать еще один оборот диска для того, чтобы попасть на следующий по порядку
сектор.
А так, пока обрабатывается сектор номер 1, диск прокрутится, пройдет
сектор 9, а когда будет все готово, головка дисковода как раз попадет на
сектор 2.
Такая разметка диска присуща самой системе ТРДОС, но это не значит, что
нельзя разметить по другому. Дело в том, что системе безразлично, вообще-то,
как на диске располагаются сектора в дорожке. Самое главное, чтобы на данной
дорожке были сектора с нужными номерами, нужной размерности и нужным числом.
Вот г-н Родионов подумал и написал программу DCU 2, где сектора
расположены по порядку возрастания номеров, не побоялся, что система не успеет
обработать два идущих подряд сектора - и не ошибся! Получился так называемый
"быстрый FAST диск", где информация с диска обрабатывается чуть быстрее, чем
на стандартно размеченном диске (см. РИС 2).
Да, и самое главное- г. Родионов не побоялся разметить диск на число
дорожек больше, чем 160, и тоже не прогадал, выжав из диска еще дополнительные
килобайты.
Вообще-то можно разметить диск на еще большее число дорожек, но чем
головка ближе к центру диска, тем риск ошибки при считывании информации
возрастает. И уж совсем плохо будет, если Вам придет в голову разметить диск
с двухсотой дорожкой. Ваш дисковод будет безуспешно долбиться головкой об
ограничители и Вам станет не по себе от дикого звука, который при этом
будет раздаваться.
Это все хорошо, ну а как все таки система узнает где и в каком месте
диска она находится? Дело в том, что у каждого сектора находится служебная
область, где записана вся необходимая информация.
В начале у сектора идет область синхронизации, состоящая из кодов
#4Е и нулей - эта так называемая область "пробела", нужная для синхронизации
микросхемы ВГ93 на область данных ( см. РИС 3 ). Далее идет индексная метка и
область И.А.М. (индексная адресная метка). Вот в ней то и записана вся
информация о данном секторе. И.А.М. начинается с байтов #A1,#FE. Далее следуют:
НОМЕР ЦИЛИНДРА ( начало с 0 )
СТОРОНА ДИСКА (0 верх ,1 низ)
НОМЕР СЕКТОРА ( с 1 по 16 )
ТИП СЕКТОРА (0 - сектор 128 БАЙТ
(его длина) 1 - сектор 256 БАЙТ
2 - сектор 512 БАЙТ
3 - сектор 1024 БАЙТ
КОНТРОЛЬНАЯ СУММА ( циклическая сумма )
Если система не находит область ИАМ или контрольная сумма не
совпадает с прочитанной (а контрольная сумма просчитывается микросхемой
каждый раз), то выдается код ОШИБКИ, и обработка или повторяется, или
прекращается в зависимости от числа попыток, заложенных программистом в
подпрограмму обработки ошибок.
Если все в порядке, то за областью ИАМ следуют байты пробела, коды
#4Е и нули. За ними идет АДРЕСНАЯ МЕТКА ДАННЫХ - коды #A1 ; #FB и только тогда
идут собственно те данные, которые считываются или записываются на диск
пользователем.
Эта область, когда данных там нет, содержит 256 нулей, (в случае ТР
ДОС). После области данных следует код КОНТРОЛЬНОЙ СУММЫ этой области. И вот
так в каждом секторе.
Вся эта информация заносится в процессе форматирования диска и, в
конечном итоге, зависит от программиста. Можно разметить по своему усмотрению
все сектора на дорожке с одним номером, или дорожки с обратной нумерацией, или
еще чего придумать, но тогда придется писать уж и программу, которая будет
разбираться на диске со всей этой мешаниной, а это будет уже не ТР ДОС, а
что-то другое.
ТР ДОС в этом не разберется и с полоборота выдаст:
* DISK ERROR * !
Для примера приводим таблицу всех байтов, которые находятся в одном
секторе, в служебной области (см. ТАБЛ.1).
Вся эта информация заносится за один проход, по всем секторам на одной
дорожке во время форматирования.
Собственно, это и есть процесс форматирования. Из этого всего следует,
что если у Вас возникнет желание отформатировать одну дорожку - на здоровье,
но отдельно отформатировать или переформатировать один сектор нельзя! Только
целиком дорожку!
Если мы внимательно подсчитаем, сколько все же килобайт помещается на
одной дорожке со всеми метками, "пробелами", и т.д, то окажется - на дорожке
приблизительно 6 200 байтов.
* 4 килобайта информации, которой располагает
пользователь;
* 2 килобайта всех служебных меток.
Да, за все надо платить. Без этих 2-х килобайт меток обмен с диском был
бы крайне затруднителен, если возможен вообще!
Итак, у нас есть размеченный диск :
160 ДОРОЖЕК , 16 СЕКТОРОВ В ДОРОЖКЕ;
ВСЕ НЕЧЕТНЫЕ ДОРОЖКИ РАСПОЛАГАЮТСЯ НА НИЖНЕЙ СТОРОНЕ;
НУЛЕВАЯ И ВСЕ ЧЕТНЫЕ НА - ВЕРХНЕЙ СТОРОНЕ.
А что-же дальше? Даже если у Вас есть размеченный диск, но не заполнен
сектор номер 8, то ТР ДОС все равно ответит Вам отказом, и диск рассмотрит как
сбойный.
В ВОСЬМОМ ЛОГИЧЕСКОМ секторе располагается информация ТР ДОС. Это тип
диска, имя диска, количество дорожек и т.д. Ниже дана раскладка всех
необходимых байтов сектора.
***************
ВОТ ТУТ НЕОБХОДИМО СДЕЛАТЬ ОТСТУПЛЕНИЕ И ДОГО-
ВОРИТЬСЯ ОБ ОДНОЙ ВЕЩИ ВО ИЗБЕЖАНИЕ ПУТАНИЦЫ !!
ДЕЛО В ТОМ ,ЧТО НОМЕРА СЕКТОРОВ РЕЛЬНО НА ДИС-
КЕ ПОМЕЧЕНЫ НОМЕРАМИ ОТ 1 ДО 16 !!
СИСТЕМА ТР ДОС НА ВЕРХНЕМ УРОВНЕ ИСПОЛЬЗУЕТ
НУМЕРАЦИЮ С 0 ДО 15, КОРРЕКТИРУЯ ПОТОМ НОМЕРА
СЕКТОРОВ ДО РЕАЛЬНЫХ.
БУДЕМ СЧИТАТЬ, ЧТО НОМЕРА С 1 ДО 16 БУДУТ
ФИЗИЧЕСКИМИ НОМЕРАМИ СЕКТОРА,
А НОМЕРА С 0 ДО 15 - ЛОГИЧЕСКИМИ.
В КАЖДОМ КОНКРЕТНОМ СЛУЧАЕ МЫ БУДЕМ УКАЗЫВАТЬ
ГДЕ ЛОГИЧЕСКАЯ НУМЕРАЦИЯ, А ГДЕ ФИЗИЧЕСКАЯ .
**************
СОДЕРЖАНИЕ ВОСЬМОГО (лог.) СЕКТОРА.
----------------------------------
С нулевого по 224 байт в секторе записаны нули.
НОМЕР БАЙТА НАЗНАЧЕНИЕ
-------------------------------------------------
225 номер первого свободного сектора
226 номер первой свободной дорожки
227 диск двусторонний /односторонний
= #16 = = #18 =
228 количество файлов на диске
229 количество свободных секторов
МЛ.БАЙТ
230 СТ.БАЙТ
231 количество секторов в дорожке = #10
232 00
233 00
234 по 242 #20
243 00
244 количество удаленных файлов
245 по 252 ИМЯ ДИСКА ( 8 символов )
------------------------------------------------
Самым главным байтом на секторе является байт с номером 231 !
Можете стереть или изменить всю информацию на секторе - диск еще
останется работоспособным, но если Вы измените этот байт - диск окажется
сбойным и прочитать его можно будет только каким-нибудь Диск-Доктором, загрузив
в него целиком дорожку N 0.
Востановить же диск после таких манипуляций можно... ,тем же самым
"Доктором", записав на это место код #10.
Сектора с 0 ПО 7 (лог.номера) занимает каталог диска.
Пока на диске нет файлов, там записаны нули, но как только на диске
появляются файлы, система автоматически создает на каждый файл нечто вроде
описателя файла, т.е. создает каталог.
На каждый описатель - заголовок файла резервируется 16 байт. Таким
образом, на диске может быть записано не больше (256 байт сектора)/ (16 байт
заголовка) * (8 секторов) = 128 ФАЙЛОВ !!! Лишние файлы будут просто не
создаваться на диске.
Кстати, и название свое система ТР ДОС-128 получила именно в честь
этого числа.
Каждый байт заголовка имеет свое значение для системы.
Структура заголовка файла имеет следующий вид :
БАЙТЫ ! НАЗНАЧЕНИЕ
------------!-----------------------------
0....7 ! ИМЯ ФАЙЛА ( 8 символов )
8 ! ТИП ФАЙЛА ( B,C,D,# )
9 ! НАЧАЛО ФАЙЛА ( мл.байт) В ОЗУ
10 ! НАЧАЛО ФАЙЛА ( ст.байт) В ОЗУ
11 ! ДЛИНА ФАЙЛА (мл.байт)
12 ! ДЛИНА ФАЙЛА (ст.байт)
13 ! ЧИСЛО ЗАНИМАЕНЫХ СЕКТОРОВ
14 ! НОМЕР ПЕРВОГО СЕКТОРА ФАЙЛА
15 ! НОМЕР ДОРОЖКИ НАЧАЛА ФАЙЛА
-------------------------------------------
* Имя файла может состоять вообще-то из любых символов, стрингов, но
рекомендуется давать все-таки имена, состоящие из кодов не меньше 30 и не
больше 127.
Можно, конечно, составить имя и из кодов, не входящих в данный диапазон,
но вероятность того, что Вы нарветесь на такую ситуацию, когда после команды
" САТ " Ваш компьютер напишет - INVALID COLOR, DISK ERROR и т.д. и т.п. -
ОЧЕНЬ ВЕЛИКА !
Так что не искушайте судьбу , да и работать с простыми именами проще.
* Тип файла.
Системой воспринимаются только файлы с типом B
В файл БЕЙСИКА
C файл кодов
D файл типа ДАННЫЕ
# файл произвольно-последова-
тельного типа.
Но это не значит, что нельзя работать с другими типами файлов.
Например, Вам захотелось, чтобы программа в процессе работы загружала
или сохраняла файлы состояния ситуации с типом, отличным от системных.
Отчего же нельзя, например , дать при выгрузке тип файла < Z >? Можно, но
только нужно помнить, что работать с этим файлом из системы ТР ДОС стандартными
командами Вы не сможете! Это можно сделать только АССЕМБЛЕРОМ.
* Начало файла в ОЗУ ( СТАРТ ).
Это справедливо только по отношению к файлам типа КОДЫ , файл БЕЙСИКА имеет
в этих байтах параметр 'ДЛИНА ПРОГРАММЫ'.
Вообще-то, если разобраться, то этот параметр нужен только в том случае,
когда Вы даете команду : LOAD "filename" CODE без указания - КУДА загружать
коды, по какому адресу. Но если Вы даете полное указание куда загрузить файл с
диска, то этот параметр становится не очень то и нужным.
* ДЛИНА ФАЙЛА В БАЙТАХ
Реальная длина файла, то есть сколько байтов он занимает в ОЗУ. Так как
перекачка файла с диска происходит через специальный системный буфер ТР ДОС,
то эта величина указывает ТР ДОС сколько конкретно байтов из сектора будет
пересылаться по нужному адресу через буфер в ОЗУ.
* КОЛИЧЕСТВО ЗАНИМАЕМЫХ СЕКТОРОВ.
Важная величина, по которой и происходит, в конечном итоге, считывание / запись
файла целиком. Даже если реальная длина файла составляет 1 байт, все равно на
диске он будет занимать целый сектор 256 БАЙТ.
ОТСЮДА ВЫВОД: желательно, чтобы реальная длина файла при записи была
кратна 256 - тем самым Вы сохраните место на диске, так как сектора будут
заняты полностью.
С файлами БЕЙСИКА чуть сложнее, но о них немного позднее...
* НОМЕР ПЕРВОГО СЕКТОРА ФАЙЛА
Тут все ясно - номер сектора на дорожке с которого начинается кодовая
последовательность файла. Может принимать значения от 0 до 15 (лог.номер).
* НОМЕР ПЕРВОГО ТРЕКА ФАЙЛА
Номер дорожки на которой находится начальный сектор кодовой последовательности
файла. Может иметь номера от 1 до 159.
Файлы записываются на диск в виде кодовой последовательности, сектор за
сектором. Если на дорожке не хватает секторов, запись переходит на следующую по
порядку номеров, дорожку. И так пока вся кодовая последовательность не будет
записана на диск.
После записи система обновляет сектор номер 8 и записывает туда
первый свободный трек, сектор, количество свободных оставшихся секторов.
Если Вы внимательно прочитали до этого места, то без труда увидите, что
длина файла жестко регламентирована объемом в 255 секторов ( #FF ),
т.е. 255*256=65280 БАЙТОВ.
Реально , если захотеть и если не пользоваться "переменными" ТР ДОС,
то можно соорудить файл хоть на весь диск. Естественно, создав такого
монстра, Вам потребуется создать и свою программу для его считывания и записи.
Если происходит стирание файла, то первым делом в каталоге вместо
первого символа имени файла записывается код 01.
В восьмом секторе увеличивается байт "количествостертых файлов".
Реально, после стирания объем свободных секторов на диске не
увеличивается (если, конечно, Вы не стираете самый последний файл в списке-
тогда можно не беспокоится - место освободится).
Чтобы освободить сектора от "стертого файла", необходимо перезаписать
все файлы, расположенные после стертого файла на его место, что и делает
команда MOVE. После этой команды происходит перезапись всех файлов, записанных
после стертого файла, обновляется информация в секторе номер 8 , и у Вас
появляется на диске ровно столько свободных секторов, сколько занимал Ваш файл.
И расположены эти освобожденные сектора сразу после всех файлов.
В этом одно из неудобств системы ТР ДОС: чтобы освободить сектора
стертого файла, записанного в начале диска, Вам приходится перезаписывать всю
информацию на диске, "сдвигая ее вниз".
Например, в IBM система MS-DOS отслеживает все сектора и при стирании
файлов сектора освобождаются сразу, так что один файл может быть расположен
в различных секторах, на различных дорожках и необязательно в упорядоченном
виде.
При загрузке такого файла (его еще называют сильно сегментированным),
затрачивается немного больше времени, но система находит по всему диску именно
те сектора, на которых записан загружаемый файл.
Но зато у ТР ДОС преимущества: ее файлы всегда единое целое и записаны
всегда последовательно по возрастанию секторов и треков.
Восстановить файл после процедуры стирания легко - надо найти в
секторах каталога Ваш файл с 01 вместо первой буквы и записать вместо 01 какой
нибудь удобоваримый символ, и все !
Да, еще не забудьте уменьшить байт стертых файлов в секторе номер 8 и
увеличить байт "количество файлов" на диске.
Но !!! Если Вы дали команду MOVE, можете не сомневаться - на месте
вашего стертого файла будет какой-нибудь другой. И в секторах каталога Вы,
скорее всего, не обнаружите даже воспоминания об имени Вашего файла.
Так что НЕ бойтесь стирать, а БОЙТЕСЬ команды MOVE .
На рисунке 4 представлен дамп первых байтов области каталога диска.
Слева представление в шестнадцатиричных кодах; справа - в символах. На рисунке
5 дан дамп программы типа ' КОДЫ ', в таком виде, в каком он лежит на диске в
секторе.
Давайте теперь посмотрим как лежит на диске в секторе блок файла,
написанного на БЕЙСИКЕ. Составим простую программу:
10 PRINT "PROGRAMM NO AUTO RUN"
Запишем ее на диск командой :
RANDOMIZE USR 15619:REM:SAVE "PROG1"
В каком-нибудь ДИСК-ДОКТОРЕ найдем тело самой программы на диске. Найти
очень просто: смотрим сначала область каталога, находим имя нашего файла,
находим в заголовке номер первого сектора файла и номер дорожки. Затем Вам
нужно будет только ввести эти данные в ДИСК-ДОКТОР, а он уж сам найдет данный
сектор на данной дорожке.
Но вот файл на секторе найден, и что же мы видим? (см.РИС.6).
С первыми четырьмя байтами понятно ; это номер строки БЕЙСИКА - 2 байта и длина всей строки - 2 байта.
Следом идет текст нашей строки, оканчивающийся кодом #0D. Следом идет байт #80, сигнализатор конца
всей программы. (На рисунке эти байты выделены инверсией). А дальше идет
какой-то довесок. Мы видим справа в символьном представлениию и имя нашей
программы и, вроде бы, число 15619, и многое другое.
Все правильно! Вся эта информация была необходима ТР ДОС на этапе
работы, когда Вы давали команду на сохранение Вашей Бейсик- программы. (Если
бы Вы сохраняли коды, то такой же довесок был бы в самом последнем секторе
файла <CODE>.)
Ведь это просто-напросто область ОЗУ, где набиралась Ваша командная
строка, сохраненная вместе с Вашим файлом.
Вспомним, если Вы попытаетесь записать на диск всего один байт, то файл
будет длиной в 1 сектор - 256 байт. Вот и в нашем случае программа была не
кратна длине 256 и поэтому в файл записалась область, граничащая с нашей
программой, а это как раз область редактора командной строки или EDIT SPACE.
Для эксперимента можете стереть всю область после байта #80 и записав
на диск, загрузить снова, дав команду LOAD...
Файл загрузится и никаких изменений в работе не произойдет. Получается,
что этот довесок сектора пропадает ?! Да, пропадает , и Вы можете использовать
его по своему усмотрению - записав туда какую-нибудь информацию, (свое имя,
например), а можете поместить там подпрограмму, например дешифрации.
Правда, загружать эту подпрограмму из этого довеска Вам придется
специальным загрузчиком. Команды ТР ДОС этого сделать не смогут!
Правда, есть еще два байта в этом "довеске" (будем называть его
потерянной областью файла), которые нужны системе - это байты АВТОЗАПУСКА
Бейсика.
На рисунке 7 дамп сектора этой же самой программы, но записанной с
Функцией LINE 10 , т.е. запуск со строки 10.
На рисунке мы видим , что в системной области файла после кодовой
последовательности #0D,#80,#AA изменился 1 байт - вместо 00 стало #0A. Это и есть
сигнал операционной системе, что после загрузки файл надо сразу запустить
со строки 10. (Следующий байт тоже входит в сигнализатор запуска).
Можете стереть (забить 00), все байты "довеска". Программа по команде LOAD
самозапускаться не будет.
Инверсией в символьной части дампа показан "хвостик" командной строки,
где видно, что после имени программы идет функция LINE 10 ; коды #CA,#31,30.
Все вышеизложенное справедливо и для файлов типа < DATA >, с той лишь
только разницей, что вместо тела программы будет находиться содержимое массива
переменной ( см.РИС 8 ).
Для полноты изложения рассмотрим файлы с последовательным и
параллельным доступом , тип <#>. При открытии файла и его записи на диск,
образуется файл длиной в 16 секторов , т.е. в 1 дорожку независимо от того,
ввели ли Вы в запись 1 байт информации или больше.
Правда, в заголовке файла, в каталоге, указывается величина в байтах,
сколько Вы туда записали , но от этого не легче - дорожка на диске уже занята.
Оба типа файла записываются на диск одинаково, и если Вам придет в
голову последовательный файл открыть для чтения как произвольный и наоборот -
пожалуйста, система не обидится, но разбираться какие записи в каком порядке
считывать - в этом случае придется Вам самому.
С точки зрения программиста, который пишет программы на Ассемблере,
все эти "довески" и нюансы не столь важны. В конечном итоге ему нужно считать
/записать информацию СЕКТОРА , (секторов), файла , а все остальное - дело
техники.
Есть еще один вид файла, о котором пока не упоминалось в этой
книге. Это так называемые "МАГИК ФАЙЛЫ", т.е. файлы сброшенные ВОЛШЕБНОЙ
КНОПКОЙ.
Но поскольку этим файлам будет посвящена целая глава, то
останавливаться здесь на этой теме мы не будем. Единственное, укажем, что
эти файлы имеют тип <CODE> и всегда имеют длину файла, равную объему ОЗУ
с адреса 16384 до 65535. Это слепок всего ОЗУ в файле, с сохраненными
значениями всех регистров процессора.
Теперь, когда у нас есть все необходимые сведения о файлах, нам
необходимо подробнее рассмотреть ту область ОЗУ, которую использует ТР ДОС в
своей работе, а именно, область системных переменных ТР ДОС.
333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
(c) В.Сироткин, г.Краснокаменск.
ГЛАВА 3
СИСТЕМНЫЕ ПЕРЕМЕННЫЕ ТР ДОС
---------------------------------
По анлогии с БЕЙСИКОМ, во время своей работы ТР ДОС создает в ОЗУ область
системных переменных, в которых хранятся параметры настройки диска, команды,
точки вызовов подпрограмм, флаги состояний и т.д. Единственное различие области
ТРДОС от системной области БЕЙСИКА заключается в том, что системные переменные
SOS привязаны к регистру IY процессора. У системных переменных ТР ДОС такой
привязки нет, как нет и своих имен, как это сделано в системных переменных
БЕЙСИКА.
Область системных ТР ДОС занимает всего 112 байт в области ОЗУ с адреса
#5ССВ по адрес #5D3B.
Область инициализируется при первом же обращении к ТР ДОС (т.е. при
любой команде, обращенной к диску) и сдвигает область начала БЕЙСИК - программы
на адрес 23867, оставляя этот адрес активным до сброса компьютера!
При первом включении компьютера, если он выходит в SOS, область
системных ТР ДОС не определена, и программа БЕЙСИКА начинается, как обычно, с
адреса 23755, но при любой команде, обращенной к ТР ДОС, происходит настройка
адресов системной области ТР ДОС, сдвиг начала программы БЕЙСИКА и
инсталирование значений переменных операционной системы.
Во время своей работы, система ТР ДОС может резервировать дополнительный
буфер для своих нужд размером 257 байт с адреса 23846. Этот буфер является
динамическим, т.е. создается только на время операций считывания/записи,
сдвигая программу Бейсика вверх по адресам. После операции буфер уничтожается,
программа Бейсика возвращается на свое место, и все встает на свои места. Эта
операция происходит незаметно для пользователя и независимо от него, если,
конечно, он работает стандартным набором команд ТР ДОС.
Если буфер не может образоваться из-за низкого RAMTOP (а такая
ситуация встречается очень часто), то возникает надпись, которая вывела из себя
многих, кто занимается адаптацией кассетных версий программ на диск, а именно :
" OUT OF MEMORY".
В дальнейшем будет показано как решить эту проблему и обходиться без
этого буфера. Еще больше места требуют для себя команды 'LIST' и 'MOVE';
командe 'MOVE' необходимо аж 4 килобайта, но я думаю, что эта команда должна
интересовать Вас в последнюю очередь.
На Рис. 9 показан дамп ОЗУ системных переменных ТР ДОС после
инициализации.
Подробное описание переменных представленно в таблице 2.
Если Вы в своих разработках будете использовать переменные ТР ДОС, то
нужно понять одну вещь: если Вы работаете с программой, которая не будет своими
рабочими кодами затирать эту область, то система сама позаботится обо всех
переменных сама. Вы, конечно, можете изменять некоторые из переменных или
использовать их значения, но, в данном случае, это не слишком и нужно. Работая
в такой программе на макроуровне (см. главу "Функции ТР ДОС"), Вы не будете
испытывать никаких трудностей.
Но если коды Вашей программы затерли область системных переменных, то
работать в этой ситуации становится затруднительно, и Вам потребуется
инициализировать несколько переменных ТР ДОС, чтобы система сработала правильно.
Вам будет необходимо инсталировать 2-3 переменные + область описателя
файла.
Если Вы попытаетесь инициализировать все переменные и работать всеми
функциями ТР ДОС, то, скорее всего, у Вас ничего не получится из-за отсутствия
свободного места ОЗУ в Вашей программе.
Если Вам все же удался этот трюк, то что же Вам мешает пойти дальше и,
инициализировав переменные БЕЙСИКА, работать в нем, используя стандартные
команды ТР ДОС ??.
Короче говоря, если это так, то у Вас достаточно свободного места в ОЗУ,
и не занимайте, просто-напросто, область системных переменных.
Хотя некоторые программы составляются именно этим путем, т.е. сохраняют
всю область переменных ТР ДОС в свободном месте, а во время работы с диском
пересылают ее на родное место.
Конечно, можно наворочать в программе ( ради ее секретности и трудности
"взлома") все ,что угодно, но поверьте - что все тайное всегда становится явным,
а простота - залог успеха !!
В главе "Функции ТРДОС" мы рассмотрим вопрос о том, какие именно
переменные необходимо вводить, чтобы при затертой области системных ТР ДОС
нормально исполнялись команды чтения/записи на диск. В конечном итоге, ведь это
нам и надо.
Забегая вперед, скажу, что составляя программы на самом низком уровне,
используя отдельные подпрограммы ТР ДОС ПЗУ, можно вообще избавиться от такой
проблемы, как 112 байт системной области.
==================================================
ТАБЛИЦА 2 . СИСТЕМНЫЕ ПЕРЕМЕННЫЕ ТР ДОС.
==================================================
АДР16 ! АДР10 ! ДЛИНА ! НАЗНАЧЕНИЕ
------!---------------!---------------------------
#5СВ6 ! 23734 ! 1* ! Если = #F4 то INTERFACE 1
! ! ! не подключен.
! ! ! Если =00,то область пере-
! ! ! менных сдвигается для под-
! ! ! ключения ИНТЕРФЕЙСА 1
! ! ! и проверяется адрес 23832
------!-------!-------!---------------------------
#5СС2 ! 23746 ! 1* ! Содержит код #С9 . При вы-
! ! ! зове подпрограмм ПЗУ SOS-
! ! ! из ТР ДОС
------!-------!-------!---------------------------
#5СС8 ! 23752 ! 1 ! Содержит код #83 дла диско-
! ! ! вода 'А'
------!-------!-------!---------------------------
#5СС9 ! 23753 ! 1 ! Тоже самое для диска В
------!-------!-------!---------------------------
#5ССА ! 23754 ! 1 ! Тоже самое для диска С
------!-------!-------!---------------------------
#5ССВ ! 23755 ! 1 ! Тоже самое для диска D
------!-------!-------!---------------------------
#5ССС ! 23756 ! 1* ! Текущий сектор при чтении
! ! ! каталога
------!-------!-------!---------------------------
#5ССD ! 23757 ! 1* ! При готовности дисковода
! ! ! = #80
------!-------!-------!---------------------------
#5ССЕ ! 23758 ! 1* ! При 0 - чтение ,при # FF
! ! ! запись СЕКТОРА
------!-------!-------!---------------------------
#5СD6 ! 23766 ! 1* ! = #FF если комманда
! ! ! не выполнена
------!-------!-------!---------------------------
#5СD7 ! 23767 ! 2* ! 1.После проверки диска
! ! ! содержит
! ! ! количество цилиндров.
! ! ! 2.Промежуточный адрес в
! ! ! озу, файлов типа <В и С>
--------------------------------------------------
#5СD9 ! 23769 ! 2* ! 1.Адрес обрабатываемого
! ! ! символа команды ДОС
! ! ! 2.Промежуточная длина
! ! ! в ОЗУ файла типа <В и С>
------!-------!-------!---------------------------
#5СDB ! 23771 ! 2* ! Промежуточная длина прог-
! ! ! раммы
==================================================
Область заголовка (индификатора) файла
--------------------------------------------------
#5СDD ! 23773 ! 8 ! Имя файла , 8 знаков
------!-------!-------!---------------------------
#5СЕ5 ! 23781 ! 1 ! Тип файла < В,С,D,# >
------!-------!-------!---------------------------
#5СЕ6 ! 23782 ! 2 ! При <С> типе - начало файла
! ! ! в озу.
! ! ! При <В> типе - длина прог-
! ! ! раммы.
------!-------!-------!---------------------------
#5СЕ8 ! 23783 ! 2 ! Рабочая длина файла
------!-------!-------!---------------------------
#5СЕА ! 23786 ! 1 ! Объем файла в секторах
------!-------!-------!---------------------------
#5СЕВ ! 23787 ! 1 ! Номер первого сектора файла
! ! ! (0-15)
------!-------!-------!---------------------------
#5СЕС ! 23788 ! 1 ! Номер первой дорожки файла
==================================================
#5СЕF ! 23791 ! 1* ! = 1 если подключен
! ! ! ИНТЕРФЕЙС 1
------!-------!-------!---------------------------
#5СF4 ! 23796 ! 1* ! Номер сектора в текущий
момент
------!-------!-------!---------------------------
#5СF5 !23797 ! 1* ! Номер дорожки в текущий
! ! ! момент
------!-------!-------!---------------------------
#5СF6 ! 23798 ! 1 ! Номер активного дисковода,
! ! ! (0-3),
! ! ! для временной операции
--------------------------------------------------
#5СF7 ! 23799 ! 2 ! При выходе из 15616 содер-
! ! ! жит 0000
------!-------!-------!---------------------------
#5СF8 ! 23800 ! 1 ! Номер дисковода при
! ! ! операции с 2_мя файлами.
! ! ! Равен #FF, если канал
! ! ! открыт.
------!-------!-------!---------------------------
#5СF9 ! 23801 ! 1 ! 1.Номер дисковода при
! ! ! работе с 2мя файлами
! ! ! (0-3)
! ! ! 2.Признак операции = #00
! ! ! LOAD FILE
! ! !
! ! ! VERIFY = #FF
! ! ! 3.Номер канала в который
! ! ! подается листинг каталога
! ! ! (экран=2 ,принтер=3)
------!-------!-------!---------------------------
#5CFA ! 23802 ! 1 ! Время перемещения головки
! ! ! дисковода 'А' = #08
------!-------!-------!---------------------------
#5CFB ! 23803 ! 1 ! То же самое для диска В:
------!-------!-------!---------------------------
#5CFC ! 23804 ! 1 ! То же самое для диска С:
------!-------!-------!---------------------------
#5CFD ! 23805 ! 1 ! То же самое для диска D:
------!-------!-------!---------------------------
#5CFE ! 23806 ! 1* ! Текущая команда для
! ! ! 1818ВГ93
------!-------!-------!---------------------------
#5CFF ! 23807 ! 1* ! Номер сектора для
! ! ! подпрограммы
! ! ! чтение / запись секторов
------!-------!-------!---------------------------
#5D00 ! 23808 ! 2* ! Текущий адрес буфера =
! ! ! ( #5D25 )
------!-------!-------!---------------------------
#5D02 ! 23810 ! 2* ! Сохранение регистра HL
------!-------!-------!---------------------------
#5D04 ! 23812 ! 2* ! Сохранение регистра DE
--------------------------------------------------
#5D06 ! 23814 ! 1 ! Количество знаков на поиск
! ! ! файла
! ! ! изначально = 09 ;
! ! ! имя файла + тип
------!-------!-------!---------------------------
#5D07 ! 23815 ! 1* ! Счетчик удаленных файлов
------!-------!-------!---------------------------
#5D08 ! 23816 ! 1* ! Первый символ в имени
------!-------!-------!---------------------------
#5D0C ! 23820 ! 2* ! Состояние буфера ТРДОС
! ! ! для перекачки информации.
! ! ! (257 байт с адреса 23846 )
! ! ! 1. #FF - Буфера нет
! ! ! 2. #00 - Буфер включен
------!-------!-------!---------------------------
#5D0E ! 23822 ! 1* ! Принадлежность комманды.
! ! ! #FF - BASIC
! ! ! ИНАЧЕ - ТРДОС
--------------------------------------------------
#5D0F ! 23823 ! 1* ! Код ошибки ТРДОС
! ! ! 1. #00 - вводит пустую
! ! ! строку
! ! ! ИНАЧЕ - RETURN
------!-------!-------!---------------------------
#5D10 ! 23824 ! 1* ! Старший байт кода ошибки
! ! ! при вызове п/п 15616
! ! ! Обнулять принудительно
! ! ! при вызове п/п 15616 !!
------!-------!-------!---------------------------
#5D11 ! 23825 ! 2* ! Адрес командной строки
! ! ! ТРДОС.
! ! ! При вызове 15616 повторяет
! ! ! адрес командной строки SOS
! ! ! (23641)
! ! ! При вызове по 15619 равен
! ! ! 23645
------!-------!-------!---------------------------
#5D13 ! 23827 ! 2* ! Копия стека ошибок.
! ! ! При равенстве старшего
! ! ! байта #АА -
! ! ! происходит запуск 'boot'
! ! ! файла,
! ! ! а в 23833 записывается
! ! ! код #FE
--------------------------------------------------
#5D15 ! 23829 ! 1* ! При равенстве 0 печатает
! ! ! сообщения ТРДОС .
! ! ! Иначе не печатает.
------!-------!-------!---------------------------
#5D16 ! 23830 ! 1 ! Копия системного регистра
! ! ! ВГ-93 - порт #FF TRDOS
------!-------!-------!---------------------------
#5D17 ! 23831 ! 1 ! Вспомогательный байт.
! ! ! Если при вызове 15616
! ! ! байт НЕ РАВЕН #АА,
! ! ! то рисуется заставка ТРДОС
! ! ! и запускается файл 'воот'.
! ! ! Если байт равен #FF ,
! ! ! то система не попадает на
! ! ! подпрограмму'ОШИБКА' при
! ! ! чтении неверного АДРЕСНОГО
! ! ! МАРКЕРА.
------!-------!-------!---------------------------
#5D18 ! 23832 ! 1* ! Если байт равен #FF ,
! ! ! то подключается
! ! ! ИНТЕРФЕЙС 1
------!-------!-------!---------------------------
#5D19 ! 23833 ! 1 ! Дисковод для работы
! ! ! (0-3)
------!-------!-------!---------------------------
#5D1A ! 23834 ! 2* ! Внутренний адрес окончания
! ! ! подпрограмм ТРДОС .
------!-------!-------!---------------------------
#5D1C ! 23836 ! 2* ! Сохраняется SP
------!-------!-------!---------------------------
#5D1E ! 23838 ! 1 ! Системный номер файла в
! ! ! каталоге диска .
! ! ! ЕСЛИ ФАЙЛ ОБНАРУЖЕН!
------!-------!-------!---------------------------
#5D20 ! 23840 ! 3 ! Первые три символа введен-
! ! ! ной коммандной строки.
==================================================
ВНИМАНИЕ! СИМВОЛОМ '*' В ТАБЛИЦЕ ОТМЕЧЕНЫ БАЙТЫ, КОТОРЫЕ НЕ РЕКОМЕНДУЕТСЯ
МЕНЯТЬ В ПРОЦЕССЕ СТАНДАРТНОЙ РАБОТЫ С КОМАНДАМИ ТР ДОС !
==================================================
44444444444444444444444444444444444444444444444444444444444444444444444444444444444
ГЛАВА 4.
МАКРО-ФУНКЦИИ ТР ДОС (СТАНДАРТНЫЕ ПОДПРОГРАММЫ)
Теперь, когда, в предыдущих главах, мы немного разобрались в структуре
диска, рассмотрим как реализовать свои глубокие познания на практике и
попытаться пообщаться с дисководом на языке АССЕМБЛЕР'а.
Для облегчения жизни программистам, в ТРДОС выделены несколько функций,
через которые можно управлять дисководом из АССЕМБЛЕРа.
Назовем программирование,через эти функции - МАКРО УРОВНЕМ.
Функции ТРДОС - это всего лишь несколько подпрограмм в ПЗУ, вызываемых
через единственную точку входа, а адрес входа в эти подпрограммы для различных
версий ТРДОС - ОДИН И ТОТ-ЖЕ!
Все эти функции вызываются через адрес:
>>>>>>>>>> #3D13 ( 15635 ). <<<<<<<<<<<<
В регистр 'С' перед обращением к этому адресу, должен быть помещен номер
вызываемой функции.
НАПРИМЕР: LD C,0 нулевая функция
CALL #3D13 вызов функции
При вызове большинства функций, (а их не много, не мало 21), система
использует область системных переменных ТРДОС, а также использует перемещаемый
буфер.
Так что проблемы снижения RAMTOP и затирания системной области ТРДОС
остаются. Правда, есть функции, которые позволяют забыть об этом и выйти из
любого трудного положения.
Функции по назначению разделяются на четыре вида:
1. Функции записи данных на диск.
2. Фнкции считывания данных с диска.
3. Функции управления контроллером.
4. Вспомогательные функции.
Рассмотрим все функции подробнее.
--------------------------------------------------
1. ФУНКЦИИ УПРАВЛЕНИЯ КОНТРОЛЛЕРОМ.
--------------------------------------------------
* C=0 ВОСТАНОВЛЕНИЕ или СБРОС микросхемы 1818ВГ93.
Переход головок дисковода на дорожку 0.
Опрашивается нажатие клавиши STOP
(C/SH+BREAK)
Во время работы функции системная область ТРДОС НЕ ИСПОЛЬЗУЕТСЯ !
--------------------------------------------------
* C=1 ВЫБОР ДИСКОВОДА.
Номер выбираемого дисковода (от 0 до 4) помещают в регистр 'А'.
Если по адресу 23802 (#5CFA) + НОМЕР ВЫБИРАЕМОГО ДИСКОВОДА записать
код #FF, то произойдет полная инициализация дисковода, определится
СИСТЕМНАЯ ОБЛАСТЬ ТРДОС, и туда запишется количество дорожек диска,
величина позиционирования головок дисковода, и т.д.
В ячейку 23798 (#5CF6) занесется номер выбранного дисковода.
Во избежание нежелателных ситуаций рекомендуется: номер вызываемого
диска дублировать еще и в адреса 23800 (#5CF8) и 23801 (#5CF9).
Функция активно использует системную область ТРДОС !
--------------------------------------------------
* C=2 ПОЗИЦИОНИРОВАНИЕ (установка головок дисковода на нужный ЦИЛИНДР
диска).
В регистр 'А' необходимо записать номер ДОРОЖКИ !!
Дорожки с номерами 0,1 (верхняя и нижние стороны диска) будут
соответствовать ЦИЛИНДРУ номер 1, дорожки 2,3 - ЦИЛИНДРУ 2 и т.д.
Быстро вычислить соответствие номера ДОРОЖКИ и номера ЦИЛИНДРА можно
по формуле:
N_ЦИЛИНДРА= INT (N_ДОР /2 )
--------------------------------------------------
* C=#15 Проверка ЦИЛИНДРА.
Регистр 'D' должен содержать номер проверяемого цилиндра.
Сначала находится нужный цилиндр, затем проверяется сам цилиндр путем
считывания информации из системной области диска.
--------------------------------------------------
* C=#16 ЗАГРУЗКА СИСТЕМНОГО РЕГИСТРА (РЕГИСТР УПРАВЛЕНИЯ КОНТРОЛЛЕРА, порт
#FF).
Код управления контроллером заносится в регистр 'А'.
Предварительно к нему прибавляется #3С (подробнее о портах
контроллера и командах управления - смотри в последующих главах).
---------------------------------------------------
* С=#17 Выбрать нижнюю сторону диска (т.е., нечетные дорожки).
---------------------------------------------------
* С=#18 НАСТРОЙКА НА ДИСК.
Проверяется 8-й сектор нулевой дорожки. (Тип и емкость диска,
количество цилиндров).
_______________________________________________________________________
2. ФУНКЦИИ ЗАПИСИ ДАННЫХ НА ДИСК
--------------------------------------------------
* С=9 ЗАПИСЬ В КАТАЛОГ ДИСКА ИНФОРМАЦИИ О ФАЙЛЕ.
На диск записывается 16 байтов из области системного ОЗУ , т.е.
описатель файла из адреса 23773 (#5CDD)
8 байтов -имя + 1 байт - тип (B,C,D,@) или др.,
2 байта - начало в ОЗУ или длина Бейсика;
2 байта - длина файла ;
1 объем файла в секторах;
1 байт - номер первого сектора файла;
1 байт - номер первой дорожки файла;
В РЕГИСТРЕ 'А' должен быть номер посадочного места в каталоге, куда
эта информация запишется,(т.е. номер файла).
---------------------------------------------------
* С=#0С ЗАПИСЬ БЕЙСИК ПРОГРАММЫ.
С адреса 23773 (#5CD0) должны быть имя и тип файла.
Если тип файла отличен от 'В', то файл записывается под именем
'ВООТ'.
--------------------------------------------------
* С=#0В ЗАПИСЬ ФАЙЛА.
С адреса 23773 (#5CD0) должны быть;
имя и тип фаила.
В РЕГИСТРАХ 'HL' - адрес начала в памяти,
В РЕГИСТРАХ 'DE' - длина файла.
--------------------------------------------------
* С=6 ЗАПИСЬ ГРУППЫ СЕКТОРОВ
Мощная подпрограмма записи информации из ОЗУ на диск, не требующая
для своей работы дополнительного буфера перекачки.
Собственно область ОЗУ, которая должна сохраняться на диске, и будет
являться этим буфером.
В регистр 'В' помещается количество СЕКТОРОВ, записываемых подряд на
диск (или проще - количество 256 байтных блоков из ОЗУ).
В регистр 'HL' начальный адрес блока ОЗУ.
В регистр 'D' номер ДОРОЖКИ .
В регистр 'Е' номер СЕКТОРА в ДОРОЖКЕ, с которого будет начинаться
запись.
В регистре 'А' число 255 .
! ЕСЛИ регистр 'В' будет равен 0, запись производиться не будет.
----------------------------------------------------------------------------
3. ФУНКЦИИ СЧИТЫВАНИЯ ИНФОРМАЦИИ С ДИСКА
----------------------------------------------------------------------------
* С=#0А ПОИСК ФАЙЛА В КАТАЛОГЕ
Поиск файла производится по ИМЕНИ + ТИПУ файла.
Шаблон имени должен быть помещен с адреса 23773 (#5CDD);
8 символов имени + 1 символ - тип.
Если по адресу 23814 (#5D06) изменить начальное значение = 9, на
меньшее, то поиск будет производиться по меньшему количеству
символов.
Если шаблон имени найден в каталоге, то при выходе из подпрограммы
регистр 'С' будет содержать порядковый номер файла в каталоге;
то же продублируется в адресах системной области 23828 (#5D14) и
23823 (#5D0F).
Если шаблон (файл) не найден,то старший байт регистра 'С' будет
установлен в 1, т.е. номер будет больше, чем 127 (#7F), а в адресе
23823 (#5D0F) установится 255 (#FF).
Содержимое адреса 23828 (#5D14) не изменится.
--------------------------------------------------
* С=8 ЧТЕНИЕ ИНФОРМАЦИИ О ФАЙЛЕ ПО ЕГО НОМЕРУ.
В регистре 'А' должен быть номер интересующего Вас файла - (число от
0 до 127). После работы подпрограммы - 16 байт информации из
каталога диска перепишутся по адресу 23773 (#5CDD).
Даже,если заданный номер в регистре 'А' будет указывать на стертый
файл или вообще на пустое место в каталоге, то и в этом случае из
диска что-то перепишется.
--------------------------------------------------
* С=5 ЧТЕНИЕ ГРУППЫ СЕКТОРОВ С ДИСКА
Мощная функция считывания информации с диска в ОЗУ, не требующая
для своей работы буфера перекачки, (буфером является та область,
куда и считывается информация.
В регистр 'В' помещается количество СЕКТОРОВ, считываемых подряд с
диска (или проще - количество 256 байтных блоков).
В регистр 'HL' начальный адрес ОЗУ, куда будут считаны данные.
В регистр 'D' номер ДОРОЖКИ.
В регистр 'Е' номер СЕКТОРА в ДОРОЖКЕ, с которого будет начинаться
считывание.
Регистр 'А' должен быть равным 0.
! Если регистр 'В' будет равен 0, то считывания в ОЗУ не произойдет.
--------------------------------------------------
* С=#0Е ЧТЕНИЕ / ПРОВЕРКА ФАЙЛА.
Имя и тип файла должны быть сформированы с адреса 23773 (#5CDD).
При 'А', равном 0 ,файл загружается по адресу, указанному в заголовке
файла в каталоге.
При 'А', равном 3, файл загружается в адрес ОЗУ, указанном в регистре
'HL' и длиною из 'DE'.
При 'А' равном 255 , файл загружается в адрес из регистра 'HL',
длиною равной длине файла, указанной в каталоге на диске.
Если в системном адресе 23801 (#5CF9) будет записан 0, то происходит
операция 'LOAD'; если #FF, то 'VERIFY'
--------------------------------------------------
4. ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ.
--------------------------------------------------
* С=3 Перенос содержимого регистра 'А' в ячейку ОЗУ 23807 (#5CFF)- номер
сектора для п/п чтения-записи сектора.
--------------------------------------------------
* С=4 Перенос содержимого регистра 'HL' в ячейки ОЗУ 23808/23809
(#5D00/#5D01) - текущий адрес буфера перекачки.
---------------------------------------------------
* С=7 ВЫВОД КАТАЛОГА ДИСКА в канал, указанный в регистре 'А'. При 'А'=2 -
каталог выводится на экран. (аналогично комманде ТРДОС 'САТ').
При 'А'=3 - каталог выводится в принтер и т.д.
При исполнении данной функции автоматически исполняется функция #18.
--------------------------------------------------
* C=#12 Удаление ФАЙЛОВ.
Перед вызовом функции, в адресе 23773 (#5CDD) должны быть
сформированы ИМЯ И ТИП ФАЙЛА, а в адресе 23814 (#5D06) - количество
знаков на поиск шаблона.
Удаляются все файлы, попадающие под этот шаблон.
Удаление происходит путем записи в первый символ имени файла кода
01, в области каталога диска.
-----------------------------------------------------------------------------
* C=#13 Копирование 16 байт из области ОЗУ, указанной регистром 'HL' в ОЗУ по
адресу 23773 (#5CDD).
--------------------------------------------------
* C=#14 Копирование 16 байт из области ОЗУ, с адресом 23773 (#5CDD), в ОЗУ,
адрес которого указан регистром 'HL'.
--------------------------------------------------
ПРИМЕЧАНИЯ:
При записи файла или заголовка файла на диск функциями, в отличии от
команд ТРДОС, на диске можно образовать несколько файлов с идентичными именами,
а, также, с типами файлов - отличными от стандартных.
При работе с функциями не забывайте , что почти все функции изменяют
область СИСТЕМНЫХ ПЕРЕМЕННЫХ ТРДОС, а, также, требуют объем свободной памяти,
приблизительно равный объему памяти при работе с КОМАНДАМИ ТРДОС.
Самыми мощными, но и самыми 'опасными' функциями считывания/записи
являются функции #05 и #06, которые, в основном, и применяются при загрузке
больших или нестандартных файлов (опасными потому, что при ошибке во время
записи есть риск потерять диск или отдельные файлы на нем).
Если внимательно рассмотреть порядковый номер заголовков файлов в каталоге,
то нетрудно заметить, что СТАРШИЙ полубайт номера соответствует номеру сектора
на нулевой дорожке, а МЛАДШИЙ полубайт - номеру записи в секторе.
Используя вышеперечисленные функции, можно писать довольно приличные и
универсальные загрузчики и адаптировать большинство программ под дисковую
систему.
С помощью этих функций были адаптированы такие программы, как "ARTSTUDIO",
"TASWORD", "PASCAL", "GENS", и многие, многие другие, а уж об играх и говорить
не приходится.
ГЛАВА 4.1
ПРИМЕНЕНИЕ ФУНКЦИЙ ТРДОС.
Рассмотрим несколько примеров и приемов программирования на основе функций
ТРДОС.
Пример 1.
---------
В первую очередь, при адаптировании программы на диск, программа может
определять: "А подключен ли вообще контроллер ?" (такая проблема может
возникнуть, если Ваша программа универсальна, т.е. работает и на магнитофоне
и на диске).
Используя функцию #13, это сделать довольно просто;
LD C,#13 функция переноса байтов
LD HL,#0900 адрес окуда перенести 16 байт
CALL #3D13 перенос 16 байт в адрес #5CDD
(если контроллера нет,то CALL #3D13 равносильно RST #38
т.к. в пзу SOS по этому адресу расположен код #FF.
А RST #38 это программа опроса клавиатуры).
LD A,(#5CDD) если контроллер есть, на время выполнения функции включится
ПЗУ ТРДОС и 16 байт перепишутся из адреса #0900, а там
код #FF
CP #FF это ТРДОС ПЗУ ?
JR NZ, NODISK нет - переход на работу с лентой
DISK .... да - инициализация диска
Приблизительно, так же можно определить номер версии ТРДОС. Только в
регистр 'HL' помещается число #0360 (по этому адресу, в ПЗУ ТРДОС находится
текстовое сообщение:'ver 5.01 *'. В разных версиях ТРДОС текстовое сообщение
может находиться по разным адресам, но, как правило, в пределах 255 байт от
этого места).
start LD C,#13 функция
LD HL,#0360 адрес текста версии
CALL #3D13 перенос 16 байт в адрес #5CDD
LD A,(#5CDD) загрузим первый байт сообщения
CP ... здесь идет сравнение перенесенных байтов.
...........
Как уж Вы будете сравнивать и с чем, - это уже зависит от Вашей фантазии.
Самое главное, что у Вас по адресу #5CDD есть 16 байт из ПЗУ ТРДОС. Не найдете
номер версии по адресу #0360, прибавляйте (или отнимайте) от этого адреса по 16
и выполняйте функцию снова.
Можно поступить еще проще, если заранее знать у какой версии ТРДОС какие
байты по одному и тому-же адресу различны.
Составив таблицу байтов из этого адреса, но из разных версий ТРДОС, Вы
будете проверять всего лишь по одному байту, сравнивая его с таблицей.
Такое определение версии ТРДОС необходимо в том случае, если в Вашей
программе используются методы программирования непосредственно микросхемы ВГ93
через процедуры ПЗУ. А так как у разных версий ТРДОС эти процедуры расположены
по разным адресам, то определение версии ТРДОС является необходимым для
нормальной работы Вашей программы в любом компьютере, а не только в Вашем!
С главы 5 этой книги начнется материал, как раз и посвященный таким
методам программирования, а пока...
Пример 2.
---------
Попробуем теперь распечатать каталог диска
.. на экране
LD A,02 номер канала для вывода
(экран)
LD C,07 функция
CALL #3D13 вывести
;
.. на принтере
LD A,03 номер канала ( принтер)
LD C,07 функция
CALL #3D13 вывести на принтер
;
Конечно, когда Вы будете выводить каталог на принтер, необходимо сначала
загрузить драйвер принтера с диска или из ПЗУ (если, конечно, он у Вас 'зашит'
в ПЗУ) и включить принтер.
В связи с этой программкой возникает интересная возможность: если
вспомнить, что последовательные файлы ТРДОС резервируют каналы с 4 по 15, то
что нам мешает 'распечатать' каталог в ФАЙЛ? Надо только открыть файл, связать
его с номером канала, и в этот канал вывести каталог.
На БЕЙСИКЕ это бы выглядело так:
10 RANDOMIZE USR 15619:REM:OPEN# 4,"CATALOG",W
20 RANDOMIZE USR 15619:REM:CAT #4
30 RANDOMIZE USR 15619:REM:CLOSE# 4
Вместо оператора 'САТ' можно использовать и 'LIST'. Тогда в ФАЙЛ "CATALOG"
запишется полная информация о файлах на диске.
Таким образом, можно в файлах хранить каталоги всех дисков которые у Вас
есть. А если в каком-нибудь 'ДИСК ДОКТОРЕ' исправить тип файла в каталоге с
'#' на 'С' , то этот файл можно загрузить в текстовый редактор , например TLW .
Рассмотрим теперь несколько примеров считывания/записи на диск. Опустим
только случаи , когда условия благоприятны; т.е. RAMTOP установлена достаточно
высоко, область системных переменных БЕЙСИКА и ТРДОС не затронута (как в таких
условиях произвести считывание/запись, я надеюсь, Вы разберетесь сами).
Разберем более тяжелые случаи адаптации программы с магнифона на диск ...
Пример 3.
---------
RAMTOP программы расположена очень низко, программа с магнитофона
загружается загрузчиком в машинных кодах с адреса, например, 23867 (#5D3B),
т.е. прямо в область БЕЙСИКА. Но области системных переменных БЕЙСИКА и ТРДОС
не затронуты.
Первое, что необходимо сделать - это просто перекопировать программу для
адаптации с ленты на диск, каким-нибудь копировщиком (TAPE -> DISK).
При работе со скопированным файлом надо знать, что длина файла и адрес
загрузки в заголовке файла, как правило, указаны НЕВЕРНО! Такое встречается в
90% магнитофонных программ.
СЧИТЫВАНИЕ ФАЙЛА С ДИСКА
Для загрузки файла с диска, нам нужен настоящий адрес посадки программы в
ОЗУ и реальная длина блока программы. И, конечно, необходим адрес запуска
программы.
Очень надеюсь, что взламывать магнитофонные загрузчики Вы уже умеете и,
в конечном итоге, у Вас есть все то ,что нужно для работы.
В любом случае, нам необходимо использовать ТРДОС функцию С=5, т.е.
непосредственно считать сектора файла, или прямо по нужному месту в ОЗУ,
или через какой нибудь буфер (например, экранный).
;-------- LOADER -----
; СТАРТ прогграммы с метки START
;
; для начала поместим имя и тип интересующего нас
; файла (9 байт) по адресу #5CDD.
; затем вызовем функцию ПОИСК файла (#0A).
; и если файл обнаружен , то прочтем информацию о
; нем т.е. весь заголовок файла из каталога.
FILENAME DEFM "filename" ; имя файла
DEFM "C" ; тип файла
START LD HL,FILENAME ; перенесем 9 байтов
LD DE,#5CDD ; в системную
LD BC,9 ; область ТРДОС
LDIR
FIND LD C,#0A ; поиск заданного
CALL #3D13 ; файла
BIT 7,C ; такой файл есть?
JP NZ,NO_FIND ; нет, выход
LD A,C ; есть, тогда
LD C,8 ; прочтем о нем
CALL #3D13 ; информацию
; Теперь с адреса #5CDD у нас есть вся информация
; о файле для его загрузки.
; Нам нужно взять номер сектора, номер дорожки и
; занести в регистр 'DE', в регистр 'В'-
; количество секторов, а в регистр 'HL'- адрес
; загрузки в ОЗУ.
LOAD LD DE,(#5CEB) ; в 'D' номер трека
; в 'Е' сектор
LD BC,(#5CE9) ; в 'В' количество
LD HL, ADRES ; адрес загрузки
LD C,05 ; функция
CALL #3D13 ; загрузить
Если реальная длина загружaемого блока в байтах кратна 256 или у Вас есть
уверенность, что последние 'лишние' байты последнего загружаемого сектора
не затрут нужную информацию в ОЗУ, то чтение секторов можно производить прямо
по адресу загрузки. Если нет - то загрузку необходимо производить через буфер
или загружать в буфер последний сектор файла, и оттуда переносить нужные
последние байты на место (в этом случае, Вам необходимо вычислить номер
последнего загружаемого сектора.)
Можно, конечно, написать загрузчик таким образом, чтобы он загружал всю
программу через буфер в 256 байт. К примеру: нам надо загрузить файл длиною в
40100 байтов, в адрес 24000. 40100 байт равны 157 секторам на диске (156
секторов + 164 байта из 157 сектора).
;------------- LOADER 2 -------
; Загрузчик программы через буфер в 256 байт.
; регистр 'В' занести количество секторов
; регистр 'D' - номер первого сектора файла на
; диске
; регистр 'Е' - номер начальной дорожки файла
;
LD SP,STEK ; установим стек на
; свободное место в
; ОЗУ,в нашем случае
; можно на 65535
START1 LD HL,BUFFER ; адрес буфера перекачки,
; (к примеру 16384 )
LD C,05 ; функция чтения
PUSH BC ; спрячем
LD B,1 ; количество загружаеных
; секторов за один раз
CALL #3D13 ; загрузим с диска в буфер
; 1 сектор = 256 байт
LD HL,BUFFER ; начало буфера
LD DE,(ADRES) ; истинный адрес посадки
; в ОЗУ
LD BC,256 ; 1 сектор
LDIR ; перенесем загруженный
; сектор туда,
; где он должен быть
LD (ADRES),DE ; спрячем адрес, куда пере-
; носить следующую порцию
LD DE,(#5CF4) ; возьмем из системной пе-
; менной ТРДОС - ТЕКУЩИЙ
; номер сектора и ТЕКУЩИЙ
; номер дорожки ФАЙЛА
; (при исполнении функции
; эти данные заносятся
; в адрес #5CF4 системой
; автоматически )
POP BC ; востановим общее ко-
; личество загружаемых
; секторов
DJNZ START1 ; все сектора считаны ?
; нет - загружай еще
LD HL,BUFFER ; да, тогда подгрузим
; последний сектор
LD C,05 ;
LD B,01
CALL #3D13
LD HL,BUFFER ; и занесем его на
LD DE,(ADRES) ; место
LD BC,164
LDIR
JP... ; стартовать программу
ADRES DEFW 24000 ; здесь адрес загружаемой
; программы
;----------------
Конечно, данный пример не очень изящен, и опытный читатель сразу заметит
- вот тут бы подпрограммочку, а тут можно было бы по другому. Все правильно,
данный пример показывает принцип загрузки, а уж как Вы этот принцип примените,
это дело Ваше.
При необходимости, можно отказаться от опроса системной переменной #5CF4
и написать два вложенных цикла; один из которых считал бы сектора и при
переходе через число 16 (16 секторов на дорожке), увеличивал бы номер дорожки
на единицу (не забудьте, что функции ТРДОС работают с номерами секторов от 0
до 15). Загрузчик увеличился бы в объеме, но выиграл бы в универсальности.
И еще: данный способ загрузки через буфер страдает некоторой
медлительностью (в 1,5 раза медленней, чем это делает ТРДОС). Но этот метод
работает, и работает неплохо.
Если областью работы данного загрузчика выбрать, к примеру, экран, на
экран же определить стек и буфер перекачки, то, в принципе, можно загружать
ЛЮБЫЕ файлы, не попадающие на системную область ТРДОС. Или, вообще, любые файлы,
если загружать блоки программы, затирающие системную область, сначала в
промежуточный буфер и, перебрасывая в конце загрузки эти блоки на нужное место.
Правда, это будет удобнее сделать другим методом, но об этом мы расскажем в
главах, посвященных программированию непосредственно микросхемы 1818 ВГ93 .
* НЕСКОЛЬКО ОБЩИХ ЗАМЕЧАНИЙ И ДОПОЛНЕНИЙ НА БУДУЩЕЕ *
- дисковая система не любит ВТОРОГО ПРЕРЫВАНИЯ, так что первой Вашей командой
должна быть команда IM1 и DI;
- в любом случае, лучше всего запретить прывания вообще или запрещать их перед
обращением к диску;
- если у Вас 2 и более дисковода, то выберите сначала номер дисковода, с
которым будете работать;
- при первом опробовании Ваших программ желательно ЗАЩИТИТЬ ДИСК ОТ ЗАПИСИ -
заклейте прорезь на диске, и Вы избежите случайных неприятностей
ПРИМЕР 4.
----------
Теперь давайте рассмотрим случай более 'тяжелый'. Системные переменные
ТРДОС и системная область БЕЙСИКА затерты кодами программы. Стек в программе
установлен или на область экрана, или на самую верхушку ОЗУ, или в другое место.
Программа в своей работе догружает другие уровни или свое состояние.
Для начала необходимо выбрать место для нашей подпрограммы загрузчика.
Это может быть та область, где помещались коды загрузчика с ленты, а если места
там не хватит, то это может быть место, где располагается подпрограмма вывода
имен создателей программы в заставке (иногда такие подпрограммы в заставках
занимают до нескольких килобайт памяти).
А если, все-таки, места свободного в программе нет, то можно впихнуть свою
"загрузку" в какую-нибудь второстепенную подпрограмму или пожертвовать частью
какой-нибудь картинки. В общем, если захотеть, можно выкрутиться. В крайнем
случае можно пожертвовать и звуком в программе.
Самая тяжелая ситуация, с которой приходится сталкиваться - это
загрузка-выгрузка состояния игры в программах типа "SIM SITY", "ELITE",
"TAY 2" и т.д.
В этих программах, казалось, байта лишнего не выкинешь, но,"разложив"
программу по полочкам, может оказаться, что из нее безболезненно можно было бы
удалить 0.5 - 1 килобайт. А для дискового загрузчика этого ПРЕДОСТАТОЧНО !
Но вот свободное место в программе найдено, подсчитана длина и место
посадки загружаемого блока в программе. Теперь осталось только загрузить...
Для начала инициализируем область системных переменных ТРДОС ;
START LD HL ,#5C00 ; сохраним область ОЗУ
LD DE ,BUFFER ; с #5C00 по #5DF4
LD BC ,#01F4 ; в другое место
LDIR ; (например экран)
LD HL ,#5C00 ; очистим это место
LD DE ,#5C01 ; на всякий случай
LD BC ,#01F3
LD (HL),L
LDIR
LD (ADRES),IY ; спрячем текущее значение
; регистра IY
LD IY,#5C3A ; инициализация индексного
; регистра на область пере-
; менных SOS
IM 1 ; прерывание 1
LD A ,#FF ; переменная SOS код сооб-
LD (#5C3A),A ; щения об ошибке
LD (#5D0C),A ; переменная ТРДОС -буфер
; перекачки отсутствует
LD A ,#C9 ; переменная ТРДОС для
LD (#5CC2),A ; возврата из подпрограмм
LD A,#83 ; код для дисковода 'А'
LD (#5CC8),A
LOADFILE CALL LOADER ; загрузим файл с диска
LD IY,(ADRES) ; востановим все
LD HL,BUFFER ; на
LD DE,#5C00 ; старое
LD BC,#01F4 ; место
LDIR
RETURN JP...(RET) ; выйдем в программу
;- - а это сам загрузчик - - - - - - - - - - -
LOADER DI
LD C,01 ; выберем дисковод 'А'
LD A,0
CALL #3D13
LD HL,FILENAME ; перенесем ИМЯ и ТИП
LD DE,#5CDD ; файла в системную
LD BC,9 ; область
LDIR
... ; И ТАК ДАЛЕЕ, СМОТРИ
; ПРЕДЫДУЩУЮ ПРОГРАММУ ...
;- - - - - - - - - - - - - - - - - - - - - - - - - - -
Вот таким, примерно, способом и загружаются не только уровни игры в
программах, но иногда и сама программа тоже.
! Если в процессе своей работы, программа использует прерывание 2, то на
выходе из загрузчика не забудьте его вернуть программе!
ПРИМЕР 5.
---------
А вот другой, довольно оригинальный, метод загрузки уровней для программ,
с использованием функции #0Е: чтение ФАЙЛА через буфер ТРДОС. Правда,
этот метод требует много места в ОЗУ.
В теле основной программы Вы должны найти место не только для своего
загрузчика, но и для копии всей инициализированной области системных переменных
ТРДОС, и в нужный момент перенести всю область на свое исконное место.
; loader level game
;
START LD DE,#5CC8 ; перенесем копию систем-
; ной
LD HL,BUFFER_1 ; области на место
LD BC,#32
LDIR
LD A,(LEVEL) ; здесь содержится номер
; загружаемого уровня
ADD A,#31 ; приведем номер к коду
; цифр таблицы ASC
LD (#5CE4),A ; занесем номер уровня в
; имя файла
DI
LD HL,BUFFER_2 ; перенесем вторую поло-
; вину
LD DE,#5CFB ; копии системной области
LD BC,#20 ; ТРДОС на место
LDIR
LD (ADRES),IY ; спрячем текущее значе-
; ние IY
LD IY,#5C3A ; занесем адрес системной
; области БЕЙСИКА (SOS)
LD (IY+00),#FF ; инициализируем адрес
; кода ошибки
IM 1 ; прерывание 1
LD A,#C9 ; код возврата из под-
; програм
LD (5CC2),A ; занесем в область SYS
; TRDOS
LD HL,#5D27 ; адрес вершины стека
; калькулятора
LD (#5C65),HL ; БЕЙСИКА
XOR A ; адрес загрузки взять из
; каталога на диске
LD C,#0E ; загрузить файл адресом
; и
CALL #3D13 ; длиною, как в каталоге
... ; здесь может идти проверка
; загрузился ли файл и тот ли
; файл загрузился.
RET ; выход в программу
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
В этом случае, во всех заголовках загружаемых файлов в каталоге должна
быть указана реальная длина и истинный адрес загрузки блоков.
ПРИМЕР 6.
---------
Рассмотрим еше один способ загрузки с диска: это, так называемый, метод
"склеенных" файлов. На диске такие файлы выглядят единым файлом БЕЙСИКА, но
содержат в себе и коды экрана-заставки и рабочие коды программы. Такие файлы,
на мой взгляд, не являются крайней необходимостью, но они есть, и с ними надо
считаться.
Способ создания таких файлов прост... (естественно, что простыми командами
ТРДОС тут не обойтись, и загружать такой файл мы будем своим загрузчиком).
При создании такого файла желательно, чтобы файлы на диске располагались
по порядку их загрузки, ПОСЛЕ файла БЕЙСИКА (можно, конечно их расположить в
любом порядке, но это очень усложнит Вам вычисления в процессе работы Вашего
загрузчика). Ну, и, естественно, эта программа не должна содержать других,
старых - "чужих" подпрограмм загрузки с диска , а то может произойти конфликт.
Захотелось, к примеру, Вам объединить в 'склееный' файл программу
"PULSE WARRIOR ". Вся программа состоит из 3х файлов:
"PULSE.WR" - файл БЕЙСИКА
"pulse.w$" - файл экрана
"pulse.w1" - файл основного тела программы
Из программы БЕЙСИКА мы узнаем ,что сначала грузится файл экрана, а затем
основные коды программы по адресу 24500. RAMTOP снижена оператором CLEAR на
24499. Стартует программа по адресу 24500 , а затем по адресу 62003.
Для начала на диск запишем простую программу на БЕЙСИКЕ под именем
"PULSE.W" и зарезервируем в первой строке, после оператора REM, область под наш
загрузчик .
1 REM : В этой строке резервируется место под наш
загрузчик; (эдак, байтов на 40)
2 RANDOMIZE USR 23872 :REM: А это запуск загрузчика
Далее, на ДРУГОЙ диск копировщиком копируем 2 файла
" pulse.w$" и "pulse.w1"
И теперь нам потребуется программа, которая могла бы редактировать сектора
диска на нулевой дорожке, т.е. область каталога.
Это может быть "DISK DOCTOR", а может, Вы захотите написать такую
программку сами: тех знаний, которые Вы уже получили, дочитав до этого места -
вполне хватит (всего лишь и надо считать с диска в ОЗУ первые СЕМЬ секторов
нулевой дорожки, изменить с помощью какого-нибудь MONS'а в этой области ОЗУ
всего лишь 1 байт и записать эту область ОЗУ на диск туда же, откуда и
считывали).
В области каталога находим имена наших программ и внимательно смотрим на
байты, отвечающие за ОБЪЕМ ФАЙЛА В СЕКТОРАХ (14-й байт от первой буквы имени
файла). В нашем случае это будет (к примеру):
у файла " pulse.w$" - 27 секторов ( #1B ),
а у "pulse.w1" - 101 сектор ( #65 ).
Складываем в уме 101 и 27 и получаем 128 (# 80) секторов. Аккуратно
вписываем эту цифру в заголовок файла "pulse.w$" вместо цифры 27 (#1В) и
записываем изменения на диск.
Теперь у нас файл "pulse.w$" по объему секторов стал равен объему обоих
файлов, т.е. он стал содержать в себе и экран, и рабочие коды программы.
Возвратимся к первому диску, и в файле БЕЙСИКА в зарезервированном месте
первой строки, с адреса 23872 (#5D40), любым способом вписываем коды
следующей программы:
; КОДЫ в 'HEX' МНЕМОНИКА
ORG #5D40 ; адрес первой
; строки 23872
31,B3,5C LD SP,#5FB3 ; CLEAR 24499
F3 DI
ED,5B,F4,5C LD DE,(#5CF4) ; текущая дорож-
; ка и сектор
21,00,40 LD HL,#4000 ; адрес экранной
; области
01,05,1B LD BC,#1B05 ; 27 секторов или
; 6912 байт
; и функция
; #05 в 'С'
CD,13,3D CALL #3D13 ; загрузить на
; экран.
ED,5B,F4,5C LD DE,(#5CF4) ; взять следующие,
; текущие
; дорожку и сектор
21,B4,5F LD HL,#5FB4 ; адрес 24500
01,05,65 LD BC,#6505 ; 101 сектор
CD,13,3D CALL #3D13 ; загрузить сектора
; в ОЗУ
CD,B4,5F CALL #5FB4 ; первый старт прог-
; раммы
; по адресу 24500
C3,33,F2 JP F233 ; второй старт прог-
; раммы
; по адресу 62003
Теперь, как только коды загрузчика окажутся на месте, и БЕЙСИК-программа
запишется на диск, например, под именем " P.W.", нам остается со второго
диска скопировать сюда наш склеенный файл.
И опять программой ДИСК ДОКТОР внесем изменения в область каталога.
А именно, посмотрев сколько занимает секторов наш БЕЙСИК-файл с загрузчиком,
складываем (опять в уме) это число с объемом нашего 'склеенного' файла и
вписываем полученный результат в байт 'количество секторов' в БЕЙСИК заголовок.
В нашем случае количество секторов у БЕЙСИК файла равно одному, а общее
количество стало быть 129 (#81).
Все - у нас на диске оказался 'склеенный' файл с именем "P.W." и типом
БЕЙСИК. Остальные файлы можно безболезненно стирать.
При запуске такого файла система загрузит поначалу только БЕЙСИК часть, а
уж БЕЙСИК нашим загрузчиком догрузит все остальное.
И последнее, что можно сказать по теме 'Считывание с диска', это то, как
можно разделять большие файлы ,записанные на диск с магнитофонной ленты.
В кассетных версиях, есть такие программы, которые загружаются единым файлом,
начиная с экрана и почти до конца ОЗУ компьютера. Такие файлы могут иметь
длину до 45 Килобайт и больше, есть файлы даже в 49 Килобайт. Таких программ
много, и возникает вопрос:
" ЧТО ДЕЛАТЬ ?"
Один способ известен уже давно - это применить популярный кассетный
копировщик "COPY-COPY" для разделения программы на несколько кусков. Такими
составными коммандами копировщика, как
* LOAD (ХХ - загрузка первых ХХ байтов файлa;
* LOAD (ХХ TO - загрузка файла БЕЗ первых ХХ байтов,
можно изловчившись за 1-2 раза разделить любой файл. Для дисковых версий такие
большие файлы легче делить на куски длинной кратной 256 (т.е. размеру сектора).
Выделить, к примеру, кусок экрана:
длина = 6912 байт = 27 секторам .
Второй кусочек областью от адреса 23296 до 25343
длина = 2048 байт= 8 секторам.
И последнй кусок - с адреса 25344 и до конца файла.
Переписав без особого труда эти кусочки на диск, мы сначала будем загружать
экран, затем кусок с адреса 25344 и оставшийся кусочек в 2 Килобайта в экранную
область. Единственное, что нам останется сделать, так это предварительно вписать
в свободное место на экране, или в другое место, коротенькую программу
переброса этих самых 2048 байт на свое законное место, т.е. на адрес 23296.
Последней строкой в БЕЙСИКЕ у нас должна находиться строка RANDOMIZE USR <адрес
программы переброса>, а последними строками программы переброса - команды
LD SP,STACK ; 'родной' стек программы
JP START ; запуск программы
Но есть и другой способ разделения файлов. Если у вас файл 'влез' в
копировщик "TAPE <-> DISK" (например, копировщик "L-COPY" может записать файл
длиною в 45056 байтов), то такой файл можно разделить и на диске (если у Вас
компьютер 128 Килобайт ,то копировщик ТD.COPY может взять в себя любой
сверхдлинный файл с ленты и записать его на диск).
Но вот Вы скопировали файл с ленты на диск... После копирования, на диске
у Вас получился файл длиною в 45056 байт (#В000), или 176 (#В0) секторов и
адресом загрузки 16384 (#4000). Стартуем программу 'DISK DOKTOR' и выходим в
режим редактирования НУЛЕВОЙ дорожки. Находим заголовок нашего файла,
(предположим имя файла у нас - "FILE 1"). Внизу этого заголовка создаем еще 2
заголовка (каждый заголовок занимает 16 байт).
- сначала впишем имена "FILE 2" и "FILE 3";
- затем в обоих файлах пометим ,что это коды, вписав после имен файлов
символ 'С';
- после идут 2 байта длины программы в байтах. Сделаем файл "FILE 2" длиною в
2048 (#0400) и адресом загрузки 23296 (#5В00), а "FILE 3" - длиною
в 36096 (#8D00) и адресом загрузки 25344 (#6300). Файл же "FILE 1" будет
экраном, загрузка в 16384 (#4000) и длиною 6912 (#1В00);
- изменим во всех заголовках байты, отвечающие за адрес загрузки, (два байта)
и длины (два байта);
- затем изменим байт, отвечающий за количество секторов; первый файл у нас
экран, длиною в 27 (#1В) секторов, второй #04 сектора и последний 141 (#8D)
секторов.
- теперь начнем изменять последние два байта в каждом загловке: байт 'номер
первого сектора файла' и байт 'номер начальной дорожки файла';
- изменять заголовок файла "FILE 1" мы не будем, а только возьмем из него
значение байта "СЕКТОР" и байта " ДОРОЖКА";
- подсчитаем, какой номер Начального сектора у нас будет во втором файле
"FILE 2". Сложим номер начального сектора файла "FILE 1" с объемом файла в
секторах. Полученный результат НАЦЕЛО разделим на 16. ОСТАТОК от деления есть
НОМЕР начального сектора файла "FILE 2" (если остатка нет, то номер сектора
будет 0);
- число же, получившееся при делении, надо сложить с числом 'НОМЕР ДОРОЖКИ'
файла "FILE 1", и мы получим номер дорожки файла "FILE 2" (естественно, если
результат деления меньше единицы, то это НОЛЬ, и складывать номер дорожки надо
с НОЛЕМ, т.е. следуюший файл расположен на этой же дорожке);
- вписываем полученные резултаты вычислений СЕКТОРА и ДОРОЖКИ в заголовок
файла "FILE 2".
- точно такие же вычисления произведем для последнего файла, "FILE 3",
только исходя уже из данных файла "FILE 2".
- после того, как все подсчитано, все байты записаны, нам остается изменить
один байт на ВОСЬМОМ системном секторе нулевой дорожки, а именно - байт
"количество файлов на диске". Расположен он на 228 (#Е4) месте от начала
сектора. Увеличим его значение на 2;
- если Вы сделаете все правильно, то у Вас на диске теперь будет вместо
одного большого - 3 файла, и Вам останется только написать для них программу
загрузки (что не составит для Вас, я надеюсь, теперь большого труда).
Приблизительно так же можно разделить дисковый MAGIC- файл, и создав свой
загрузчик запускать его не командой GOTO , а 'нормальными' командами. Но об
этом чуть позже - MAGIC-файлам будет посвящена отдельная глава.
А теперь поговорим о ЗАПИСИ ...
Наиболее часто ЗАПИСЬ файла на диск необходима в системных программах, где
активно идет обмен информацией с различными файлами. Это и БАЗЫ ДАННЫХ, и
ЭЛЕКТРОННЫЕ ТАБЛИЦЫ, и ПОИСКОВЫЕ СИСТЕМЫ. Или это файлы, где необходимо
сохранять и считывать пользовательские наработки (РЕДАКТОРЫ ТЕКСТОВ, ГРАФИЧЕСКИЕ
РЕДАКТОРЫ, АССЕМБЛЕРы, и др.).
В некоторых случаях проблема решается средствами стандартных команд ТРДОС,
в некоторых средствами ФУНКЦИЙ ТРДОС, а в иных (таких мало), прямым
программированием контроллера (например, текстовый редактор WORD, различные
D.C.U., A.D.M., форматеры, исправители дисков, система IS.DOS и др.). В играх
ЗАПИСЬ используется в случаях, когда нужно сохранить состояние игры ("SIM SITY",
"ELITE", "TAU CETI"), или когда необходимо записать результаты игры - таблицу
выигрышей.
Как и в предыдущих случаях, мы не будем рассматривать методы, основанные
на командах ТРДОС и случаи, когда всего везде хватает. Единственное, что можно
сказать по этому поводу, так это то, что такими способами, (когда все
"тип-топ"), адаптированы графический редактор "ARTIST 2", текстовый редактор
"T.L.W.+" и многие другие программы.
Для случаев, когда можно использовать функцию #0В - запись файла и #0С -
запись файла БЕЙСИКА, единственное добавление: Ваша программа сначала должна
проверить имя Вашего записываемого файла на предмет отсутствия такого же имени
на диске. И если такое имя на диске есть, то 'задать' вопрос о смене введенного
имени или о стирании старого файла с диска.
Если этого не сделать, то у Вас на диске могут оказаться файлы с идиентичными
именами и типами, а это недопустимо, если поступать по правилам!
Рассмотрим, в идеале, что должна делать программа, которая записывает
данные на диск в виде файла:
1. Выбрать дисковод ( #01 функция).
2. Настроить диск ( #18 функция).
3. Запросить у пользователя имя файла (длину, адрес начала и тип ), а если надо,
то распечатать на экране каталог диска.
4. Перенести введенную информацию в системную область, в адрес #5CDD.
5. Дать команду "ПОИСК файла на диске".
6. Если имя такое есть, запросить об удалении старого или об изменении
введенного имени.
7. Удалить старый файл, если нужно.
8. Узнать из 8-го системного сектора диска первый СВОБОДНЫЙ сектор и дорожку,
а, также, количество свободных секторов на диске.
9. Подсчитать, хватит ли места на диске для Вашей программы, и если нет, то
попросить сменить диск.
10.Узнать из 1 - 7 сектора каталога первое свободное место для заголовка (по
символу 0).
11.Если все в норме, то записать полный заголовок Вашего файла в область
каталога на первое свободное место.
12.Записать блок данных из ОЗУ на диск с первого СВОБОДНОГО сектора диска.
13.Скорректировать данные на 8-ом системном секторе диска, а именно:
байт 'количество свободных секторов',
байт 'количество удаленных файлов',
байт ' первый свободный сектор',
байт ' первая свободная дорожка',
байт ' число файлов на диске',
14. Выйти в основную программу.
Конечно, на практике половина этих пунктов не соблюдается; для этого бы
потребовалось слишком много места в ОЗУ, но некоторые пункты являются
обязательными.
Встает вопрос: "Как же впихнуть такую огромную подпрограмму в, и без того,
ограниченное пространство ОЗУ игровой или системной программы" ?
Если внимательно присмотреться, то можно заметить, что, как для загрузки,
так и для записи, МИНИМАЛЬНЫЕ данные которые нам требуются - это:
. номер первой дорожки файла,
. номер начального сектора файла,
. реальная длина сохраняемых/загружаемых блоков.
И если точно знать первые два параметра и передавать их в подпрограмму ЗАПИСИ,
то можно избавиться от многих проблем. Но как же узнать эти самые параметры и
как вносить эти все изменения в системную область диска? Да при помощи БЕЙСИКА!
Он нам будет и создавать файл и делать все остальное...
Он же передаст все параметры этого файла в программу.
Назовем этот способ работы:
'МЕТОДОМ ФИКСИРОВАННОГО ФАЙЛА'
----
В поцессе ВЗЛОМА адаптируемой программы, скинем на диск область ОЗУ, где лежат
те данные, которые программа записывает и считывает во время своей работы.
Это нужно делать в первичном состоянии игры, т.е. тогда, когда в области
данных не произошли изменения.
Предположим, что область данных программы занимает у нас 256 байт. После
записи на диск у нас получится файл состояния игры, например, с именем
"level 0" и длиною 256 байт. Теперь нам надо написать БЕЙСИК файл, который
будет изначально формировать файл состояния игры, а также запускать игру и
передавать данные о файле-подпрограмме работы с диском.
Сделаем запрос имени файла, с которым потом будет работать программа, и
перезапишем данные из файла "level0 " в файл с введенным именем.
10 INPUT " FILE ACCES ? "; A$
20 RANDOMIZE USR 15619:REM:LOAD"level 0"CODE 40000
30 RANDOMIZE USR 15619:REM:SAVE A$ CODE 40000,256
Теперь, когда файл состояния пересоздался (продублировался) с введенным именем,
нам нужно найти всего лишь НОМЕР его начального СЕКТОРА и НОМЕР ДОРОЖКИ.
Это без труда делается функцией:
#0А - поиск файла по имени и типу (имя и тип файла по адресу
системных переменных у нас уже есть - результат работы строки 30 БЕЙСИКА),
а затем функцией:
#08 - чтение информации о файле.
После этого осталось лишь сохранить в какой-нибудь ячейке ОЗУ данные из
адреса #5CF4 / 5CF5 (сектор/дорожка), и можно приступать к загрузке тела игровой
программы (можно сделать еще проще, если изначально все данные для файла
состояния поместить в БЕЙСИК файле в НУЛЕВОЙ строке. Там же поместить и
программу-опеделитель параметров файла).
В теле игровой программы нами тоже должны быть внесены изменения.
Вместо обращения программы к магнитофону, мы должны вписать подпрограмму
загрузки/выгрузки на диск, опираясь на сохраненные данные о секторе и о дорожке
нашего файла. Используя все те принципы, которые используются при загрузке
файлов (см.выше), несложно написать коротенькую подпрограмму, которая используя
функцию #06, будет сохранять состояние развития Вашей игры, конкретно в уже
зафиксированный файл. Считывать тоже.
При неоднократном запуске программы с такой адаптацией, если на запрос
"FILE ACCES?" Вы введете имя файла, который уже существует, то ТРДОС
проигнорирует строку 30 БЕЙСИКА, т.к. файл с таким именем уже существует.
Но при этом в системной области ТРДОС оставит 'слепок'- имя и тип файла, и
в основную программу, в конечном итоге, попадут координаты 'старого' файла, в
котором сохранено состояние от предыдущего раза.
Таким образом, "малой кровью" мы получим нужный результат. Правда, в таком
методе есть один недостаток - невозможность сохранения предыдущих данных,
так как новые данные будут постоянно затирать старые. Но эту неприятность можно
избежать простым копированием файлов предыдущих состояний на резервные диски и
их переименованием.
По такой, приблизительно, схеме адаптирована программа "ELITE" (адаптация
"JOYSTICK CLUB") с сохранением боевого рейтинга пилота и остальных данных
полета. Правда, там не используются ФУНКЦИИ ТРДОС, а программируется
непосредственно контроллер дисковода. Но, в общем и целом, принципы сохранения
состояния игры похожи.
* * *
Теперь, опираясь на все те знания, которые Вы уже почерпнули, Вы сможете
адаптировать практически ЛЮБУЮ программу с кассеты на диск.
Нетерпеливый читатель наверное воскликнет:
"А на кой черт вся остальная книга?".
Не торопитесь!!
Дело в том, что все самые интересные и сложные программы (а особенно, когда
программы активно работают с диском), адаптируются методами, позволяющими
непосредственно управлять дисковым накопителем, что гораздо экономичнее,
профессиональнее и удобнее. Правда, это намного сложнее, но!.. Как говорили
древние греки,-"Чем сложнее, тем проще!"
И вот тем,
кто не успокоился на достигнутом,
кто желает научиться:
- влезать в системную область диска, которую обычными методами не "достать";
- форматировать диски не только стандартным способом;
- каким образом прочитать диски других форматов (например от IBM PC);
- как востанавливаются сбойные диски
и многое-многое другое ...
вот им то и предназначена оставшаяся часть книги.
55555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555
TR-DOS для начинающих - 5-я глава книги "Общие сведения по дисковой системе ТР ДОС".
<b>TR-DOS для начинающих</b> - 5-я глава книги
TR DOS ДЛЯ НАЧИНАЮЩИХ
В наступившем году мы продолжаем публикацию книги В.Сироткина
"Общие сведения по дисковой системе ТР ДОС". Нужно сказать, что публикуемые
далее материалы представляют интерес не только для начинающих, но и для
более подготовленных пользователей и программистов. Поэтому название рубрики
уже не в полной мере отражает ее содержание.
Продолжение. Начало см. в ZX РЕВЮ 1996 NN 1-2, 4-5, 6, 7-8
ГЛАВА 5
ИНТЕГРАЛЬНАЯ МИКРОСХЕМА КР 1818 ВГ93.
--------------------------------------------
Прежде всего нам надо разобраться с самой главной микросхемой контроллера
дисковода ТРДОС (BETA DISK интерфейса): с микросхемой КР 1818 ВГ93.
Вся логика работы системы зависит,прежде всего от нее. Эта микросхема
представляет собой программный микроавтомат с внутренней логикой работы,
который обеспечивает управление дисководом, обмен данными между процессором и
дисководом, программирование номеров дорожки, сектора, стороны диска, длины
сектора и т.д. и т.п.
В общем, он обеспечивает все то ,что необходимо для нормальной работы с
дисководом. Обмен данными между микросхемой ВГ93 и процессором копьютера
происходит по 8-ми разрядной шине данных. По этой же шине происходит
программирование микросхемы на различные режимы и подача команд контроллеру.
Графическое изображение микросхемы показано ниже на рисунке 9.
РИС 9.
Назначение выводов микросхемы.
------------------------------
ВЫВОД ! МНЕМОН.! НАЗНАЧЕНИЕ ВЫВОДА
--------------------------------------------------
1 ! ВS ! Вывод микросхемы контроль подлож-
! ! ки; в схемах не подключается.
--------------------------------------------------
2 ! W ! Вход ЗАПИСЬ.При W=0 - Разрешение
! ! записи в выбранный регистр.
--------------------------------------------------
3 ! CS ! ВХОД. При 0 - микросхема выбрана
--------------------------------------------------
4 ! R ! Вход ЧТЕНИЕ.При = 0 - микросхема
! ! выдает нa Ш.Д. содержимое выбран-
! ! ного регистра .
--------------------------------------------------
5,6 ! А0,А1 !Вход Адреса выбора внутренних
! ! регистров ВГ93 для операций чтения
! ! /записи.
! !
! ! А0 А1 ЧТЕНИЕ ЗАПИСЬ
! ! -------------------------------
! ! 0 0 РЕГ.СОСТ. РЕГ.КОМАНД
! ! 1 0 РЕГ.ДОРОЖ. РЕГ.ДОРОЖ.
! ! 0 1 РЕГ.СЕКТОР. РЕГ.СЕКТР.
! ! 1 1 РЕГ.ДАННЫХ РЕГ.ДАННЫХ
--------------------------------------------------
7-14 ! D0-D7 ! Вход/Выход данных или команд
--------------------------------------------------
15 ! STEP ! Выход на дисковод импульса переме-
! ! щения головки на 1 шаг ( цилиндр)
--------------------------------------------------
16 ! DIRC ! Выход для дисковода. При =
! ! 0 - перемещение головки от ЦЕТРА
! ! к краю ;
! ! при = 1 - от края к центру
--------------------------------------------------
17 ! SL ! Выход для внешнего регистра .Сдвиг
! ! импульса данных с выхода WD ВЛЕВО.
--------------------------------------------------
18 ! SR ! Выход для внешнего регистра.Сдвиг
! ! импульса данных с выхода WD ВПРАВО.
--------------------------------------------------
19 ! CLR ! Вход СБРОСА. При =0 - микросхема
! ! выполняе команду ВОСТАНОВЛЕНИЕ.
! ! РЕГ.СЕКТОРА сбрасывается в #01.
! ! Выход 39 (INTRQ) = 0.
--------------------------------------------------
20 ! GND ! Корпусной вывод.
--------------------------------------------------
21 ! Ucc1 ! Питание + 5 вольт
--------------------------------------------------
22 ! TEST !Вход- Скорость перемещения головки.
! ! При =1
! !на выходе 'STEP' импульсы перемеще-
! ! ния идит с удвоенной частотой.
--------------------------------------------------
23 ! HRDY ! Вход ГОТОВНОСТЬ головки. При = 1
указание на готовность к работе.
--------------------------------------------------
24 ! CLC ! Тактовая частота
--------------------------------------------------
25 ! RSTB ! Выход СТРОБ ЧТЕНИЯ. Устанавлива-
! ! ется в 1 после приема 2х байтов
! ! нулей при одинарной плотности
! ! диска ; или
! ! 4х байтов нулей (единиц) при
! ! удвоенной плотности диска.
--------------------------------------------------
26 ! S ! Вход синхронизации. Сигнал выра-
! ! батывается из сигналов RAWR .
--------------------------------------------------
27 ! RAWR ! Вход ДАННЫХ от дисковода.
--------------------------------------------------
28 ! HLD ! Выход ГОТОВНОСТЬ для дисковода.
! ! (При =1 - готов)
--------------------------------------------------
29 ! TR43 ! Выход ПОЛОЖЕНИЕ головки.
! ! При = 1 указывает,что головка
! ! на цилиндре с номером большим 43.
! ! (Сигнал вырабатывается только в
! ! процессе ЗАПИСИ или ЧТЕНИЯ )
--------------------------------------------------
30 ! WSTB ! Выход. При = 1 включает дисковод
! ! на запись.
--------------------------------------------------
31 ! WD ! Выход данных для записи на диск.
--------------------------------------------------
32 ! CPRDY ! Вход Готовность дисковода для
! ! ЗАПИСИ или ЧТЕНИЯ.
! ! При = 0 - команды
! ! ЗАПИСЬ/СЧИТЫВАНИЕ НЕ ВЫПОЛНЯЮТСЯ,
! ! а вырабатывается СИГНАЛ INTRQ.
! ! (Не влияет на остальные вспомо-
! ! гательные команды).
--------------------------------------------------
33 !WF / DE ! ВХОД / ВЫХОД : ОШИБКА / ДАННЫЕ .
! ! При WSTB = 1 - WF - ВХОД;
! ! (если WF =0 то, запись любой ко-
! ! манды прекращается).
! ! При WSTB = 0 - DE - ВЫХОД;
! ! (на DE устанавливается 0 при
! ! ЧТЕНИИ после загрузки головки
! ! и HRDY = 1).
--------------------------------------------------
34 ! TR00 ! ВХОД - НОЛЬ ДОРОЖКА.
! ! При = 0,головки дисковода нахо-
! ! дятся на 0 дорожке.
--------------------------------------------------
35 ! JP ! Вход ИНДЕКСЫЙ ИМПУЛЬС.
! ! При = 0 - индексный импульс счи-
! ! тан и диск начал очередной оборот.
--------------------------------------------------
36 ! WPRT ! Вход ЗАПРЕТ ЗАПИСИ . (При = 0)
--------------------------------------------------
37 ! DDEN ! Вход ПЛОТНОСТЬ ДИСКА.
! ! (При = 1 - плотность ДВОЙНАЯ)
--------------------------------------------------
38 ! DRQ ! Выход СТРОБ ДАННЫХ.
! ! * При DRQ = 1 ;
! ! во время ЧТЕНИЯ - указание на то,
! ! что РЕГИСТР ДАННЫХ содержит инфор-
! ! мацию для передачи.
! ! Во время ЗАПИСИ - ГОТОВНОСТЬ реги-
! ! стра ВГ 93 принять данные от Ш.Д.
! ! * При DRQ=0 ;
! ! При ЧТЕНИИ - указание,что данные
! ! из регистра ВГ93 считаны процес-
! ! сором.
! ! При ЗАПИСИ указание ,что регистр
! ! ВГ93 принял данные с Ш.Д.
--------------------------------------------------
39 ! INTRQ ! Выход ГОТОВНОСТЬ микросхемы.
! ! При = 1 - микросхема готова
! ! принять команду ;
! ! при = 0 - микросхема ВЫПОЛНЯЕТ
! ! команду.
! ! ИЛИ был считан регистр СОСТОЯНИЯ.
--------------------------------------------------
40 ! Ucc 2 ! Напряжение питания +12 вольт.
--------------------------------------------------
Для нас с вами не потребуется досконального знания функционирования всех
входов-выходов микросхемы и режимов ее работы.
С точки зрения программиста, нам потребуется знание логики работы всего
лишь нескольких выводов, подключенных непосредственно в адресное пространство
портов контроллера ТР ДОС.
Микросхема ВГ93 имеет 5 внутренних регистров, доступных программисту,
через которые идет управление работой ВГ93, и еще один внешний регистр,
выполненный, как правило, на микросхеме 555ТМ9 (триггере-защелке), для
управления дисководом и для опроса готовности.
Все регистры подключены к определенным адресам в адресном пространстве
компьютера, как ПОРТЫ ввода/вывода, и, значит, обращатьсяк к ним надлежит
командами IN или OUT. Но простой подачей в эти порты каких-либо значений или
при попытке считать что-то из этих портов, Вы ничего не добьетесь, т.к. эти
порты во время работы SOS Бейсика недоступны!
В ПЗУ ТРДОС есть все необходимые команды обращения к этим портам, и
проблема заключается только в том, как выйти на ПЗУ ТРДОС. Вот об этом мы и
поговорим чуть позднее...
РАСПРЕДЕЛЕНИЕ ПОРТОВ ТРДОС - ВГ93.
----------------------------------
* РЕГИСТР ДОРОЖКИ - ПОРТ #3F (ЧТЕНИЕ/ЗАПИСЬ)
--------------------------------------------
Служит для ЗАПИСИ номера дорожки при операциях или для СЧИТЫВАНИЯ номера
текущей дорожки.
* РЕГИСТР СЕКТОРА - ПОРТ #5F (ЧТЕНИЕ/ЗАПИСЬ)
--------------------------------------------
Служит для ЗАПИСИ номера сектора при операциях или для СЧИТЫВАНИЯ номера
текущего сектора.
* РЕГИСТР ДАННЫХ - ПОРТ #7F (ЧТЕНИЕ/ЗАПИСЬ)
--------------------------------------------
Служит для обмена данными между диском и компьютером.
* РЕГИСТР КОМАНД - ПОРТ #1F ( ЗАПИСЬ )
--------------------------------------------
Служит для подачи команд контроллеру.
* РЕГИСТР СОСТОЯНИЯ - ПОРТ #1F ( СЧИТЫВАНИЕ )
--------------------------------------------
Служит для определения текущего состояния выполнения команд.
* РЕГИСТР УПРАВЛЕНИЯ - ПОРТ #FF
(СЧИТЫВАНИЕ/ЗАПИСЬ)
--------------------------------------------------
Служит для управления дисководом и для считывания готовности микросхемы ВГ93
- при работе или при обмене данными. Этот регистр выполнен на микросхеме
ТМ9 - триггере защелке. При подаче в порт байта, он сохраняется -
защелкивается.
Рассмотрим подробнее РЕГИСТР УПРАВЛЕНИЯ (#FF).
** При ЗАПИСИ в этот регистр можно выбрать номер дисковода, сбросить
микросхему ВГ93, выбрать сторону диска, дать готовность дисководу и выбрать
плотность записи.
Ниже на рисунке представлены биты этого регистра и за что они отвечают при
ЗАПИСИ в него.
Режим ЗАПИСИ в регистр УПРАВЛЕНИЯ (#FF).
┌────┬─────┬─────┬──────┬─────┬──────┬─────┬─────┐
│ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
│ │ │ │ │ │ │ │ │
└────┴─────┴─────┴──────┴─────┴──────┴─────┴─────┘
│ │ │ │ ├────────┴───
│ │ │ │ │выбор диска
│ │ │ │ │при = 0,0
│ │ │ │ └─диск 'А────
│ │ │ │
│ │ │ ├──────────────────
│ │ │ │сброс микросхемы
│ │ │ └───ВГ93 ; при=0───
│ │ ├──────────────────────────
│ │ │ готовность дисководу (=1)
│ │ └──────────────────────────
│ ├────────────────────────────────
│ │ сторона диска при= 1 - верх
│ │ при= 0 - низ
│ └────────────────────────────────
│
├────────────────────────────────────────────
│ плотность записи при=1 - одинарная
│ при=0 - двойная
└────────────────────────────────────────────
БИТЫ 0 и 1 - с выхода триггера ТМ9 напрямую связаны со входами дисковода "DISK
A,B,C"
БИТ 2 - это вход CLR микросхемы ВГ93
БИТ 3 - это вход HRDY микросхемы ВГ93 и одновременно разрешение
прохождения сигнала IP от дисковода к микросхеме ВГ93
БИТ 4 - напрямую связан со входом дисковода "SIDE"
BIT 6 - это вход DDEN микросхемы ВГ93
В Режиме ЧТЕНИЕ регистра #FF использованы всего 2 бита, (смотри ниже).
Режим ЧТЕНИЯ регистра УПРАВЛЕНИЯ (#FF).
┌────┬─────┬─────┬──────┬──────┬──────┬──────┬────┐
│ │ │ │ │ │ │ │ │
│ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
└────┴─────┴─────┴──────┴──────┴──────┴──────┴────┘
│ │ │═══════ НЕ ИСПОЛЬЗУЕТСЯ ═════════│
│ │
│ ┌─┴──────────┬────────────────────────────────
│ │ готовность │ при= 1 - готовность к обмену
│ │ ДАННЫХ: │ при= 0 - данные считаны/приняты
│ └────────────┴────────────────(не готовы )────
│
├─────────────────────────────────────────────────
│ готовность при=1 - команда ВЫПОЛНЕНА
│ МИКРОСХЕМЫ ВГ93: при=0 - команда на ИСПОЛНЕНИИ
│ или считан регистр
└─────────────────────────────────СОСТОЯНИЯ ВГ93──
БИТ 6 - это выход DRQ микросхемы ВГ93
БИТ 7 - это выход INTRQ микросхемы ВГ93
Микросхема ВГ93 обеспечивает прием и выполнение 11 команд, поданных в
Регистр КОМАНД. Каждая команда,из одиннадцати, имеет свой КОД-МОДИФИКАТОР ,
который изменяет некоторые условия выполнения команды.
Условно команды подразделяются на четыре типа:
1 - команды перемещения головок дисковода и поиска: эти команды загружают
(прижимают) головки.
2 - команды чтения/записи секторов диска.
3 - команды чтения/записи дорожки и адресной метки.
4 - команды принудительного прерывания операций.
Результаты выполнения или невыполнения команд отображаются в Регистре
СОСТОЯНИЯ, каждый бит которого несет информацию о ходе выполнения команды.
Разберем подробнее каждую команду.
Команды ПЕРВОГО ТИПА.
-------------------------
Данные команды обеспечивают загрузку головок, т.е. прижатие их к поверхности
диска.
* Команда ВОСТАНОВЛЕНИЕ.
............................
Переход головок дисковода на НУЛЕВОЙ ТРЕК (ЦИЛИНДР). Если на входе TR00
микросхемы не появится подтверждение о выходе головок на 0 ,то через 256
импульсов действие команды прекращается.
КОД команды.
┌─7──┬──6──┬──5──┬───4──┬───3──┬───2──┬───1──┬──0─┐
│ │ │ │ │ │ │ │ │
│ 0 │ 0 │ 0 │ 0 │ G │ V │ F1 │ F2 │
└────┴─────┴─────┴──────┼──────┴──────┴──────┴────┘
│ МОДИФИКАТОР
При:
G=0 - головка поднята.
G=1 - головка опущена (в работе).
V=0 - местонахождение головки не проверяется.
V=1 - читается и проверяется номер цилиндра под головкой.
F1,F2 - коды времени перемещения головок (см.таблицу).
* KОМАНДА ПОИСК ЦИЛИНДРА.
..........................
КОД команды.
┌─7──┬──6──┬──5──┬───4──┬───3──┬───2──┬──1──┬───0──┐
│ │ │ │ │ │ │ │ │
│ 0 │ 0 │ 0 │ 1 │ G │ V │ F1 │ F2 │
└────┴─────┴─────┴──────┼──────┴──────┴─────┴──────┘
│ МОДИФИКАТОР
При:
G=0 - головка поднята.
G=1 - головка опущена (в работе).
V=0 - местонахождение головки не проверяется.
V=1 - читается и проверяется номер цилиндра под головкой.
F1,F2 - коды времени перемещения головок (см.таблицу).
Перед командой Регистр ДОРОЖКИ должен содержать номер ТЕКУЩЕГО ЦИЛИНДРА,
а Регистр ДАННЫХ номер ТРЕБУЕМОГО. Поиск продолжается до тех пор, пока регистр
ДОРОЖКИ не сравняется с Регистром ДАННЫХ.
Поиск выполняется при модификаторе V=1 !!
* КОМАНДА ШАГ.
................
Перемещение головки на ОДИН шаг,(на 1 цилиндр). Направление перемещения
остается прежним.
КОД команды.
┌─7──┬──6──┬──5──┬───4──┼───3──┬───2──┬──1──┬───0──┐
│ │ │ │ │ │ │ │ │
│ 0 │ 0 │ 1 │ J │ G │ V │ F1 │ F2 │
└────┴─────┴─────┼──────┴──────┴──────┴─────┴──────┘
│ МОДИФИКАТОР
При:
G=0 - головка поднята.
G=1 - головка опущена (в работе).
V=0 - местонахождение головки не проверяется.
V=1 - читается и проверяется номер цилиндра под головкой
F1,F2 - коды времени перемещения головок (см.таблицу).
J=0 - содержимое Регистра ДОРОЖКИ не меняется.
J=1 - содержимое Р.ДОРОЖ. меняется на 1 (+ /-).
* КОМАНДА ШАГ ВПЕРЕД (к центру диска)
......................................
Перемещение головки на ОДИН шаг ВПЕРЕД (на 1 цилиндр).
КОД команды.
│
┌─7──┬──6──┬──5──┬───4──┼───3──┬───2──┬──1──┬───0──┐
│ │ │ │ │ │ │ │ │
│ 0 │ 1 │ 0 │ J │ G │ V │ F1 │ F2 │
└────┴─────┴─────┼──────┴──────┴──────┴─────┴──────┘
│ МОДИФИКАТОР
При:
G=0 - головка поднята.
G=1 - головка опущена (в работе).
V=0 - местонахождение головки не проверяется.
V=1 - читается и проверяется номер цилиндра под головкой.
F1,F2 - коды времени перемещения головок (см.таблицу).
J=0 - содержимое Регистра ДОРОЖКИ не меняется.
J=1 - содержимое Р.ДОРОЖ. увеличивается на 1.
* КОМАНДА ШАГ НАЗАД (к краю диска)
......................................
Перемещение головки на ОДИН шаг НАЗАД (на 1 цилиндр).
КОД команды.
┌─7──┬──6──┬──5──┬───4──┼───3──┬───2──┬──1──┬───0──┐
│ │ │ │ │ │ │ │ │
│ 0 │ 1 │ 1 │ J │ G │ V │ F1 │ F2 │
└────┴─────┴─────┼──────┴──────┴──────┴─────┴──────┘
│ МОДИФИКАТОР
При:
G=0 - головка поднята.
G=1 - головка опущена (в работе).
V=0 - местонахождение головки не проверяется.
V=1 - читается и проверяется номер цилиндра под головкой.
F1,F2 - коды времени перемещения головок (см.таблицу).
J=0 - содержимое Регистра ДОРОЖКИ не меняется.
J=1 - содержимое Р.ДОРОЖ. уменьшается на 1.
КОМАНДЫ ВТОРОГО ТИПА.
-----------------------------
* КОМАНДА ЧТЕНИЕ СЕКТОРА ,(секторов).
......................................
Команда выполняется после того, как микросхемой ВГ93 идентифицирована
область ИАМ сектора (индексная адресная метка) и подсчитана контрольная сумма
(все это происходит автоматически после подачи команды и не зависит от
программиста).
Перед командой необходимо:
1. Установить головку на нужный ЦИЛИНДР
2. в Регистр СЕКТОРА занести номер требуемого СЕКТОРА.
КОД команды.
┌─7──┬──6──┬──5──┬───4──┼───3──┬───2──┬───1──┬───0──┐
│ │ │ │ │ │ │ │ │
│ 1 │ 0 │ 0 │ М │ S │ E │ C │ 0 │
└────┴─────┴─────┼──────┴──────┴──────┴──────┴──────┘
│ МОДИФИКАТОР
При:
M=0 - чтение ОДНОГО сектора, содержимое Регистра СЕКТОРА увеличивается на 1.
М=1 - после чтения одного сектора, содержимое Регистра СЕКТОРА увеличивается
на 1, и начинается чтение следующего - и так до последнего сектора на
ДОРОЖКЕ.
S=0 - сторона диска НИЗ.
S=1 - сторона диска ВЕРХ.
Е=0 - нет задержки для установки головки дисковода.
Е=1 - задержка в 15 мс. для установки головки после сигнала HLD микросхемы
(готовность).
С=0 - сторона диска не проверяется.
С=1 - проверка стороны диска в процессе идентификации.
Если в процессе идентификации микросхема не находит в ИАМ-области НОМЕР
нужного СЕКТОРА, или же вообще не находит ИАМ, то в Регистре СОСТОЯНИЯ
записывается признак "МАССИВ ЧТЕНИЯ НЕ НАЙДЕН".
Если по окончании считывания сектора не совпали Контрольные суммы, то в
Регистр СОСТОЯНИЯ записывается признак ОШИБКА, и выполнение команды ОБРЫВАЕТСЯ.
При чтении массива данных из поля сектора в Регистр ДАННЫХ микросхемы,
КАЖДЫЙ байт сопровождается сигналом СТРОБ ДАННЫХ (вывод DRQ микросхемы;
6-й бит порта #FF, контроллера ТРДОС).
Если текущий байт не был считан из Регистра ДАННЫХ по сигналу СТРОБ ДАННЫХ
до прихода следующего байта, то в регистр ДАННЫХ записывается следующий
байт, а в Регистре СОСТОЯНИЯ записывается признак ПОТЕРЯ ДАННЫХ.
* КОМАНДА ЗАПИСЬ СЕКТОРА ,(секторов).
......................................
Команда выполняется после того, как микросхемой ВГ93 идентифицирована
область ИАМ сектора, (индексная адресная метка) и подсчитана контрольная
сумма (все это происходит автоматически после подачи команды и не зависит от
программиста).
Перед командой необходимо:
1. Установить головку на нужный ЦИЛИНДР.
2. в Регистр СЕКТОРА занести номер требуемого СЕКТОРА.
КОД команды.
┌─7──┬──6──┬──5──┬───4──┼───3──┬───2──┬───1──┬──0──┐
│ │ │ │ │ │ │ │ │
│ 1 │ 0 │ 1 │ М │ S │ E │ C │ А │
└────┴─────┴─────┼──────┴──────┴──────┴──────┴─────┘
│ МОДИФИКАТОР
При:
M=0 - запись ОДНОГО сектора, содержимое Регистра СЕКТОРА увеличивается на 1.
М=1 - после записи одного сектора,содержимое Регистра СЕКТОРА увеличивается
на 1 и начинается запись следующего, и так до последнего сектора на
ДОРОЖКЕ.
S=0 - сторона диска НИЗ.
S=1 - сторона диска ВЕРХ.
Е=0 - нет задержки для установки головки дисковода.
Е=1 - задержка в 15 мс. для установки головки после сигнала HLD микросхемы
(готовность).
С=0 - сторона диска не проверяется.
С=1 - проверка стороны диска в процессе идентификации.
А=0 - запись в область ИАМ признака - "сохранения данных (#FB) (без стирания)".
A=1 - запись в ИАМ признака - "стирать данные разрешено (#F8)".
Если в процессе идентификации микросхема не находит в ИАМ области НОМЕР
нужного СЕКТОРА, или же вообще не находит ИАМ, то в Регистре СОСТОЯНИЯ
записывается признак МАССИВ ЧТЕНИЯ НЕ НАЙДЕН.
После того, как ИАМ найдена, следует запрос данных - сигнал DRQ.
Микросхемой записываются (автоматически) нужные пробелы в секторе (12 нулей)
перед областью данных, затем записывается "Адресная метка данных" и только
затем сами данные.
Перед каждым запросом следующего байта данных микросхемой выставляется
Строб ДАННЫХ, (выход DRQ).
Если на запрос DRQ в регистр данных не будет помещен следующий байт для
записи, то в регистре СОСТОЯНИЯ выставится признак ПОТЕРЯ ДАННЫХ, а на
диск запишется байт 00 !
После записи всего массива данных микросхемой автоматически записывается
контрольная сумма в 2 байта и байт #FF.
KОМАНДЫ ТРЕТЬЕГО ТИПА.
----------------------
* КОМАНДА ЧТЕНИЯ АДРЕСНОЙ МЕТКИ .
...................................
Команда выполняется тогда, когда головка дисковода находится в рабочем
положении, т.е. прижата к диску (сигнал HLD=1). Считываются ШЕСТЬ байтов
ИНДЕКСНОЙ области диска, включая Контрольную сумму (Номер цилиндра, сторона
диска, тип сектора, номер сектора, 2 байта контр.суммы).
Все байты сопровождаются СТРОБОМ чтения БАЙТА (вывод DRQ или 6-й бит порта
#FF). При выполнении команды содержимое Регистра ДОРОЖКИ пересылается в
Регистр СЕКТОРА и запоминается.
По окончании выполнения команды генерируется сигнал INTRQ (7 бит порта
#FF).
КОД команды. │
┌─7──┬──6──┬──5──┬───4──┼───3──┬───2──┬───1──┬──0─┐
│ │ │ │ │ │ │ │ │
│ 1 │ 1 │ 0 │ 0 │ 0 │ E │ 0 │ 0 │
└────┴─────┴─────┴──────┴──────┴─┬────┴──────┴────┘
│
- МОДИФИКАТОР
При:
Е=0 - нет задержки для установки головки дисковода.
Е=1 - задержка в 15 мс. для установки головки после сигнала HLD микросхемы
(готовность).
* КОМАНДА: ЧТЕНИЕ ДОРОЖКИ ЦЕЛИКОМ.
.................................
По этой команде считывается ВСЯ информация с дорожки, вместе с индексным
массивом,пробелами и областью данных. Во время чтения НЕ выдается СТРОБ
чтения БАЙТА и не проверяются контрольные суммы. Применяется, в основном, эта
команда в диагностических целях, например, для юстировки головок дисковода или
в программах типа "ТРЕК КОПИР".
КОД команды.
┌─7──┬──6──┬──5──┬───4──┼───3──┬───2──┬───1──┬─0─┐
│ │ │ │ │ │ │ │ │
│ 1 │ 1 │ 1 │ 0 │ 0 │ E │ 0 │ 0│
└────┴─────┴─────┴──────┴──────┴─┬────┴──────┴───┘
│
- МОДИФИКАТОР
При:
Е=0 - нет задержки для установки головки дисковода.
Е=1 - задержка в 15 мс. для установки головки после сигнала HLD микросхемы
(готовность).
* КОМАНДА: ЗАПИСЬ ДОРОЖКИ ЦЕЛИКОМ.
..................................
Эта команда применяется для ФОРМАТИРОВАНИЯ диска. Запись информации
происходит за ОДИН РАЗ на целую дорожку. При форматировании вся необходимая
информация должна находиться в ОЗУ/ПЗУ и содержать все пробелы и индексные
метки.
КОД команды.
┌─7──┬──6──┬──5──┬───4──┼───3──┬───2──┬───1──┬───0─┐
│ │ │ │ │ │ │ │ │
│ 1 │ 1 │ 1 │ 1 │ 0 │ E │ 0 │ 0 │
└────┴─────┴─────┴──────┴──────┴─┬────┴──────┴─────┘
│
- МОДИФИКАТОР
При:
Е=0 - нет задержки для установки головки дисковода.
Е=1 - задержка в 15 мс. для установки головки после сигнала HLD микросхемы
(готовность).
При выполнении команды ЛЮБАЯ информация запишется на дорожку, кроме
некоторых байтов. А именно: появление байтов #F5...#FE интерпретируются как
начало СЛУЖЕБНЫХ МЕТОК. БАЙТ #F5 - запись кода #А1, инициализация подсчета
контрольной суммы.
БАЙТ #F6 - запись кода С2 - индексная метка.
БАЙТ #F7 - запись контрольной суммы.
БАЙТ #FB - адресная метка данных.
БАЙТ #FE - адресная метка ИНДЕКСНЫХ данных.
Все это справедливо для записи на диск ДВОЙНОЙ плотности (с
модифицированной частотной мадуляцией). Для одинарной плотности байты меток
будут немножко другими, но такие дисководы стали уже редкостью и рассматривать
одинарную плотность записи мы не будем.
В процессе форматирования эти байты меток не должны записываться в область
пробелов и в область для данных! Подробнее массив данных для форматирования
дисков мы рассмотрим в отдельной главе.
КОМАНДЫ ЧЕТВЕРТОГО ТИПА.
........................
* КОМАНДА "ПРИНУДИТЕЛЬНОЕ ПРЕРЫВАНИЕ".
Команда предназначена для завершения выполняемой операции и может быть
записана в Регистр КОМАНД в любой момент времени.
КОД команды.
┌─7──┬──6──┬──5──┬───4──┼───3──┬───2──┬───1──┬───0─┐
│ │ │ │ │ │ │ │ │
│ 1 │ 1 │ 0 │ 1 │ m3 │ m2 │ m1 │ m0 │
└────┴─────┴─────┴──────┼──────┴──────┴──────┴─────┘
│ МОДИФИКАТОР
При:
m3=m2=m1=m0 = 0 команда прекращается, но сигнал JNTRQ не вырабатывается
(порт #FF, 7 байт).
м0 = 1 прерывание происходит при переходе от 0 на 1 на входе CPRDY.
m1 = 1 прерывание происходит при переходе от 1 на 0 на входе CPRDY.
m2 = 1 прерывание по первому индексному сигналу (началу нового оборота диска).
м3 = 1 немедленное прерывание и установка на выходе JNTRQ сигнала ГОТОВНОСТЬ
(бит 7, порта #FF).
Время установки времени перемещения головки в командах ПЕРВОГО типа
зависит, прежде всего, от уровня на входе 'TEST' микросхемы ВГ93, тактовых
импульсов на входе CLK, а также от состояния модификаторов команд #F0,#F1.
__________________________________________________
TEST ! F1 ! F0 !Время перемещения на 1 шаг
! ! !при CLK=1 MГЦ ! при CLK=2
--------------------------------------------------
1 ! 0 ! 0 ! 6 ! 3
--------------------------------------------------
1 ! 0 ! 1 ! 12 ! 6
--------------------------------------------------
1 ! 1 ! 0 ! 20 ! 10
--------------------------------------------------
1 ! 1 ! 1 ! 30 ! 15
--------------------------------------------------
0 ! - ! - ! 400 ! 200
__________________________________________________
Любая команда в процессе своего исполнения обновляет регистр СОСТОЯНИЯ
микросхемы (порт #1F при чтении). С помощью этого регистра как раз и ведется
"диалог" между компьютером и контроллером о выполнении или невыполнении команд.
Ниже дана таблица значения битов этого регистра при выполнении различных
команд. А следом за ней помещена таблица, в которой показаны все команды ВГ93.
(C) Лебедев Павел, 1996
Недавно мне понадобилась как можно более подробная информация по
программированию КР1818ВГ93 и я полез в сборники ZX РЕВЮ... Вот тут-то все и
началось!
Сначала я хочу сказать о вызове подпрограмм TR-DOS. Время от времени в
различных книгах и программах вместо
PUSH HL
JP #3D2F
встречается
PUSH HL
JP #3D30
Хочу предостеречь людей от таких "улучшений". Несмотря на то, что по
адресу #3D2F в TR-DOS находится команда NOP, а RET - в следующей ячейке,
передавать управление необходимо именно на адрес #3D2F. Дело в том, что в
некоторых компьютерах ПЗУ не успевает переключаться достаточно быстро, и
процессор как бы "проскакивает" команду RET. Последствия этого могут быть
самыми печальными.
Теперь несколько слов об управлении КР181ВГ93. Практически во всех
изданиях, посвященных этому вопросу, сказано, что напрямую прочитать регистр
состояния из порта #1F невозможно. Каково же было мое удивление, когда
поковырявшись в своей TR-DOS 5.03 по адресу #09BF я обнаружил следующие
команды:
09BF IN A,(#1F)
09A1 RET
Возможно, это имеет место только в TR-DOS 5.03.?
В связи с этим, а также в целях создания более качественного
программного обеспечения для работы с КР1818ВГ93 программистами СНГ, предлагаю
открыть раздел, в который все желающие могли бы прислать адреса необходимых
подпрограмм для своей версии ПЗУ, чтобы их версия TR-DOS поддерживались новыми
программами.
Предлагаю точки входа для TR-DOS 5.03:
1. Вывод в порт BC регистра A:
2A53 OUT (C),A
2A55 RET
2. Вывод в порт #1F (регистр команд) регистра A:
2FC3 OUT (#1F),A
2FC5 RET
3. Вывод в порт #FF регистра A:
1FF3 OUT (#FF),A
1FF5 RET
4. Чтение регистра состояния (порт #1F) в регистр A:
09BF IN A,(#1F)
09A1 RET
(snd: в версии 5.04 пункты 1,2,3 совпадают...4 - в пзу 5.04 отсутствует!)
И наконец - хочу привести усовершенствованную версию Advanced Drive
FX, опубликованого в ZX РЕВЮ 95/5. Новая версия несколько более понятна,
меньше по объему, позволяет оперировать со всеми дисководами от A до D и
изменять скорость зажигания и затухания светодиода. Для зажигания необходимо
вызвать процедуру F_IN, а для затухания - F_OUT. Подпрограммы работы с портами
настроены на TR-DOS 5.03. Для работы с другими версиями надо изменить адреса в
строках 390-410. В строке 420 регулируется скорость эффекта, а в строке 430
задается дисковод.
10 ORG 50000
20 F_IN LD E,1 ;зажигание производится при изменении
;переменной на +1
30 JR DO_FX ;переход на процедуру
40 F_OUT LD E,255 ;затухание производится при изменении
;переменной на -1
50 DO_FX XOR A ;подготовка аккумулятора
60 LOOP1 ADD A,E ;изменяем переменную
70 RET Z ;выход, если светодиод зажегся или потух
;полностью
80 LD B,SPEED ;взяли скорость загорания (затухания)
90 LOOP2 PUSH BC ;сохранили счетчик
100 LD D,A ;сохранили указатель загорания (затухания)
110 LD HL,OUT_FF ;адрес подпрограммы вывода регистра A в
;системный регистр (порт #FF)
120 LD A,#3C ;в аккумуляторе необходимое число, которое
;указывает на номер дисковода: биты 0 и 1.
130 CALL DOS ;заносим число в порт
140 LD HL,OUT_1F ;выдаем команду контроллеру "Чтение
;адреса"
150 LD A,#D0
160 CALL DOS
170 WAIT LD HL,IN_1F ;чтение регистра состояния
180 CALL DOS
190 XOR #80 ;проверка готовности дисковода
200 JR NZ,WAIT ;если не готов, то ждать
210 LD A,8 ;выдаем команду "Восстановление"
220 LD HL,OUT_1F
230 CALL DOS
240 LD B,D ;задержка
250 DJNZ $
260 LD HL,OUT_FF ;выбираем нужный дисковод
270 LD A,DRVNUM
280 CALL DOS
290 LD A,D ;восстановили указатель
300 NEG ;изменили знак
310 LD B,A ;задержка
320 DJNZ $
330 LD A,D ;восстановили указатель
340 POP BC ;восстановили счетчик
350 DJNZ LOOP2 ;повтор, пока счетчик не обнулился
360 JR LOOP1 ;переход на начало процедуры
370 DOS PUSH HL ;процедура вызова подпрограмм из ПЗУ
;TR-DOS
380 JP #3D2F
390 IN_1F EQU #09BF ;адрес подпрограммы чтения системного
;регистра
;необходимо изменить под конкретный тип
;ПЗУ
400 OUT_1F EQU #2FC3 ;выдача команды для контроллера
410 OUT_FF EQU #1FF3 ;запись системного регистра
420 SPEED EQU #08 ;скорость эффектов
430 DRVNUM EQU #00 ;номер дисковода
Не забудьте, что при использовании других версий TR-DOS надо изменить
адрес в строке 390. Для того, чтобы узнать, есть ли у Вас в ПЗУ такая
подпрограмма, необходимо сначала выгрузить ПЗУ TR-DOS на диск.
ИФК: Павел Ковалевский (Smith CODER) из г. Нижевартовска прислал программу,
которая позволяет считывать данные из системных портов DOS.
КОРР: Во всех изданиях о TR-DOS я находил информацию только о записи данных
в системные порты DOS, а о чтении их либо не говорилось, либо утверждалось, что
сделать это практически невозможно. В принципе, это все правильно, но все же
вытащить данные из TR-DOS можно и довольно просто. Да, действительно, в DOS
нет прямой команды IN A,(C) и RET, и считать порт просто не получится. Но
можно обмануть процессор, используя IM2, который генерирует прерывания 50 раз в
секунду.
Для этого ждем когда до генерации прерывания остается несколько тактов и
передаем управление DOS в любую точку, где есть IN H,(C) или подобная команда.
После того, как процессор выполнит команду IN H,(C), он переходит на следующую,
вот тут-то происходит прерывание, и, в итоге, у Вас в регистре H данные из порта
#3F.
ORG 25000
LD IX,16115 ; АДРЕС КОМАНДЫ IN H,(C)
LD BC,#3F ; СИСТЕМНЫЙ РЕГИСТР
CALL DOS
RET
DOS LD (COC+1),SP ; ЗАПОМИНАЕМ СТЕК
PUSH IX ;┐
PUSH HL ;│ЗАПОМИНАЕМ ИЗМЕНЯЕМЫЕ РЕГИСТРЫ
PUSH DE ;│
PUSH AF ;┘
LD A,201
LD (65535),A
LD A,59 ; УСТАНАВЛИВАЕМ РЕЖИМ IM2 ДЛЯ ПРАВИЛЬНОГО ОТСЧЕТА
LD I,A
IM 2
EI
HALT ; НАЧАТЬ ОТСЧЕТ
DI
LD HL,MET
LD (60927),HL ; 237*256+255
LD A,237 ; УСТАНАВЛИВАЕМ АДРЕС ДЛЯ ВОЗВРАТА В MET
LD I,A
IM 2
EI
XOX LD DE,2751 ; СЧЕТЧИК
AS DEC DE
LD A,D
OR E
JR NZ,AS
POP AF ;┐
POP DE ;│ВОССТАНАВЛИВАЕМ РЕГИСТРЫ КРОМЕ IX
POP HL ;┘
JP 15664 ; ВЫХОД В DOS
MET DI ; ВОЗВРАТ. В РЕГИСТРЕ H ДАННЫЕ СИСТЕМНОГО РЕГИСТРА #3F
COC LD SP,0
IM 1
EI
RET
IM1 & 2
from Fyrex
Пункт 1 -> Проблема вектора прерываний
Всем известно, что для эффективного создания любой программы
нужно использовать прерывание IM2, но иногда используют не
совсем верный способ установки этих прерываний. Заранее прошу
извинения за повторение прописных истин, но для некоторых эти
истины не до конца прописаны. Так вот, в классическом виде
установка прерываний IM2 происходит следующим путём:
а) создание таблицы прерываний;
б) установка указателя на подпрограмму прерывания;
в) запрещение текущих прерываний;
г) установка нового вектора таблицы прерываний;
д) переход в режим IM2;
е) разрешение уже вновь установленных прерываний IM2.
В этом месте, конечно, многие скажут: "Да, все верно, так это и
происходит" - но некоторые "экономы" сразу заметят то, что они
пропускают один или несколько из вышеперечисленных шагов. Сразу
проясню, что же происходит, если не совсем корректно установить
прерывания IM2. Все достаточно просто: в момент возникновения
самого прерывания, переход программы происходит не совсем в ту
подпрограмму, которая задумана, и даже вовсе в случайное место
памяти. Главный нюанс всей проблемы в том, что это происходит
далеко не на всех компьютерах и не всегда одинаково. А суть
этого кроется в не совсем отлаженной плате компьютера, как
правило, самопального. Решение же этой проблемы заключается в
правильном способе установки в своей программе прерывания IM2, и
тогда на любом, самом трухлявом Пентагоне, все будет нормально
работать. Приведу для примера кусок программы, правильно
ставящей IM2, и если кто-то не хочет углубляться далее в эту
проблему, просто скопируйте этот участок себе в код.
LD HL,#BE00
LD DE,#BE01
LD BC,#1BF
LD A,H
LD (HL),C
LDIR
LD (HL),#C3
LD HL,MY_INT_ROUTINE
LD (#BFC0),HL
DI
LD I,A
IM 2
EI
....
Здесь мы задействуем под прерывание IM2 области памяти:
#BE00..#BF01 и #BFBF..#BFC1.
По сути, этот участок и есть классическая установка IM2. Чаще
всего некоторым кодерам не хочется жертвовать 257-ю байтами для
полной таблицы прерываний, и они устанавливают указатель таблицы
в различные места - кто куда придумает, в основном им полюбилась
область ПЗУ, из-за этого обычно и происходят фатальные зависы их
программ у некоторых юзеров.
Вот вам первый постулат:
* Нежелательно устанавливать вектор прерываний вне области
памяти #8000..#BF00. В области памяти #4000..#7f00 или
#C000..#FF00 не стоит ставить вектор таблицы, поскольку на
многих компьютерах с медленной памятью по этим адресам как раз и
располагается эта самая медленная память. Фатальных глюков не
происходит, но сама программа может работать заторможено. А в
области памяти #0000..#3F00 вообще лучше никогда не ставить
вектор таблицы, поскольку там невозможно создать нормальную
таблицу прерываний.
А вот второй постулат:
* При создании самой таблицы прерываний корректнее всего
заполнить всю таблицу (257 байт) одним и тем же значением.
Это, пожалуй, самое злосчастное место, ведь используя таблицу из
ПЗУ (т.е. заполненную мусором), или не заполняя её в ОЗУ, вы тем
самым допускаете вероятность перехода процессора в ложное место
памяти, ведь никто со 100% вероятностью вам не гарантировал, что
переход происходит только по 2 последним байтам (включая
Клайва). Т.е. я не говорю, что подобное явление носит массовый
характер, но, судя по нашему городу, могу утверждать, что с этим
часто сталкивались многие. Когда спек был больше распостранён,
да и программ делали больше, это явление встречалось часто, т.е.
то у кого-то одно не работает, то у другого другое. Сейчас могу
точно сказать, что у одного из четырех спектрумистов, знакомых
мне (включая меня :), не работает ни одна из программ, с
хреновой таблицей прерываний. У меня, по странному капризу моего
Пентагона, подобные программы могут проработать довольно долго,
но кончают все одинаково - сбросом через некоторое время.
Напоследок приведу свой способ установки прерываний IM2, который
мне кажется удобней:
Где-то в начале программы:
ORG #8000
DEFS 257,#81 ; в ALASMe заполняет 257 байт кодом #81
....
ORG #8181
DI
; INTERRUPT ROUTINE
EI
RET
....
ENTRY:
....
DI
LD A,#80
LD I,A
IM 2
EI
....
Таким образом таблица и сопутствующие действия происходят на
этапе трансляции программы, что избавляет вас от лишних
действий, к тому же вы как бы включаете таблицу в размер
программы, тем самым заранее соглашаетесь с лишними занятыми
257-ю байтами, которые на самом деле никогда не были решающими.
Кстати, нетрудно заметить, что по адресу #8101 получаются 128
свободных байт, которые можно использовать как ячейки для
переменных. Необходимо добавить, что если в конце вашей
программы нужно передать управление системе, то лучше сделать
это следующим способом:
DI
LD A,#3F
LD I,A
IM 1
RET (оооох ! чуть не написал rts ;-)
Почему это стоит делать именно таким образом, читайте далее.
Пункт 2 -> Вектор прерываний и TR-DOS.
Создав правильную таблицу прерываний IM2 согласно советам
предыдущего пункта, вы ещё не до конца освобождаетесь от
потенциальных проблем с прерываниями. Если вы захотите вдруг в
середине программы, где уже работает ваше прерывание IM2,
загрузить что-нибудь с диска, то обычно используете для этого
подпрограммы TR-DOS. Мы сейчас не рассматриваем загрузчики с
включенными прерываниями IM2, это уже рассматривалось в
различных статьях. Следующий комментарий в равной степени будет
относиться и к стандартной работе с TR-DOS через #3D13, и к
часто используемым быстрым загрузчикам (через прямой вызов
низкоуровневых подпрограмм работы с ВГ93). Перед работой с
TR-DOS многие отключают музыку и прерывания посредством команды
DI. Как показывает грустная практика, этого недостаточно,
особенно при использовании низкоуровневых подпрограмм TR-DOS.
Другими словами, перед вызовом любой подпрограммы TR-DOS лучше
перейти в режим прерывания IM1 c вектором #3F:
DI
LD A,#3F
LD I,A
IM 1
CALL TR-DOS
Эта проблема очень похожа на описанную в предыдущем пункте, но
здесь уже происходит конфликт между ВГ93 и процом. Особо не
углубляясь в детали, советую использовать приведенную
конструкцию для доступа в TR-DOS. При использовании быстрых
загрузчиков с использованием низкоуровневых вызовов TR-DOS
хватит лишь добавления IM1/#3F в начало цикла счета секторов для
загрузки или записи.
Пункт 3 -> Опрос клавиатуры.
Предполагаю, что к этому пункту вряд ли прислушаются, но не могу
не поведать о такой проблеме, как опрос клавиатуры. Я думаю
многие не понаслышке знакомы с таким понятием, как дребезг
клавиатуры. Я с ним вплотную познакомился именно на Пентагоне.
Другими словами, при не очень удачном коде опроса клавиатуры
хрен чего наберешь потом. Сразу могу однозначно сказать, что
самый лучший способ опросить клавиатуру для набора какого-либо
текста... это использовать подпрограмму в ПЗУ Бейсика 48. Многие
могут мне возразить, что, мол, нам не пристало пользоваться
подпрограммами ПЗУ, мы, мол, сами все можем написать. Но опять
же грустная практика показала, что самая удачная подпрограмма
опроса именно для набора текста - это ПЗУшная подпрограмма из
обработки прерывания IM1. Можно ее, конечно, просто скопировать
оттуда, но не проще ли сделать RST#38?! Я использую ее следующем
образом :
XOR A
LD (23560),A
LD IY,#5C3A
RST #38
Этот участок располагаю внутри моего прерывания IM2. Такой
способ конечно не отличается большой скоростью выполнения,
однако он очень короткий и главное - довольно безглючный.
Последнюю нажатую клавишу затем можно получить в любом месте
программы из ячейки по адресу 23560. Дополнительную информацию
про такой опрос клавиатуры можно получить из различных
справочных книг по спеку. Всю эту, на первый взгляд, ересь для
продвинутого кодера я рассказываю не случайно. Дело в том, что
проблема всплывает в различных случаях, когда самые корректные с
виду подпрограммы опроса клавиатуры начинают барахлить. Это
имеет место по различным причинам, в частности:
а) при использовании конроллера пц-клавы;
б) при изменении каких-то внутренних характеристик компа (обычно
Пентагона, будь он неладен) - как правило при старение железа;
в) турбирование проца.
Опять же отмечу, что это происходит редко, однако эти
прискорбные случае были много раз зафиксированы мною в нашем
регионе. Ну и, как вы уже догадались, единственная программа,
которая выдержала проверку по глючности, - это именно
подпрограмма опроса в ПЗУ Бейсика 48. Честно скажу, так и не
понял, что в ней магического, может, потому, что она именно в
ПЗУ расположена, а может, и по нескольким особенностям сразу,
только она практически никогда не глючит. Если вы просто хотите
опросить несколько клавиш на предмет нажатости, то хороший
способ - воспользоваться командой IN A,(C), а не IN A,(254).
Другими словами, чтение из порта клавиатуры с помощью пары BC
происходит надежнее, видимо, вследствие технологической
особенности Z80.
Все эти рекомендации многим могут показаться странными, но все
это проверено на собственном опыте. Практическое применение этих
методик вы можете найти в нашей последней игре FARSPACE (для
CC01), если залезете туда STS-ом. :)
by Fyrex/Mayhem (c) Buzz 2002
Прерывания from XLD
Что это такое и зачем нужно? Недавно
XL_DESIGN предлагалась игра, музыка в ко-
торой подтормаживала при действиях. Когда
автору деликатно указали на данную проб-
лемму, он решил её просто - убрал музы-
ку...
Итак, прерывание это внешний сигнал, по-
даваeмый на процессор 50 раз в секунду
(для маскирoванных прерываний). Это про-
исходит в те моменты, когда видеоконтрол-
лер начинает рисовать очередной кадр
изображения(как раз 50 раз в секунду).
Для программиста это означает, что с на-
чалoм каждого кадра процессору предлага-
ется отвлечься от выполнения основной
программы и перейти в программу обработки
прерывания. Если прерывания разрешены -
он так и сделает, если нет - "прeдлoжe-
ние" проигнорируется. Прерывания делятся
на 2 типа: "маскируемые" (те, что могут
быть разрешены или запрещены) и "hemacku-
руемые" - никогда не игнорируются. В
Speccy NMI (non masked interrupt) исполь-
зуется для обслуживания кнопки MAGIC.
Как происходит прерывание? По окончании
текущей команды процесор проверяет - есть
ли сигнал прерывания и если есть, то как
реагировать (см. выше). Переход в прог-
рамму обработки осуществляется по принци-
пу CALL. T.e. процессор запоминает теку-
щее значение PC в стеке и переходит по
установленному для данного прерывания ад-
ресу. Адрес зависит от режима обработки.
Режимов (interrupt mask) есть несколько:
IM 0 - мы даже не будем o нём говорить,
так как для этого режима нужен контроллер
прерываний, которого нет ни в одном
спектруме, а без контроллера он полностью
эквивалентен IM 1 (на стабильной машине).
IM 1 - обрабатывается по адресу 56 (#38)
IM 2 - режим позволяет задавать свой ад-
рес для процедуры обработки.
NMI (немаскируемое прерывание) - oбраба-
тывается процедурой по адресу 102 (#66).
При нажатии кнопки magic происходит пе-
реключение ПЗУ на ПЗУ TR-Dos'а (или в те-
heboe ПЗУ), где и сидит программка его
обработки. Кстати, её тоже можно заменить
на свою, но уже путем nepenporpammupoba-
ния ПЗУ.
Теперь поподробней. Как только вы вклю-
чите компьютер и перед вами появится зас-
тавка, будет включен режим IM 1, который
используется, в основном, для опроса кла-
виатуры. Этот режим для нас особой цен-
ности не представляет, т.к. обрабатывает-
ся только процедурами ПЗУ. Поэтому будем
говорить про режим IM 2. Для управления
режимами обработки прерываний существуют
следующие команды:
IM 0, IM 1, IM 2
DI (Disable interrupt) - для запрещения
маскирoванных прерываний.
EI (Enable interupt) - для разрешения
маскирoванных прерываний.
При запрещенных прерываниях процессор на
них не будет реагировать. Heмаскирoванныe
прерывания не запрещаются.
Для того, чтобы отследить статус преры-
вания, можно использовать следующую про-
цедуру:
LD A,I ; читаем статус прерывания
; во флаг четности/переполнения
JP PE,EI_INT ; переход, если прерывания включены
DI_INT ... ; прерывания выключены
EI_INT ...
Теперь мы знаем как включить прерывания
и можем обсудить способ установки своего
адреса для обработчика прерываний. (в ре-
жиме IM 2)
В процессоре есть специальный регистр
для этого, это регистр I - вектор преры-
ваний. В нем указывается старший байт ад-
реса, содержащего адрес обработчика.
Младший байт читается с шины данных и на
нормальной машине всегда равен 255 или
#FF. Ho из-за того, что ненормальных ма-
шин довольно много (глюкoвыe пентагоны)
приходится делать поправку на них. Глюч-
ность их в том, что при обработке преры-
вания у них с шины может читаться чёрт
знает что. В связи с этим приходится
строить таблицу длинной 256 байт.
Далее мы приведём пример "пoстрoитeля"
такой таблички. После построения он будет
занимать 512 байт после адреса TABLE.
Прерывания будут разрешены, то есть обра-
ботчик к моменту построения должен уже
находиться в нужном месте памяти.
TABLE EQU #FCOO ; адрес таблицы переходов размером 257 байт,
; причем младший байт адреса должен быть равен нулю.
; адрес #FCOO самый верхний, возможный для этой процедуры.
INT EQU #8000 ; адрес обработчика прерывания, здесь ставь-
; те нужное Вам значение.
IM2_MAKE
DI
LD HL,TABLE
LD A,H
INC A
MAKE LD (HL),A
INC L
JR NZ,MAKE
INC H
LD (HL),A
DEC A
LD I,A
IM 2
LD HL,TABLE
INC H
LD L,H
LD (HL),#C3
INC HL
LD DE,INT
LD (HL),E
INC HL
LD (HL),D
EI
RET
Обработчик прерываний следует делать так:
INT
DI
PUSH AF,BC,DE,HL,IX,IY
EXX
EX AF,AF
PUSH AF,BC,DE,HL
...
собственные процедуры
...
POP HL,DE,BC,AF
EX AF,AF
EXX
POP IY,IX,HL,DE,BC,AF
EI
RET
Запрещать прерывания желательно, так как
есть плохо настроенные спектрумы, у кото-
рых прерывание может успеть прийти еще
раз до выхода из обработчика.
He следует также забывать, что прерыва-
ние не резиновое, в среднем это 64-68 ты-
сяч тактов. Если ваши процедуры не успе-
вают завершиться за это время, то выпол-
нение программы замедлится в два раза
(вместе с прерываниями). Так как самая
тормозная машина у нас Скорпион и этих
скорпионов очень много, надо избегать та-
ких ситуаций и придерживаться барьера в
65 тысяч тактов. Вообще данная тема под-
рoбнo освещалась в ZF-3.
В основном, прерывание используется для
проигрывания музыки на AY, рисования кур-
сора и подсчета времени. Для музыки надо
только не запрещать прерываний в програм-
ме и она никогда не будет притормаживать.
Практически все музыкальные редакторы
компилируют музыку вместе с плеером, его,
конечно, можно и не присоединять, но это
вам пока не понадобится. Плеер работает
везде одинаково. Сначала его надо oтынс-
таллирoвать, а потом проигрывать с часто-
той 50 Гц, то есть один раз в прерывание.
C помощью своего обработчика это делается
очень просто. В прерывании ставится вызов
плеера и все! Например так:
CALL INSTALL
...
INT
DI
PUSH AF,BC,DE,HL,IX,IY
EXX
EX AF,AF
PUSH AF,BC,DE,HL
CALL PLAY
POP HL,DE,BC,AF
EX AF,AF
EXX
POP IY,IX,HL,DE,BC,AF
EI
RET
Адреса INSTALL и PLAY вам сообщит музы-
кальный редактор при компиляции музыки.
Ну вот, вроде про маскированные прерыва-
ния все. Теперь пару слов o hemackupobah-
ных прерываниях. Они имеют больший npuo-
putet, чем маскируемые, то есть процессор
считает их более важными. C помощью прог-
pammatopa вы сможете сами задать адрес
обработчика NMI (немаскируемого прерыва-
ния) путем замены кода в адресе 102 или
#66 в ПЗУ Tr-dos. Например, так сделан
теневой сервис-монитор на Scorpion'e. He-
которые умельцы прошивают в ПЗУ STS, это
будет получше скорпионовского сервис-мо-
hutopa.
from codersbucket
Interrupts on the ZX Spectrum
What are interrupts?
Interrupts, as the name suggests, are when the
normal running of a computer program are interrupted
so something else can happen. On modern computers there can be
various interrupts, but on the ZX Spectrum
there were very few, most times you will hear of only 3
different interrupt modes 0, 1 and 2. While the main
focus of this document is interrupt mode 2, we will quickly
cover modes 0 and 1.
These three interrupts are all maskable, that is to say we
can stop the effect of them by using the DI (Disable inter
rupts) instruction. If we want them to start happening ag
ain we use the EI (Enable interrupts) instruction. For
completeness I will point out that when we use the DI
instruction, the interrupts still trigger,
that is the Z80 is still aware of them, it just ignores
them and carries on doing what it was doing anyway.
Interrupt Mode 0
This interrupt mode is not really used.
The reason for this is that it is triggered by external hardware
. The devices must place instructions (such as RST or CALL)
onto the data bus when it sets the interrupt request on t
he Z80. It is this interrupt mode however that is
running when the spectrum first powered up, luckily
the Spectrum ROM soon setups up Interrupt Mode 1.
Interrupt Mode 1
This interrupt is very similar to IM2 (Interrupt Mode
2) in that it is triggered by the vertical blanki
ng of the screen refresh that happens roughly 50 ti
mes a second. The vertical blank is the time when
the beam that draws the screen has reached the bott
om and is turned off while it moves back to the top,
ready to draw the next frame. Mode 1 is the
standard interrupt used by the Spectrum while running
basic and, unless you alter it, while your programs
are running.
When the vertical blank occurs the current conte
nts of the program counter (PC) are pushed onto
the stack (SP). The address $0038 is then loade
d into PC and the Spectrum will start running
the program stored at this location. As you
may notice by the address, this is in the
ROM and as such we have no way of altering what this does.
The ROM routine mainly scans the keyboard and stores details
of what is being pressed.
Interrupt Mode 2
As mentioned IM2 is very similar to IM1 above e
xcept it is known as a vectored interrupt. Like
IM1 it is triggered roughly 50 times a second on
the vertical blank, it pushes the current PC onto
the stack but then rather than jumping to a specific
address it uses a vector table to find out what
value to load into PC. There is a lot of confusion
over the exact operation of this vector table, and
as different emulators deal with it in different
ways, it is best to go with the method that works for all.
The vector table is simple a list of memory addres
ses, 128 of them in total. The table sits on a 25
6 byte boundary and the high byte of its address i
s loaded into the special I register on the Z80.
The confusion seems to occur with what fills in the
bottom 8 bits. The value comes from the BUS and
general feeling is (and as such the safest rule to
follow) that any value can occur. Some documenta
tion such as ZAKS (Programming the Z80 book) will
say that only the higher 7 bits will be used from
the BUS, the least significant bit will always be
0. While to me this seems the most logical given
the vector table, as mentioned not all emulators f
ollow this rule and as such I would strongly advise
against assuming this bottom bit is 0. Why does
it make a difference you may ask? To answer that
lets look at what happens when the interrupt is triggered.
When the interrupt is triggered, and they are enabled,
the contents of the PC are pushed onto the stack.
The contents of the I register are loaded into t
he upper 8 bits of the address bus, the BUS suppl
ies the lower 8 bits, as we are not using any devi
ces these bits can be any value. . Given this
address ((I * 256) + BUS bits) a two byte address
is read and loaded into the PC. The Z80 will then
run the code stored at this address as per normal.
First lets assume that the I register is set to
$40 and that the bottom bit is always 0. When
the interrupt is triggered the memory address
will we calculated as $40xx where xx is any eve
n 8 but value (0, 2, 4… 254). We can simply st
ore the address of our interrupt routine at all
128 locations $4000, $4002, $4004 …… $40fe.
This way when the interrupt occurs the same r
outine address will be loaded into the program
counter no matter what the BUS value is.
The code to set this up would be something like
ORG $8000
; Setup the 128 entry vector table
di
ld hl, VectorTable
ld de, IM2Routine
ld b, 128
; Setup the I register (the high byte of the table)
ld a, h
ld i, a
; Loop to set all 128 entries in the table
_Setup:
ld (hl), e
inc hl
ld (hl), d
inc hl
djnz _Setup
; Setup IM2 mode
im 2
ei
ret
; Basically nothing
IM2Routine:
ei
reti
; Make sure this is on a 256 byte boundary
ORG $F000
VectorTable:
defs 256
Hopefully the code shows that the vector table stores
the 128 entries pointing to the routine IM2Routine.
When the interrupt is triggered the byte address at
(I * 256) + Bus will be read along with the following byte
to form the address. The routine at this address will
be called, in our example no matter what the Bus is it
will always call IM2Routine. But what happens if we
don’t assume bit 0 will always be 0?
In our example above IM2Routine will have the
address $8016, so the first eight bytes of our vector
table will be
defb $16, $80, $16, $80, $16, $80, $16, $80
The rest of the table will follow the same pattern.
When bit 0 was always 0 the low byte would always
be pulled out as $16 and the following high byte o
f the address would be $80 (assuming Bus was 0 the
low byte would be pulled out from the start of the
table and the high byte would be the next byte), th
e next option would be Bus was 2 which would give
the same result just reading from 2 bytes into the
table. If however bit 0 was not always 0 we cou
ld end up reading starting from byte 1 in the tab
le giving our low byte of $80 and our high byte of $16.
This gives us the address $1680 which certainly isn’t
where we want the Z80 to jump to.
Given the reasons behind how the vectored interrupt
is designed I would speculate that the hardware doe
s indeed always reset bit 0 to 0 however as mention
ed not all emulators follow this rule (The hardware
ive tested this on does mind you). To be safe it
s best to work on the theory it can do a read from
any memory address. So, how do you deal with that?
Simply make sure all the bytes in the table are
the same value.
One other thing to note with the issue of readin
g from any byte is what happens if the Bus value
is 255 when the interrupt triggers. This will
be used as the low end byte of the address on the
vector table, but as we need to read two bytes it
will read the last entry in the 256 byte table
as well as the byte following it. To allow for this we
must use a 257 byte table when allowing for any Bus value.
So with that in mind lets take a look at the code
to setup the table.
ORG $8000
; Setup the 128 entry vector table
di
ld hl, IM2Table
ld de, IM2Table+1
ld bc, 256
; Setup the I register (the high byte of the table)
ld a, h
ld i, a
; Set the first entries in the table to $FC
ld a, $FC
ld (hl), a
; Copy to all the remaining 256 bytes
ldir
; Setup IM2 mode
im 2
ei
ret
; This routine now needs to be at a specific address
(remember we only have from $FCFC to $FE00 else we
will overwrite our table)
ORG $FCFC
; Basically nothing
IM2Routine:
ei
reti
; Make sure this is on a 256 byte boundary
ORG $FE00
IM2Table:
defs 257
Why do we need an interrupt routine?
Having looked at how we setup an interrupt routine,
you may find yourself asking why you need one.
Well there are a few good reasons to set one up,
firstly why let the ROM waste good cpu cycles doing
something that you may not need? The main reason
however often comes down to timing, especially with
things like music.
A music player will usually require that you call an
update function every frame, which is 50 times a se
cond. If you don’t do this then the music will star
t to sound very strange indeed as frequencies etc wil
l all go wrong. While it might seem easy to do this
if you know your program will always run in a frame,
when you start to write more complex programs and gam
es this might not always be the case. In a game you
may have an unknown number of enemies on the screen
firing an unknown number of bullets. While your code
might happily run in a frame if there are 5 enemies,
what happens when the 6th one appears?
To solve this we can put the music update routine into
the interrupt, then no matter how long the game takes
to update the music will continue to be updated at a
constant 50 frames a second. To do this will simply
add a call to the music player update in the IM2Routine.
; Update a music player
IM2Routine:
call Music_Update
ei
reti
Seems easy enough right? Well yes and no, the chance
s are this would crash your computer horribly. The r
eason is that the interrupt can occur at absolutely a
ny time and it doesn’t care what the state of your re
gisters are. When we just had EI/RETI it was fine, u
nfortunately the music update function will most like
ly alter registers, and even if it handles storing and
restoring them all itself, if it very good practice
to store and restore them yourself to be sure.
; Update a music player with register storing / restore
IM2Routine:
push af
push hl
push bc
push de
push ix
push iy
exx
ex af, af’
push af
push hl
push bc
push de
call Music_Update
pop de
pop bc
pop hl
pop af
ex af, af’
exx
pop iy
pop ix
pop de
pop bc
pop hl
pop af
ei
reti
Just to clear up a few things…
The Z80 provides 2 other interrupts that are not
maskable, that is you cannot disable them with the
DI instruction, they are the NMI and BUSRQ. To m
y knowledge the BUSRQ was never used as this was d
esigned to allow DMA systems to work along side the
Z80, something the spectrum never had.
The NMI (Non-Maskable Interrupt) was used on the
Spectrum but was only available via additional hardware.
Certain hardware devices had a button that would trigger
a NMI which would cause the current program to stop
and the routine at address $0066 to be executed.
As this address is fixed and in the ROM it is of
very little use for software programmers.
RET / RETI
You may have noticed that the interrupt routine
ends with RETI rather than the standard RET, thi
s is for completeness and not specifically requi
red, you can end with RET if you prefer. The ma
in reason RETI was added to the instruction set i
s to allow hardware to detect when the end of the in
terrupt routine and to pass control onto the next in
terrupt in a dasiy chained system. The hardware coul
d specifically check the opcode for RETI. If it
checked RET it could get confused when coming to the end
of a function rather than the entire interrupt routine.
To my knowledge no devices for the spectrum
use this feature.
EI but not DI?
There is another oddity in the interrupt routine
, we have an EI at the end but we never do a DI
. This is due to the fact the Z80 will do an i
nternal DI when it starts processing the interrupts.
The reason for this is to stop multiple interrupts
triggering on top of each other and overrunning the
system stack. You can do an EI before the end of
your interrupt routine if you wish but general practice is
to wait until you have finished then enable them,
that way there is no chance of you starting a new
interrupt routine before the old one has finished.
;from breakintoprogram.co.uk
In order to make your game compatible with the 128K Spectrum,
you will need to create your vector table.
I usually arrange my memory map as follows:
SP @ 0x000 – so it grows down from the top of RAM
Interrupt vector table @ 0xFE00 (257 bytes) with the value 0xFD
A 3 byte jump instruction @ 0xFDFD to the interrupt routine
This is quite economical on memory, and will ensure that
your 48K game is compatible with the 128K Spectrum.
If you are planning on using memory paging on the 128K Spectrum,
I’d suggest moving the interrupt routine elsewhere.
The above code will work,
but remember to set IM2_JP to an address where the high and low bytes are the same.
Stack_Top EQU #0000 ; Stack at top of RAM
IM2_Table EQU #FE00 ; 256 byte page (+ 1 byte) for IM2
IM2_JP EQU #FDFD ; 3 bytes for JP routine under IM2 table
Initialise_Interrup DI
LD DE, IM2_Table; The IM2 vector table (on page boundary)
LD HL, IM2_JP ; Pointer for 3-byte interrupt handler
LD A, D ; Interrupt table page high address
LD I, A ; Set the interrupt register to that page
LD A, L ; Fill page with values
lll LD (DE), A
INC E
JR NZ, lll
INC D ; In case data bus bit 0 is not 0, we
LD (DE), A ; put an extra byte in here
LD (HL), #C3 ; Write out the interrupt handler, a JP instruction
INC L
LD (HL), low Interrupt; Store the address of the interrupt routine in
INC L
LD (HL), high Interrupt
IM 2; Set the interrupt mode
EI ; Enable interrupts
RET
Interrupt DI ; Disable interrupts
PUSH AF ; Save all the registers on the stack
PUSH BC ; This is probably not necessary unless
PUSH DE ; we're looking at returning cleanly
PUSH HL ; back to BASIC at some point
PUSH IX
push iy
EXX
EX AF,AF'
PUSH AF
PUSH BC
PUSH DE
PUSH HL
;;;;PUSH IY
; Your code here...call proc1;call proc2 etc
;;;;POP IY; Restore all the registers
POP HL
POP DE
POP BC
POP AF
EXX
EX AF,AF'
pop iy
POP IX
POP HL
POP DE
POP BC
POP AF
EI ; Enable interrupts
RET ; And return
Т.е. тебе нужно ждать нужное кол-во тактов?
Так бы стразу и сказал
ld bc,нужное число тактов
call DELAY;ждёт нужно число тактов из bc>141такта
; Z80 delay routine; by Jan Bobrowski, license GPL, LGPL
DELAY:
; wait bc T (including call; bc>=141)
; destroys: af, bc, hl
ld hl, -141
add hl, bc
ld bc, -23
_loop add hl, bc
jr c, _loop
ld a, l
add a, 15
jr nc, _g0
cp 8
jr c, _g1
or 0
_g0 inc hl
_g1 rra
jr c, _b0
nop
_b0 rra
jr nc, _b1
or 0
_b1 rra
ret nc
ret
Так же повотрю программу для определе-
ния количества тактов между прерываниями
компьютера.
В регистре DE кол-во тактов деленное
на десять (например 7166 это 71660
тактов).
ORG >#7FFF ;!!!!
DI
LD HL,#FE00 ; Организация режима
LD A,H ; IM 2
LD I,A
DEC A
LD (HL),A
INC L
JR NZ,$-2
INC H
LD (HL),A
LD A,#C9 ; Временно блокируем
LD (#FDFD),A ; программу возврата
LD HL,RETURN ; Заносим адрес возв-
LD (#FDFE),HL; рата из подсчета
IM 2
LD DE,0 ; Обнуляем счетчик
LD HL,CYC
LD A,#C3
EI
HALT ; холостой прогон
EI
HALT ; холостой прогон
EI
LD (#FDFD),A ; Разблок. возврат
CYC INC DE ; считаем такты
JP (HL)
RETURN DI
POP AF ; приводим стек в исходное
INC DE ; состояние
INC DE
;(C) J/C.I.C.
В этой статье я кратко изложу принцип программного подсчета
количества тактов в строке (за сколько тактов видео-контроллер
перешлет 1 строку изображения на монитор). Для чего это надо:
для создания крутых эффектов на бордюре, multicolor'а и т.п. Эти
эффекты будут правильно работать на всех компах кроме динозавров
с медленной памятью и машине, где количество строк по вертикали
не 320 или 312. Програмный изврат, о котором я Вам сейчас пове-
даю, будет автоматически настраиваться под любой попавшийся ему
в руки комп, но надо будет только ему указать количесво строк по
вертикали и все !
А теперь ближе к делу. Сначала немного теории. Как, Вы дума-
ете, задается количество тактов между прерываниями ? Может где-
то на плате запаян счетчик который и отсчитывает нужное количес-
тво ? Нет ! Все совсем проще. Как известно, у нормального компа
312 или 320 вертикальных строк изображения (вместе с экраном и
бордюром). Так вот, когда ULA (video-контроллер) эти строки пе-
редаст монитору, приходит прерывание, а затем все опять по цик-
лу. Но у компов разное количество тактов между прерываниями, а
количество строк совпадает. Значит, у них за разное время переб-
расывается 1 строка. Известно также, что ULA за 1 такт при 3.5
мгц перебрасывает 2 точки на монитор (и это константа). Значит,
на разных компьютерах разное количество точек по горизонтали !!!
Теперь можно вывести формулу: количество строк * количество
тактов в строке = количество тактов между прерываниями - и это
со 100%-й точностью !!! Допустим, что количество тактов между
прерываниями приблизительно можно сосчитать. Количество строк
укажет user (или coder) при инсталяции, и количество тактов в
строке неизвестное, которое можно найти опять же по формуле (это
математика): количество тактов между прерываниями / на количест-
во строк = неизвестное (если будет остаток при делении , то ре-
зультат нужно будет от-INC'рементить).
А теперь программа:
DI
LD A,#18
LD (#FFFF),A
LD A,#C3
LD (65524),A
LD HL,INT1
LD (65525),HL ;адрес обработки прерывания
LD HL,#BE00
LD DE,#BE01
LD (HL),255
LD A,H
LD I,A
LD BC,#0100 ;генерируем таблицу векторов
LDIR ;прерывания
IM 2 ;установка 2 режима прерывания
LD DE,0
LD HL,INT2
EI
HALT
......
INT1 LD (65525),HL ;новый адрес обработки прерывания
LD HL,INT3
EI
HALT
......
INT2 LD (65525),HL ;опять кладется новый адрес
EI
INC DE ;цикл подсчета тактов
JP $-1
;
INT3 POP HL
POP HL ;для того чтобы вернуться по RET
EX DE,HL ;к первому HALT'у
ADD HL,HL ;HL * 8
ADD HL,HL
ADD HL,HL
LD A,N ;N=0 если 312 строк, иначе 320
OR A
LD BC,156 ;а вот догадайтесь сами, почему
JP Z,INT4 ;156 вместо 312 и 160 вместо 320
LD BC,160
XOR A
INT4 OR A
SBC HL,DE ;деление как по формуле ...
INC A
JP NC,INT4 ;теперь в A будет количество
;тактов в строке.
Поясню, что 3 раза я записывал разный адрес обработки преры-
вания для того, чтобы на некоторых глючных компах (на моем к
примеру) все работало О.К., а если вместо 3 раз будет 2 раза, то
на выходе в A может запросто оказаться 1.
TAPE
Header:
0 - тип блока (0=programm, 1= number array, 2=character array, 3=bytes)
1-10 имя блока (10 байт)
11-12 длина блока
13-14 bytes: адрес, program: номер строки старта (line xx), array:
14=имя переменной, 13 не учитывается
15 используется только Program: длина программ бейсика (bytes и arrays не используется)
Скорость
Из-за того, что разные биты на ленте занимают разное время, процедура
ленты Spectrum не имеет четко определенной скорости. Однако
мы можем рассчитать некоторые пределы и типичные значения; как
отмечалось выше, бит «0» занимает 2 × 855 = 1710 T-состояний, а бит
«1» занимает 2 × 1710 = 3420 T-состояний. Если предположить,
что Спектрум 48K работает на частоте 3,50 МГц (машины 128K работают
на частоте 3,54 МГц или примерно на 1% быстрее;
здесь эта разница практически не имеет значения), это означает, что:
Поток чистых нулевых «0» битов загружается со скоростью 3500000 ÷ 1710 ≈ 2046 бод.
Поток чистых битов «1» загружается со скоростью 3500000 ÷ 3420 ≈ 1023 бод.
Смешанный поток с равным количеством битов «0» и «1» загружается со скоростью
2 × 3500000 ÷ (3420 + 1710) ≈ 1364 бод.
В реальных данных Spectrum, как правило, было больше
битов «0», чем битов «1», поэтому типичная скорость передачи данных
на самом деле была немного выше, чем 1364 бод.
;load bytes w/o header ;save
LD IX,addr
LD DE,len
LD А,#ff ;load bytes
SCF ;вроде не нужен при записи
CALL #556 ;rom proc LD-BYTE / #4C2 = SA-BYTES
Load=Merge
ld bc,#0022
rst #30
push de
pop ix
ld (iy+#3a),1
ld (ix+1),#ff
call #071d
ld hl,(#5c42)
ld (#5c45),hl
rst 8
db #ff
.TAP
формат для хранения образа кассеты. Позволяет хранить данные,
сохранённые стандартными процедурами Sinclair BASIC для работы
с кассетой на ZX Spectrum. Для хранения данных в нестандартных
форматах предназначен более сложный формат TZX.
Файл в формате TAP может содержать один или несколько блоков.
Каждый блок состоит из двухбайтового заголовка с длиной
последущих данных и непосредственно данных, включая флаги
и контрольную сумму. Отдельные TAP-файлы могут легко "склеиваться"
вместе, например, командой
COPY /B file1.tap + file2.tap all.tap в MS-DOS.
==========================================================================================================
Offset: Field type: Length: Description: Additional information:
0 WORD(LSB,MSB) 2 [data length] length of the following datablock in bytes (0..65535)
2 Tape data [data length] data as it is stored on tape, may be a header or
any data from ZX-Spectrum
----------------------------------------------------------------------------------------------------------
Valid data blocks - headers (always 19 bytes long):
==========================================================================================================
case #1: program header or program autostart header - for storing BASIC programs
Offset: Field type: Length: Description: Additional information:
0 BYTE 1 flag byte always 0. Byte indicating a standard ROM loading header
1 BYTE 1 data type always 0: Byte indicating a program header
2 ARRAY 10 OF CHAR 10 file name loading name of the program. filled with spaces (CHR$(32))
12 WORD 2 [data length] length of the following data (after the header)
= length of BASIC program + variables
14 WORD 2 autostart line LINE parameter of SAVE command. Value 32768 means
"no auto-loading"; 0..9999 are valid line numbers.
16 WORD 2 [program length] length of BASIC program; remaining bytes
([data length] - [program length]) = offset of variables
18 BYTE 1 checksum byte simply all bytes (including flag byte) XORed
==========================================================================================================
case #4: byte header or SCREEN$ header - for storing machine code or screens
Offset: Field type: Length: Description: Additional information:
0 BYTE 1 flag byte always 0. Byte indicating a standard ROM loading header
1 BYTE 1 data type always 3: Byte indicating a byte header
2 ARRAY 10 OF CHAR 10 file name loading name of the program. filled with spaces (CHR$(32))
12 WORD 2 [data length] length of the following data (after the header)
in case of a SCREEN$ header = 6912
14 WORD 2 start address in case of a SCREEN$ header = 16384
16 WORD 2 unused = 32768
17 BYTE 1 checksum byte simply all bytes (including flag byte) XORed
----------------------------------------------------------------------------------------------------------
Valid data blocks - data blocks:
==========================================================================================================
case #5: standard data blocks or custom data blocks - (2+[data length]) bytes
Offset: Field type: Length: Description: Additional information:
0 BYTE 1 flag byte always 255 indicating a standard ROM loading data block or
any other value to build a custom data block
1 BYTE [data length] data block the essential data (may be empty)
1+[data length] BYTE 1 checksum byte simply all bytes (including flag byte) XORed
----------------------------------------------------------------------------------------------------------
Разное...
SCREEN FADEIN & FADEOUT
ClS LD HL,16384 ; Start address of screen bitmap
LD DE,16385 ; Address + 1
LD BC,6144 ; Length of bitmap memory to clear
LD (HL),0 ; Set the first byte to 0
LDIR ; Copy this byte to the second, and so on
LD BC,767 ; Length of attribute memory, less one to clear
LD (HL),A ; Set the first byte to A
LDIR ; Copy this byte to the second, and so on
RET
;
FADEIN LD HL,DEPK_SCR
LD DE,#4000
LD BC,#1800
LDIR
LD B,8
FAIN3 ;;;HALT
LD HL,#5800
LD DE,DEPK_SCR+#1800
FAIN2 LD A,(DE)
AND #3F
JR Z,FAIN1
LD A,(DE)
AND #C0
OR (HL)
LD (HL),A
LD A,(DE)
XOR (HL)
AND #07
JR Z,FAIN4
INC (HL)
FAIN4 LD A,(DE)
XOR (HL)
AND #38
JR Z,FAIN1
LD A,(HL)
ADD A,#08
LD (HL),A
FAIN1 INC DE
INC HL
LD A,H
CP #5B
JR C,FAIN2
DEC A
DJNZ FAIN3
RET
;FADEOUT
FADEOUT LD B,#08
FADEL ;;;HALT
LD HL,#5800
LD A,(HL)
AND A
JR Z,FADO1
FADO3 LD A,(HL)
AND #07
JR Z,FADO2
DEC (HL)
FADO2 LD A,(HL)
AND #38
JR Z,FADO1
LD A,(HL)
SUB #08
LD (HL),A
FADO1 INC HL
LD A,H
CP #5B
JR C,FADO3
DJNZ FADEL
; Fill a box of the screen with a solid colour
; A: The colour
; HL: Address in the attribute map
; C: Width
; B: Height
;
Fill_Attr: LD DE,32
1xxx: PUSH HL
PUSH BC
2xxx: LD (HL), A
INC L
DEC C
JR NZ, 2xxx
POP BC
POP HL
ADD HL,DE
DJNZ 1xxx
RET
;fullscreen (paper+border) flasher
FLASHSCREEN ld ix,flashdata
FLASHCIKL halt
ld a,(ix+00)
inc ix
cp FF
ret z
out (FE),a
ld c,a
rlca
rlca
rlca
or c
ld hl,5800
ld de,5801
ld bc,02FF
ld (hl),a
ldir
jr FLASHCIKL
flashdata db 01,02,03,04,05,06,07,06,05,04,03,02,01,00,FF
;random fade pixels
PIXFA LD B,8
LD DE,0
PF2 LD HL,#4000
PUSH DE
PF3 LD A,(DE)
AND (HL);if XOR - white noise
LD (HL),A
INC HL
INC DE
LD A,H
CP #58
JR NZ,PF3
POP DE
LD HL,100
ADD HL,DE
EX DE,HL
DJNZ PF2
RET
;random fade pixels & attrs from Lode Runner
lrfade ld b,4
ld de,0
lrfad2 push bc
ld bc,#1b00
ld hl,#4000
lrfad1 ld a,(de)
and (hl)
ld (hl),a
inc hl
dec bc
inc de
ld a,c
or b
jr nz,lrfad1
pop bc
halt
djnz lrfad2
ret
;double shift left-right vertical lines
LD B,8
LOOP2 LD HL,16384
PUSH BC
; HALT
LD BC,#0C00
LOOP1 SLA (HL)
INC HL
SRL (HL)
INC HL
DEC BC
LD A,B
OR C
JR NZ,LOOP1
POP BC
DJNZ LOOP2
RET
;up to down line R clear scree
ORG 40000
LD HL,16384
LD B,192
LOOP PUSH BC
PUSH HL
PUSH HL
CALL LINE
POP HL
CALL CLS
POP HL
CALL ADDR
POP BC
DJNZ LOOP
RET
LINE LD B,32
LD A,R
AND 63
LD D,A
L1 LD A,(DE)
LD (HL),A
INC HL
INC DE
DJNZ L1
LD BC,#BBB ;wait
PAUSE DEC BC
LD A,B
OR C
JR NZ,PAUSE
RET
CLS PUSH HL
POP DE
INC DE
LD (HL),0
LD BC,31
LDIR
RET
ADDR INC H
LD A,H
AND 7
RET NZ
LD A,L
ADD A,32
LD L,A
RET C
LD A,H
SUB 8
LD H,A
RET
;затухание линия за линией
begin ei
ld hl, #5720
ld c, #1E
loc_606B push hl
ld b, 0
loc_606E ld a, h
cp #58
jr c, loc_6088
ld a, (hl)
and 7
jr z, loc_6079
dec a
loc_6079 ld d, a
ld a, (hl)
and #38
jr z, loc_6081
sub 8
loc_6081 or d
ld d, a
ld a, (hl)
and #C0
or d
ld (hl), a
loc_6088 inc hl
ld a, h
cp #5B
jr z, loc_6090
djnz loc_606E
loc_6090 halt
ld de, #20
pop hl
add hl, de
dec c
jr nz, loc_606B
ret
diagfad ei
halt
pv1 ld e,0 ;X
ld d,0 ;Y
plp0 ld l,d,h,0
add hl,hl
add hl,hl
add hl,hl
add hl,hl
add hl,hl
ld c,e
ld b,#58
add hl,bc
ld (hl),0
inc d
ld a,d
cp 24
jr nc,pv2
dec e
jp p,plp0
pv2 ld e,31 ;X
ld d,23
plp1 ld l,d,h,0
add hl,hl
add hl,hl
add hl,hl
add hl,hl
add hl,hl
ld c,e
ld b,#58
add hl,bc
ld (hl),0
dec d
jp m,exlp1
inc e
bit 5,e
jp z,plp1
exlp1 ld a,(pv2+1)
dec a
ld (pv2+1),a
ld a,(pv1+1)
inc a
ld (pv1+1),a
cp 32-4
jp nz,diagfad
ret
Some Procedures from game Dominion (?) by Players
****************************************
* FADE SCREEN DOWN TO ZERO *
****************************************
FADEOUT LD B,8
FADE2 PUSH BC
EI
HALT
LD HL,22528+256
LD BC,512
FADE3 LD A,(HL)
AND A
JP Z,NOTHING
RES 7,A
RES 6,A
RES 5,A
RES 4,A
RES 3,A
DEC A
LD (HL),A
NOTHING INC HL
DEC BC
LD A,B
OR C
JP NZ,FADE3
LD B,3
CALL PAUSE
POP BC
DJNZ FADE2
LD HL,18432
LD DE,18433
LD (HL),L
LD BC,4095
LDIR
RET
****************************************
* FADE WHOLE SCREEN DOWN TO ZERO *
****************************************
TOTALFADE LD B,8
FADE25 PUSH BC
EI
HALT
LD HL,22528
LD BC,768
FADE35 LD A,(HL)
AND 7
LD (HL),A
AND A
JP Z,NOTHING5
DEC A
LD (HL),A
NOTHING5 INC HL
DEC BC
LD A,B
OR C
JP NZ,FADE35
LD B,3
CALL PAUSE
POP BC
DJNZ FADE25
LD HL,16384
LD DE,16385
LD (HL),L
LD BC,6911
LDIR
JP CLEARMASK
****************************************
* TOTAL FADE IN SCREEN *
****************************************
TOTALFADEU LD B,8
FADE257 PUSH BC
EI
HALT
LD HL,22528
LD BC,768
FADE357 LD A,(HL)
CP 7
JP Z,NOTHING57
INC A
LD (HL),A
NOTHING57 INC HL
DEC BC
LD A,B
OR C
JP NZ,FADE357
LD B,3
CALL PAUSE
POP BC
DJNZ FADE257
RET
****************************************
* FADE SCREEN UP TO ZERO *
****************************************
FADEIN LD B,8
FADE23 PUSH BC
EI
HALT
LD HL,22528+256
LD BC,512
FADE33 LD A,(HL)
CP 7
JP Z,NOTHING3
INC A
LD (HL),A
NOTHING3 INC HL
DEC BC
LD A,B
OR C
JP NZ,FADE33
LD B,3
CALL PAUSE
POP BC
DJNZ FADE
SCROLLERS
ADDRP1 EQU #4067;адрес верхней линии для сдвига вверх
ADDRP2 EQU #4067+32;адрес на знакоместо ниже
ADDRA1 EQU #5867;то же самое
ADDRA2 EQU #5867+32;для атрибутов
ADDRP3 EQU #57C7-32;адрес линии для сдвига вниз
ADDRP4 EQU #57C7;адрес на знакоместо ниже
ADDRA3 EQU #5AC7-32;то же самое
ADDRA4 EQU #5AC7;для атрибутов
SHIR EQU 8 ;ширина сдвигаемого окна
DLINA EQU 19*8 ;длина --//-- NB! для сдвига вниз это значение должно быть меньше на еденицу
;Сдвиг произвольного окна (графика+атрибуты) вверх(C) Wizard/DTr
SCR_UP LD HL,ADDRP2
LD DE,ADDRP1
LD B,DLINA
SCRLU_P PUSH BC
PUSH DE
PUSH HL
LD BC,SHIR
LDIR
POP HL
CALL DOWN_HL
POP DE
CALL DOWN_DE
POP BC
DJNZ SCRLU_P
LD HL,ADDRA2
LD DE,ADDRA1
LD B,DLINA/8
SCRLU_A PUSH BC
PUSH DE
PUSH HL
LD BC,SHIR
LDIR
POP HL
POP DE
LD B,32
scrlu INC HL
INC DE
DJNZ scrlu
POP BC
DJNZ SCRLU_A
RET
;Сдвиг произвольного окна (графика+атрибуты) вниз (C) Wizard/DTr
SCR_DN LD HL,ADDRP3
LD DE,ADDRP4
LD B,DLINA
SCRLD_P PUSH BC
PUSH DE
PUSH HL
LD BC,SHIR
LDIR
POP HL
CALL UP_HL
POP DE
CALL UP_DE
POP BC
DJNZ SCRLD_P
LD HL,ADDRA3
LD DE,ADDRA4
LD B,DLINA/8
SCRLD_A PUSH BC
PUSH DE
PUSH HL
LD BC,SHIR
LDIR
POP HL
POP DE
LD B,32
scrld DEC HL
DEC DE
DJNZ scrld
POP BC
DJNZ SCRLD_A
RET
;
DOWN_HL INC H
LD A,H
AND 7
RET NZ
LD A,L
ADD A,32
LD L,A
RET C
LD A,H
SUB 8
LD H,A
RET
DOWN_DE INC D
LD A,D
AND 7
RET NZ
LD A,E
ADD A,32
LD E,A
RET C
LD A,D
SUB 8
LD D,A
RET
UP_HL DEC H
LD A,H
CPL
AND 7
RET NZ
LD A,L
SUB 32
LD L,A
RET C
LD A,H
ADD A,8
LD H,A
RET
UP_DE DEC D
LD A,D
CPL
AND 7
RET NZ
LD A,E
SUB 32
LD E,A
RET C
LD A,D
ADD A,8
LD D,A
RET
;LEN EQU $-#6000
; DISPLAY "Длина модуля ",LEN," мегабайт"
;--------------------------------------;
; FULL SCREEN SCROLL UP 1 ;
; coded by Kolotov Sergey ;
; (c) SerzhSoft, Shadrinsk, may, 1998 ;
;--------------------------------------;
_NULL EQU 0
;--------------------------------------;
_DATA EQU #6000
SCR_TBL EQU _DATA ;,#0300
DATAEND EQU SCR_TBL+#0300
;--------------------------------------;
ORG #8000
;--------------------------------------;
MAINPRG
EI
;
CALL MK_STBL
; CALL MKSRLUP
;
LD HL,#0000
LD DE,#4000
LD BC,#1800
LDIR
;
LD B,#C0
LP_MAIN PUSH BC
HALT
DI
CALL SRL_UP
EI
POP BC
DJNZ LP_MAIN
;
RET
;--------------------------------------;
MK_STBL
LD HL,SCR_TBL
LD DE,#4000
LD B,#C0
LP_MSTB LD (HL),E
INC HL
LD (HL),D
INC HL
;
INC D
LD A,D
AND #07
JR NZ,$+12
LD A,E
ADD A,#20
LD E,A
JR C,$+6
LD A,D
SUB #08
LD D,A
;
LD (HL),E
INC HL
LD (HL),D
INC HL
DJNZ LP_MSTB
; RET
;--------------------------------------;
MKSRLUP
LD HL,SRL_UP
LD (HL),#ED ;ld (...),sp
INC HL
LD (HL),#73
INC HL
PUSH HL
INC HL
INC HL
LD (HL),#01 ;ld bc,...
INC HL
LD (HL),#E0
INC HL
LD (HL),#17 ;ld bc,#17E0
INC HL
LD (HL),#31 ;ld sp,...
INC HL
LD DE,SCR_TBL
LD (HL),E
INC HL
LD (HL),D ;ld sp,SCR_TBL
INC HL
PUSH HL ;lp_srup
LD (HL),#D1 ;pop de
INC HL
LD (HL),#E1 ;pop hl
INC HL
LD B,#20
LP_MSU1 LD (HL),#ED ;ldi
INC HL
LD (HL),#A0
INC HL
DJNZ LP_MSU1
LD (HL),#EA ;jp pe,...
INC HL
POP DE
LD (HL),E
INC HL
LD (HL),D ;jp pe,lp_srup
INC HL
LD (HL),#31 ;ld sp,...
INC HL
LD (HL),B ;#00
INC HL
LD (HL),#58 ;ld sp,#5800
INC HL
LD B,#10
LP_MSU2 LD (HL),#C5 ;push bc
INC HL
DJNZ LP_MSU2
LD (HL),#31 ;ld sp,_NULL
INC HL
EX DE,HL
POP HL ;ld (...),sp
LD (HL),E ; ^^^
INC HL
LD (HL),D
EX DE,HL
INC HL
INC HL
LD (HL),#C9 ;ret
RET
;--------------------------------------;
_CODE EQU $
SRL_UP EQU _CODE ;,#0066
CODEEND EQU SRL_UP+#0066
;--------------------------------------;
;--------------------------------------;
; FULL SCREEN SCROLL UP 2 ;
; coded by Kolotov Sergey ;
; (c) SerzhSoft, Shadrinsk, may, 1998 ;
;--------------------------------------;
_NULL EQU 0
LNS_NUM EQU #40
;--------------------------------------;
_DATA EQU #6000
SCR_TBL EQU _DATA ;,#0300
DATAEND EQU SCR_TBL+#0300
;--------------------------------------;
ORG #8000
;--------------------------------------;
MAINPRG
EI
;
CALL MK_STBL
; CALL MKSRLUP
;
LD HL,#0000
LD DE,#4000
LD BC,#1800
LDIR
;
LD B,#C0
LP_MAIN PUSH BC
HALT
DI
CALL SRL_UP
EI
POP BC
DJNZ LP_MAIN
;
RET
;--------------------------------------;
MK_STBL
LD HL,SCR_TBL
LD DE,#4000
LD B,#C0
LP_MSTB LD (HL),E
INC HL
LD (HL),D
INC HL
;
INC D
LD A,D
AND #07
JR NZ,$+12
LD A,E
ADD A,#20
LD E,A
JR C,$+6
LD A,D
SUB #08
LD D,A
;
LD (HL),E
INC HL
LD (HL),D
INC HL
DJNZ LP_MSTB
; RET
;--------------------------------------;
MKSRLUP
LD HL,SRL_UP
LD (HL),#ED ;ld (...),sp
INC HL
LD (HL),#73
INC HL
PUSH HL
INC HL
INC HL
LD (HL),#01 ;ld bc,...
INC HL
LD (HL),#E0
INC HL
LD (HL),#17 ;ld bc,#17E0
INC HL
LD (HL),#31 ;ld sp,...
INC HL
LD DE,SCR_TBL
LD (HL),E
INC HL
LD (HL),D ;ld sp,SCR_TBL
INC HL
LD (HL),#C3 ;jp ...
INC HL
LD D,H
LD E,L
LD BC,#0042+2
ADD HL,BC
EX DE,HL
LD (HL),E
INC HL
LD (HL),D ;jp lp_srup+#0042
INC HL
PUSH HL ;lp_srup
LD C,LNS_NUM
LP_MSU0 LD (HL),#D1 ;pop de
INC HL
LD (HL),#E1 ;pop hl
INC HL
LD B,#20
LP_MSU1 LD (HL),#ED ;ldi
INC HL
LD (HL),#A0
INC HL
DJNZ LP_MSU1
DEC C
JR NZ,LP_MSU0
LD (HL),#EA ;jp pe,...
INC HL
POP DE
LD (HL),E
INC HL
LD (HL),D ;jp pe,lp_srup
INC HL
LD (HL),#31 ;ld sp,...
INC HL
LD (HL),B ;#00
INC HL
LD (HL),#58 ;ld sp,#5800
INC HL
LD B,#10
LP_MSU2 LD (HL),#C5 ;push bc
INC HL
DJNZ LP_MSU2
LD (HL),#31 ;ld sp,_NULL
INC HL
EX DE,HL
POP HL ;ld (...),sp
LD (HL),E ; ^^^
INC HL
LD (HL),D
EX DE,HL
INC HL
INC HL
LD (HL),#C9 ;ret
RET
;--------------------------------------;
LN_SRUP EQU LNS_NUM*#0042+#0027
;--------------------------------------;
_CODE EQU $
SRL_UP EQU _CODE ;,LN_SRUP
CODEEND EQU SRL_UP+LN_SRUP
;--------------------------------------;
RUNNING LINES ATTRIBUTES
;Attribute Running Line (lvd'96)
arline ld a, (alcnt)
dec a
jr nz, arl2
ld hl, (cure)
inc hl
ld a, (hl)
cp 0
jr nz, arl1
ld hl, text
ld a, (hl)
arl1 ld (cure), hl
ld l, a
ld h, 0
ld de, #3C00 ;font here
add hl, hl
add hl, hl
add hl, hl
add hl, de
ld de, albuff
ld bc, 8
ldir
ld a, 8
arl2 ld (alcnt), a
ld hl, #5A01
ld ix, albuff
ld b, 8
alldi ld c, h
ld d, c
ld e, l
dec e
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
rlc (ix+0)
inc ix
ex de, hl
jr nc, arl3
ld a, (coltxt)
ld (hl), a
jr alldic
arl3 ld a, (colbkg)
ld (hl), a
alldic inc hl
inc hl
djnz alldi
ret
coltxt db #78 ;color of letters
colbkg db #99 ;color of background
alcnt db 1
cure dw albuff+7
albuff db 0,0,0,0,0,0,0,0
text db "Runnin Line By Vadik (aka LVD) in feb.1996"
db 0
;attribute running line by L.Smirnov
ORG #8000
LD SP,#8000
CALL INIT
RES 5,(IY+1)
A1 HALT
CALL LINE
BIT 5,(IY+1)
JR Z,A1
RET
LINE LD HL,#5A01
LD DE,#5A00
LD BC,255
LDIR
LD HL,FLAG
RLC (HL)
JR NC,L1
LD HL,(TEXT)
INC HL
LD (TEXT),HL
LD A,(HL)
OR A
JR NZ,L3
INIT LD A,1
LD (FLAG),A
LD HL,TXT
LD (TEXT),HL
LD A,(HL)
L3 LD L,A
LD H,0
ADD HL,HL
ADD HL,HL
ADD HL,HL
LD DE,#3C00 ; LD DE,(#5C36)
ADD HL,DE
LD DE,BUFF
LD BC,8
LDIR
L1 LD HL,#5A1F
LD DE,BUFF
LD C,32
LD A,8
L2 LD (HL),#90 ;color 1
EX DE,HL
RLC (HL)
EX DE,HL
JR NC,L4
LD (HL),#FA ;color 2
L4 INC DE
ADD HL,BC
DEC A
JR NZ,L2
RET
BUFF DEFS 8
FLAG DEFB 1
TEXT DEFW TXT
TXT DEFB "Atribute scroller, Written by Smirnov Leonid 02.11.1996... ", 0
;from TASM4.12 RUNNING ATTR LINE BOLD
ORG #8000
LD SP,#6000
START CALL ATTRLINE
JP START
ATTRLINE XOR A
INC A
LD (atlin2+1), A
LD HL, attr_text
LD (atlin3+1), HL
atlin1 EI
HALT
LD HL,#5901
LD DE,#5900
LD BC,#FF
LDIR
atlin2 LD A, 0
RRCA
LD (atlin2+1), A
LD C, A
JR NC, atlfnt
atlin3 LD HL, attr_text
BIT 7, (HL)
JR Z, atlin4
LD HL, attr_text
atlin4 LD A, (HL)
INC HL
LD (atlin3+1), HL
LD L, A
LD H, 0
ADD HL, HL
ADD HL, HL
ADD HL, HL
LD A, #3C
ADD A, H
LD H, A
LD (atlfnt+1), HL
atlfnt LD HL, #3D00
LD DE, #591F
atlin5 LD A, (HL)
RRCA
OR (HL)
INC HL
AND C
CP 1
CCF
SBC A, A
LD (DE), A
LD A, #20
ADD A, E
LD E, A
JR NC, atlin5
BIT 5, (IY+1)
JR Z, atlin1
RES 5, (IY+1)
RET
attr_text DB "DON-T SLEEP, MOTHERFUCKER... scroll from Tasm4.12"
DB #FF
;by Alex Raider
ORG #8000
LD HL, TEXT
LD A,(HL)
OR #40
LD (ldia0+1),A
INC HL
LD (ATRL+1),HL
LOOPATR EI
HALT
CALL ATTRLIN
JR LOOPATR
ATTRLIN CALL ATRLDI
ATREX LD A, 0
DEC A
AND 7
LD (ATREX+1), A
RET NZ
ATRL LD HL, 0
LD A, (HL)
AND A
JR NZ, ATRPRN
RET
ATRPRN INC HL
LD (ATRL+1), HL
SUB #20
ADD A, A
LD H, 0
LD L, A
ADD HL, HL
ADD HL, HL
LD A, H
ADD A, #3D
LD H, A
LD DE, buff8
LD A, (HL)
RRCA
; OR (HL)
LD (DE), A
INC L
INC DE
LD A, (HL)
RRCA
; OR (HL)
LD (DE), A
INC L
INC DE
LD A, (HL)
RRCA
OR (HL)
LD (DE), A
INC L
INC DE
LD A, (HL)
RRCA
OR (HL)
LD (DE), A
INC L
INC DE
LD A, (HL)
RLCA
OR (HL)
LD (DE), A
INC L
INC DE
LD A, (HL)
RLCA
OR (HL)
LD (DE), A
INC L
INC DE
LD A, (HL)
RLCA
OR (HL)
LD (DE), A
INC L
INC DE
LD A, (HL)
RLCA
OR (HL)
LD (DE), A
INC L
INC DE
RET
buff8 DB 0,0,0,0,0,0,0,0
ATRLDI LD IX, buff8
LD DE, #5A00
LD HL, #5A01
LD LY, 8; ;vysota
ldiloop LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
SLA (IX+0)
INC IX
LD A, #31 ;color backgnd
JR NC, ldiex
ldia0 LD A, 0
ldiex LD (DE), A
INC HL
INC DE
DEC LY;YL
JP NZ, ldiloop
RET
TEXT DB #81; color of letters
DB "ATTR SCROLLER from FLASH Inc intro by ALEX RAIDER......some modified :) "
;DB 0
CIRCLES
;--------------------------------------;
; CIRCLE AND TEXTURED FILL ;
; special for KF-STUDIO ;
;(c) SerzhSoft, Shadrinsk, 31 july 1997;
;--------------------------------------;
ORG #8000
JP EXAMPLE
;--------------------------------------;
;используется в изменяемых командах
_NULL EQU 0
;--------------------------------------;
;адрес текстуры для вывода
TXRADR DW TEXTURE
;--------------------------------------;
ATTR_P EQU 23693 ;атрибуты экрана
;--------------------------------------;
;биты: 0 - over 0/1, 2 - inverse 0/1
; 4 - ink&paper 9 (прозрачные атр.)
P_FLAG EQU 23697
;--------------------------------------;
;демонстрационный пример
EXAMPLE
XOR A
LD (P_FLAG),A
;
ld hl,-100
ld de,-100
ld a,255
CALL CIRCLE
;
LD E,10
LD D,10
XOR A
CALL TXRFILL
;
ld hl,200
ld de,100
ld a,50
CALL CIRCLE
;
LD E,200
LD D,100
LD A,1
CALL TXRFILL
;
ld hl,115
ld de,150
ld a,20
CALL CIRCLE
;
LD E,115
LD D,150
LD A,2
CALL TXRFILL
;
LD A,#04 ;[set 2,a]
LD (P_FLAG),A ;inverse 1
;
LD E,10
LD D,170
LD A,2
CALL TXRFILL
;
CALL WAITKEY
RET
;--------------------------------------;
;ждет нажатия на любую клавишу
WAITKEY
XOR A
IN A,(#FE)
CPL
AND #1F
JR Z,WAITKEY
RET
;--------------------------------------;
;Algorythm designed by SerzhSoft (c)1996
;рисование окружности
; HL=x, DE=y (-32768..+32767)
; A=радиус (0..255) ;при 0 - точка
CIRCLE
ld (X_CIRC),hl
ld (Y_CIRC),de
ld e,a
ld c,#00
ld b,c
ld d,c
srl a
LPCIR1 ex af,af'
LPCIR2 call PUT8PX
inc c
ex af,af'
sub c
jr nc,LPCIR1
dec e
add a,e
ex af,af'
ld a,e
cp c
jr nc,LPCIR2
ret
;
PUT8PX call PUT4PX
;
PUT4PX ld a,c
ld c,e
ld e,a
ld hl,_NULL
X_CIRC equ $-2
push hl
add hl,bc
call PUT2PX
pop hl
sbc hl,bc
;
PUT2PX inc h
dec h
ret nz
ld a,l
ld (X_NEW),a
ld hl,_NULL
Y_CIRC equ $-2
push hl
add hl,de
call PUT1PX
pop hl
sbc hl,de
;
PUT1PX inc h
dec h
ret nz
ld a,l
cp #C0
ret nc
push de
ld d,a
ld e,_NULL
X_NEW equ $-1
push bc
call PLOT
pop bc
pop de
ret
;--------------------------------------;
;текстурная заливка
; E=x, D=y, A=номер текстуры (0..255)
TXRFILL
RRCA
RRCA
RRCA
LD H,A
AND #E0
LD L,A
XOR H
ADD A,TEXTURE/256 ;A,'TEXTURE
LD H,A
LD (TXRADR),HL
;
;заливка, но не по номеру текстуры, а
;по адресу, установленному в TXRADR
TXRFIL2
LD A,D
CP #C0
RET NC
LD A,(P_FLAG)
BIT 2,A
LD A,#A9 ;[xor c] - inv. 0
JR Z,IF_TXF0
LD A,#A1 ;[and c] - inv. 1
IF_TXF0 LD (INV_TXF),A
LD HL,BUFFER
LD A,#FF
PUSH AF
PUSH DE
LP_TXF1 POP DE
INC D
JP Z,GO_TXF0
DEC D
CALL POINTHL
JR NZ,LP_TXF1
EX AF,AF'
LP_TXF2 LD A,E
LD (HL),A
OR A
JR Z,GO_TXF1
DEC E
CALL POINTHL
JR Z,LP_TXF2
LP_TXF3 INC E
JR Z,GO_TXF8
GO_TXF1 PUSH HL
CALL POINT
JR NZ,GO_TXF7
LD A,(HL)
OR C
CALL TO_PLOT
POP HL
LD A,D
OR A
JR Z,GO_TXF4
DEC D
CALL POINTHL
JR Z,GO_TXF2
EX AF,AF'
LD A,B
JR GO_TXF3
GO_TXF2 EX AF,AF'
INC A
DEC A
JR NZ,GO_TXF3
LD A,C
PUSH DE
GO_TXF3 EX AF,AF'
INC D
GO_TXF4 LD A,D
CP #BF
JR NC,LP_TXF3
INC D
CALL POINTHL
JR Z,GO_TXF5
EX AF,AF'
AND A
JR GO_TXF6
GO_TXF5 EX AF,AF'
JR C,GO_TXF6
SCF
PUSH DE
GO_TXF6 EX AF,AF'
DEC D
JR LP_TXF3
GO_TXF7 POP HL
GO_TXF8 LD A,E
SUB (HL)
INC HL
LD (HL),D
INC HL
LD (HL),A
INC HL
JR LP_TXF1
;
LP_TXF4 ADD HL,DE
DEC HL
LD A,(HL)
DEC HL
LD D,(HL)
DEC HL
LD E,(HL)
PUSH HL
PUSH AF
CALL POINT
POP AF
LD B,A
LD A,E
RRA
RRA
RRA
RRA
LD A,D
RLA
LD DE,(TXRADR)
XOR E
AND #1F
XOR E
LD E,A
LP_TXF5 LD A,(DE)
AND C
INV_TXF XOR C
XOR (HL)
LD (HL),A
RRC C
JR NC,GO_TXF9
INC L
LD A,E
XOR #01
LD E,A
GO_TXF9 DJNZ LP_TXF5
POP HL
GO_TXF0 LD DE,BUFFER
AND A
SBC HL,DE
JR NZ,LP_TXF4
RET
;--------------------------------------;
;выч. адреса и проверка состояния точки
POINT
LD B,#07
LD A,D
RRA
SCF
RRA
RRA
AND #5F
LD H,A
XOR E
AND B
XOR E
RRCA
RRCA
RRCA
LD L,A
LD A,D
XOR H
AND B
XOR H
LD H,A
LD A,E
AND B
LD B,A
LD A,#80
JR Z,GO_PNT
LP_PNT RRCA
DJNZ LP_PNT
GO_PNT LD C,A
AND (HL)
RET
;
POINTHL
PUSH HL
CALL POINT
POP HL
RET
;--------------------------------------;
;установка точки
PLOT
CALL POINT
LD A,(P_FLAG)
PUSH HL
LD HL,#A9A9 ;over 1, inv. 1
BIT 0,A
JR NZ,IF_PLT1
LD L,#B1 ;[or c] - over 0
IF_PLT1 BIT 2,A
JR NZ,IF_PLT2
LD H,#00 ;[nop] - inverse 1
IF_PLT2 LD (OVR_PLT),HL
POP HL
LD A,(HL)
OVR_PLT OR C
INV_PLT XOR C
;
TO_PLOT ;вход для пр-ры заливки и др.
LD (HL),A
LD A,(P_FLAG)
BIT 4,A
RET NZ
LD A,H
RRA
RRA
RRA
AND #03
OR #58
LD H,A
LD A,(ATTR_P)
LD (HL),A
RET
;--------------------------------------;
;выравнивание на сегмент (граница 256)
DS $/256*256+256-$
;--------------------------------------;
;различные текстуры 2*2 знакоместа
TEXTURE
DB %11111111,%11110000
DB %11111111,%11111000
DB %11000000,%00110100
DB %11011111,%11111010
DB %11011111,%11110100
DB %11011010,%11111010
DB %11011101,%11110100
DB %11011010,%11111010
DB %11011111,%11110100
DB %11011111,%11111010
DB %11111111,%11110100
DB %11111111,%11111010
DB %01010101,%01010100
DB %00101010,%10101010
DB %00010101,%01010100
DB %00000000,%00000000
;
DB %11111111,%11110000
DB %11111111,%11111000
DB %11000000,%00110100
DB %11011111,%11111010
DB %11011111,%11110100
DB %11011000,%11111010
DB %11011010,%11110100
DB %11011000,%11111010
DB %11011111,%11110100
DB %11011111,%11111010
DB %11111111,%11110100
DB %11111111,%11111010
DB %01010101,%01010100
DB %00101010,%10101010
DB %00010101,%01010100
DB %00000000,%00000000
;
DB %11111111,%11110000
DB %11111111,%11111000
DB %11000000,%00110100
DB %11011111,%11111010
DB %11011111,%11110100
DB %11011111,%11111010
DB %11011111,%11110100
DB %11011111,%11111010
DB %11011111,%11110100
DB %11011111,%11111010
DB %11111111,%11110100
DB %11111111,%11111010
DB %01010101,%01010100
DB %00101010,%10101010
DB %00010101,%01010100
DB %00000000,%00000000
;--------------------------------------;
;буфер для запоминания координат линий
; при заливке (требует большого объема)
BUFFER EQU $
;--------------------------------------;
MUSIC ANALYSERS
;горизонтальный аттрибутный аналайзер с Барнаульских дисков
ORG #6000
CALL MUS ;INIT MUSIC PLAYER
EI
ZA HALT
CALL MUS+5 ;PLAY MUSIC
CALL MEFF ;DRAW ANALYSER
IN A,(#FE)
CPL
AND #1F
JR Z,ZA
CALL MUS
RET
MEFF LD HL,#5800 ;chan a pos
LD A,8
CALL L1
LD HL,#5820 ;chan b pos
LD A,9
CALL L1
LD HL,#5840 ;chan c pos
LD A,10
CALL L1
RET
L1 LD BC,65533
OUT (C),A
IN A,(C)
AND 15
AND A
JR Z,PS1
LD B,A
EX AF,AF'
LD A,#FF
LL1 LD (HL),A
INC L
DJNZ LL1
EX AF,AF'
SUB 15
NEG
AND A
JR Z,PS1
LD B,A
LD A,7
LL2 LD (HL),A
INC L
DJNZ LL2
PS1 RET
MUS INCBIN "Music with player here
to do......