(C) PSB / Halloween
Direct Programming
General Sound.
Hello! I decided to write a little about Kulnev Spectrum
sound card, as well as its programming. But just do not think
that this is another article about how to play MOD'y and
effects or to voice the game ... Such literature in
Spectrum media did not (or do I just have not seen: -!).
Start with the source. Year in 1997, I
acquired GS (By the way, thanks for that HellRaiser /
Halloween), and how, perhaps, many began to learn of his
program, he played same MOD and FX, voiced Break games wrote
your player MOD (who understood and TR-DOS
and MS-DOS). But it all slowly fed up with it, because a
variety of something small. A After all, when GS is only
advertising, wrote it - one more complex, with its
percent, memory, and the frequency of once in 3 above.
So it is. And most importantly, a percent - all of us respected
Z80!!!
Many people probably still think,
how to program the GS directly,
if the description there are no such commands? Where people get
information on the undocumented commands and do what's inside
your GS? Do you think there is any thread docks on this? Now -
YES! Now even the scheme GS dismantled piece by piece, but
first of all This was not, but know it was still
possible and even easy. I will describe how it made
I have (in any, someone would be interested).
In the old days there was a tradition (one can say so)
tear anywhere AY'shnye Mouzon. Well me and I drove to pull
MOD from Target Renegade and Xecutor. Pull something pulled,
but in Xecutor found in the boot loader test GS. It addition to
the standard information has betrayed and copyrights of
firmware. Having examined more closely prog that all this takes
out card (There are several different pieces of getting any),
figured where the address can be set from which all this is
taken ... well took it and scored a similar prog to MASM, only
I have it pulled from the address # 0000 first 32kb. And then,
looking through the STS (Z80 after all!), What happened, saw
the program! All right, it was Rom. Well more understandable:
he was quietly apart, what and how, found the main loop, which
handles incoming commands looked just like the work documented
team, of which discovered some of the ports,
as for sound output and so on. Several
experiments, and I already knew almost
all, long before the appearance of such information. And it's
all thanks to the 3 rd (!!!) commands from bootstrapper
Xecutor!
Well, suppose we know General Sound "inside" and what we
do for you? Yes, very much, or more precisely - the use of
fees in an unusual way. This may
be anything, ranging from appending procedures that are lacking
in the standard version, but to its programs, which can be, and
the music is not included.
That's it. And now, actually,
programming.
Interface by Speccy
1. Command register (# BB = 187, record).
2. Status register (# BB = 187, Reading).
Bits: 7 - Data bit *
0 - Command bit *
3. Data register (# B3 = 179, record).
4. Output register (# B3 = 179, Reading).
*) Actually, I do not remember exactly as described by the
values of these bits after certain operations in the standard
instructions (Such as when a port is something to write
or if it is read, the bits somehow change), but I can say is
easier - bit is set to 1 if it _NE_OBSLUZHEN_. That is, if we
have a thread in the kinematic Data register, Data bit it will
be 1 until the GS out of it is not read. The same will be
inside the GS - Until the ZX does not read port, Data bit is 1.
Features command descriptions
SC$%'%$ - Send a command code$%'%$ into the instruction register
WC - Waiting for the adoption / implementation team
(Reset Command bit)
SD$%'%$ - Send data$%'%$ in the data register
WD - Waiting for the adoption of data (dumping Data bit)
GD$%'%$ - Take$%'%$ data from data register
WN - Waiting for new data from the GS (Installation Data bit)
Teams
# 18 - LD DE, nnnn
SD nnnn_LOW
SC # 18: WC
SD nnnn_HIGH
Record in the reg. a couple of DE value nnnn
(Within the GS!). nnnn_LOW and nnnn_HIGH - senior and junior
bytes value nnnn.
# 1A - Get data from (DE)
SC # 1A: WC
GD Value
Reads bytes from the cells addressable
DE.
# 1B - INC DE
SC # 1B: WC
Increases the value of DE at 1.
Thus, by using only
These 3 teams, you can read the memory
GS and learn everything you need:
LD HL, # 0000 address in the GS, where we want
something to read
LD A, L: CALL SD; do LD DE, nnnn in GS
LD A, # 18: CALL SC
LD A, H: CALL SD
LD HL, # 8000; address in the ZX, which will all add
LD DE, # 8000; block length
LOOP LD A, # 1A: CALL SC; take the value of GS
IN A, (# B3)
LD (HL), A
LD A, # 1B: CALL SC; increase the address in the GS
INC HL: DEC DE
LD A, D: OR E: JR NZ, LOOP
RET
SC OUT (# BB), A
WC IN A, (# BB): RRCA: JR C, WC
RET
SD OUT (# B3), A: RET
WD IN A, (# BB): RLCA: JR C, WD
RET
WN IN A, (# BB): RLCA: JR NZ, WN
RET
But there are a number of other useful (just necessary)
teams.
# 14 - Put datablock to GS
SD LENG_LOW
SC # 14: WC
SD LENG_HIGH: WD
SD GS_ADR_LOW: WD
SD GS_ADR_HIGH: WD
SD DataByte: WD; many times in a row, as indicated in LENG
Send a block of data in the GS at
GS_ADR and length LENG. This team is very
conveniently load their programs in GS.
# 13 - Jump to ADR
SD ADR_LOW
SC # 13: WC
SD ADR_HIGH
Passes at ADR in the GS. Suited to run the loaded program.
# 10 - OUT (PORT), A
SD PORT
SC # 10: WC
SD Value
# 11 - IN A, (PORT)
SD PORT
SC # 11: WC
GD Value
These two commands allow you to read
or write the value of the port and from the port of GS.
There are still a lot of useful and useless all teams (ala
any kovoksy and etc.), but for direct programming they
We do not need them.
Interface from the General Sound
Internal Ports:
# 00, the record - to open the desired page memory
# 01, reading - read the contents of the command register
# 02, reading - read the contents of data register
# 03, write - send data to Speccy
# 04, reading - read the contents of status register
# 05, the recording - reset bit Command bit in the status
register # 06, the recording - the volume of channel A
# 07, Record - Volume channel B
# 08, the record - the volume of the channel C
# 09, the record - the volume of the channel D
There are more ports and # 0A # 0B, but they
general nafig not needed and not used
(Although, who wants the scheme could see what they do;).
And now the details ...
1. OUT (# 00), Page (open a desired page of memory) In General
Sound location memory is as follows:
# 0000 - # 3FFF - the first 16K ROM
# 4000 - # 7FFF - 16k RAM
# 8000 - # FFFF - page RAM
Ie pages in the GS at 32 kilobytes
and are located at address # 8000. At zero
ROM page is full (ie, addresses # 8000 - # BFFF, too, that with
# 0000 - # 3FFF, but with # C000 - continued). In
the first page of the second 16k - copy memory
with # 4000 - # 7FFF, and the first 16K - the usual.
The second, third, etc. page - the usual
pages that can be used freely. In the basic version of the GS
(128k) 3 page: 2, 3 and 4, while the GS512 - 14 pages (not
including the 1 st and 0 th). Shorter than more memory, the
more pages. In GS_ROM a memory tester
(After reset), so that's out there like a set
already 64 pages ...
And yet at the memory. At 512
kilobyte version has a hardware
"Bug": when you turn on one page
included just two, ie 32k disappear.
Therefore, the total memory available in GS
51,232 = 480k!
2. IN A, (# 01) (read the contents of the register command) Ie
read what was sent Speck into the instruction register (# BB).
3. IN A, (# 02) (read the contents of register data) Ie read
what was sent to the SPECA data register (# B3).
4. OUT (# 03), Data (send data to
Speccy) In fact, to transfer data to the Spectrum, ie they can
be read on ZX from the data register (# B3).
5. IN A, (# 04) (read the contents of status register)
Bits: 0 - Command bit
7 - Data bit
Assigning them the same as that of the Status register of
the Speccy. For example, if Command bit is 1, then entered a
team and we ought to execute it.
Note: while working with register data
(Read / write), Data bit _avtomaticheski_
changes, in contrast to the Command bit! If the number sent to
the Spectrum in the register commands, the Command bit, as
expected, set to 1, but it does not reset when
reading his GS'om.
6. OUT (# 05), A (reset bit in the Command bit
status register) Usually this is given by
know the components that the team performed in the GS
(Or accepted for maintenance). Sends a port number can be
anything.
Primerchik (algorithmic) works with all of this:
> We wait until Command bit will not install in
1. We therefore wait until when to go Speck team.
> Take the value from the command register (team number).
Depending on this, we jump right place.
> ... Jump, let's here ... Must make it clear to Speck, the
team accepted that he is not lying in wait. Ie do OUT (# 05),
and then execute the desired prog. Or you can do differently,
if something you need to do, and then pass
the results of the components: first, all are doing,
pushes data through OUT (# 03), a
then do OUT (# 05). On Speccy be
will give the team number, wait for it
Implementation and boldly take the data from the Output
register. Generally, it is necessary to act logically, and if
you do OUT (# 05) before the OUT (# 03), the risk
take on the Spectrum is not the data. Well, yes
I think that such moments, and so must be clear ...
7. OUT (# 06), Volume (the volume channel A)
OUT (# 07), Volume (volume of the channel B)
OUT (# 08), Volume (volume of the channel C)
OUT (# 09), Volume (the volume channel D)
Volume specified by a number from 0 to
63 (# 3F).
Output to DAC:
He made a very original way: by
reading from memory. So, first we need to write a number in the
cell, and then read it from there. Channel (more precisely
DAC), depends on the address:
# 6000 - # 60FF - for channel A
# 6100 - # 61FF - for channel B
# 6200 - # 62FF - for channel C
# 6300 - # 63FF - for the channel D
# 6400 - # 64FF - for channel A
# 6500 - # 65FF - for channel B
# 6600 - # 66FF - for channel C
# 6700 - # 67FF - for the channel D
Etc. to # 7FFF.
This memory area can be conveniently
used a buffer, ie you first
quickly throw in her data, and then in
the right time to play.
Interrupt:
The frequency of the signal INT - 37500Gts, ie
Interrupt come 37.5 thousand times per second. They usually
hang proigryvalku muse, more precisely, prog, which throws the
data into TsAP'y.
Let us estimate 12000000/37500 = 320 cycles
interrupt. It's not so much, so
should expect that what hangs on
interrupts, it was not too long, otherwise the main prog is
very slow.
Practice
It will examine specific
Examples of programming GS-ASM'e.
1. Determination of GS.
; CY = 1 if the GS does not
GS_DET LD A, # 7F: IN A, (# FE): CPL; very good variant -
RRCA: RET C; forced off user'om.
LD A, # F3: OUT (# BB), A; restart GS
LD B, 200; time (ZX_INTs), during which the forward
; Response from the GS - 4 sec.
GS_DET1 EI: HALT: DI
IN A, (# BB): RRCA; look, took a GS team
IN A, (# 7B): JR NC, GS_DET2
; In a, (# 7b) is needed in order to prog do not fly at Penta;
gon'ah included with the ZX-LPRINT3, because GS if not, to
include; Xia ROM ZX-LPRINT3!
DJNZ GS_DET1
SCF: RET; GS did not respond
GS_DET2 XOR A: OUT (# BB), A; seq. stage - the team Reset Flags.
GS_DET3 EI: HALT: DI
IN A, (# BB): CP # 7E; check whether the folded flag ...
IN A, (# 7B): RET Z
DJNZ GS_DET3
SCF: RET; time is up, and no flags are folded
, Although ... if all of a sudden (# BB) is always read 0 (no
fee), then ; Test makes a mistake ... Then it would be
necessary (and it is necessary at all?), Add ; Tests, for
example, to get GS to return the value you specified , (Team #
10, port 3).
2. The loader programs in GS.
INITgs CALL DEINIT; restart GS, if not already done
LD HL, SUBPRG; address of our auxiliary loader
LD BC, # 0080; block length
LD A, C: CALL SD
LD A, # 14: CALL SC; load unit in the GS
LD A, B: CALL SD: CALL WD
XOR A: CALL SD: CALL WD; load address # 4000
LD A, # 40: CALL SD: CALL WD
LOOPiGS LD A, (HL): CALL SD: CALL WD; send block
INC HL: DEC BC
LD A, B: OR C: JR NZ, LOOPiGS
XOR A: CALL SD; run the program from the address # 4000
LD A, # 13: CALL SC
LD A, # 40: CALL SD
; And then our work boot, which will prepare everything for
; Download our software. Why such a distorted? First, when
; Works internally on a map, the stack was in the # 4400, and
since , # 4000 were all sorts of system variables of the
software. And our small, Lyonka loader rearrange the stack and,
if necessary, include nuzh; nuyu us page, etc.
LD HL, PROG; address and the length of the program
LD BC, EPRG-PROG_
LOOpiGS LD A, (HL): CALL SD: CALL WD; send block
INC HL: DEC BC
LD A, B: OR C: JR NZ, LOOpiGS
RET
; And here is the bootloader. Consider that it should work on
, At # 4000, so either it assembliruem respectively,
, Or do not use direct addressing (JP, CALL)
SUBPRG DI
LD SP, # 407F
LD HL, # 4080: PUSH HL; load address in the GS
LD C, 2 port
LD DE, EPRG-PROG_
SUBPRG1 IN A, (4): BIT 7, A: JR Z, SUBPRG1; waiting for the
arrival of bytes
INI: DEC DE; throw it into memory
LD A, D: OR E: JR NZ, SUBPRG1
RET
; Then run our prog. Template is:
PROG
ORG # 4080, $
PROG_ .....
.....
EPRG
; Org # 4080, $ - is the syntax STORM'a, and how it is done in
; Other assemblers, you know better. Meaning such that prog;
Ramm is physically located behind the main, but Assemblée;
rovana at address # 4080.
3. Search for free memory in the GS.
LD HL, # FFFF
LD A, # 0F; maximum of 15 pages ...
LPP0 OUT (0), A: LD (HL), A; page is written in her room
DEC A: CP 1: JR NZ, LPP0; all pages except the 1 st and
0 th
; You can, of course, and the 1 st count, but work with it are
not very ; Convenient (ala the 5 th Bank Speccy).
INC A
LD DE, PAGETAB; table of page numbers
LD IX, 0, we assume that the number of pages
LPP1 OUT (0), A
CP (HL): JR NZ, LPP2
LD (DE), A; if the page number coincides with the number
INC DE, HX; written in it, then puts it into a table
LPP2 INC A: BIT 6, A: JR Z, LPP1; 15-I - the last
; Further, if necessary, we can clean page. But it is desirable
, Note that pages can be a lot, and it would be better (if
memory serves ; Allows) to clear through PUSH.
.....
; You can send information on the number of pages Speck.
LPP3_ LD A, HX: OUT (3), A
OR A: JP Z, 0, if the pages do not, then RESET
IN A, (4): RLCA: JR C, $ -3; wait until he takes them
.....
PAGETAB DS 14
4. The main cycle and procedures.
; Several variants of the program. If you intend to
; A small number of commands, you can simply Paul; We used the
terms CP # XX: JR Z, NNNN. If the team a lot, ; Better sign up
with addresses.
GSMAIN1 IN A, (4): RRCA: JR NC, GSMAIN1; forward command
IN A, (1), take her number
LD HL, GSMAIN1: PUSH HL; on RET return to the cycle
OR A: JR Z, PROG0; command 0
CP 1: JR Z, PROG1; Team 1
CP 2: JP Z, PROG2; Team 2
CP # F3: JP Z, 0; standard RESET'y
CP # F4: JP Z, 0
And if the team does not, then do not do anything, but simply
give ; Spectrum to know that the team is accepted, and it hangs
in ; Anticipation
OUT (5), A
RET
; Option program when the first signal of the Spectrum, that all
, OK, and then execute the prog.
PROG0 OUT (5), A
.....
RET
; Option program when the first execute something, we
, Then sends the results to the Spectrum, and then the signal
that ; Prog executed.
PROG1 .....
OUT (5), A
RET
And A can be, and so vypendritsya.
PROG2 OUT (5), A
.....
IN A, (4): RRCA: JR NC, $ -3; waiting for the arrival
of any team
.....
OUT (5), A
RET
, Ie here is this: you send a command from the Spectrum # 2
; GS said that the team is accepted, something executes and
waits for, the progress of any team. Then again something
performs, and says ; That everything is ready. Obtained by a
trigger.
5. Upload / download data blocks.
a) Option 1 - briefly and clearly.
; Loading Data (GS side):
LD HL, # 8000; address in the GS
LD BC, # 4000; block length
WAITD IN A, (4): RLCA: JR NC, WAITD; waiting for bytes
IN A, (2): LD (HL), A: INC HL; accept
DEC C: JR NZ, WAITD; repeat
DEC B: JR NZ, WAITD
; Unloading (GS side):
LD HL, # 8000; address in the GS
LD BC, # 4000; block length
WAITD_1 LD A, (HL): OUT (3), A; send byte
WAITD_ IN A, (4): RLCA: JR C, WAITD_; waiting for adoption
INC HL
DEC C: JR NZ, WAITD_1; repeat
DEC B: JR NZ, WAITD_1
; Loading data into GS (ZX side):
LD HL, # 8000
LD BC, 0 - # 4000 (0 minus the length of the block)
GS1 LD A, (HL): OUT (# B3), A; throw
IN A, (# BB): RLCA: JR C, $ -3; waiting for adoption
INC C: JR NZ, GS1
INC B: JR NZ, GS1
; / Export data from the GS (ZX side):
LD HL, # 8000
LD BC, 0 - # 4000
GS2 IN A, (# BB): RLCA: JR NC, $ -3; waiting for admission
IN A, (# B3): LD (HL), A; accept
INC C: JR NZ, GS2
INC B: JR NZ, GS2
b) Option 2 - accelerated by opening cycles, but it must be
fixed block length.
; Loading Data (GS side):
LD HL, # 8000; address in the GS
LD E, 8; the number of cycles
LD C, 2 port
LOAD_2
.0 IN A, (4): RLCA: JP NC, $ -3: INI; repeat 256 (0) times
DEC E: JP NZ, LOAD_2; 256 * 8 = 2048 bytes take
; Loading data into GS (ZX side):
LD HL, # 8000
LD C, # B3; data port
LD E, 8; the number of cycles
LOAD_2_
.0 OUTI: IN A, (# BB): RLCA: JP C, $ -3; repeat 256 (0) times
DEC E: JP NZ, LOAD_2_; 2048 bytes give
; The meaning is clear - to unwind, thereby zhrem memory, but
; Slightly improves performance. The remaining procedures must
be ; Will write themselves ... By the way, the cycle of
expectation, too, can not, a lot of deployment:
And it was
IN A, (# BB): RLCA: JP C, $ -3; 11 +4 +10 = 25 cycles
; Was
LOOOP
IN A, (# BB): RLCA: JR NC, LOOOPE; 11 +4 +7 = 22 cycles
IN A, (# BB): RLCA: JR NC, LOOOPE
IN A, (# BB): RLCA: JR C, LOOOP
LOOOPE
; Because jr case of violation of conditions takes 7 clock
cycles, we ecosystems; nomim to 3 cycles per cycle. Ie survey
are more frequent.
c) And one more no less interesting way:
; Loading Data (GS side):
LD HL, # 8000; address
LD C, 1 Port
IN A, (4): RLCA: JP NC, $ -3;
INI; + repeat the required number. times
IN A, (2): LD (HL), A: INC HL; /
OUT (5), A; then to GS never thought that came to the
team
; Thus, the reception goes through 2 ports. Ie Port 1
; (# BB on ZX) is the first byte, and Port 2 (# B3) - the
second one.
; Loading data into GS (ZX side):
LD HL, # 8000; address
LD C, # BB; port
OUTI;
LD A, (HL): OUT (# B3), A: INC HL; + Rept. the required
number. times
IN A, (# BB): RLCA: JP C, $ -3; /
; Use this method to transfer data from GS is not, perhaps
because in this direction is only one port.
6. Sound output.
a) Playing a sample from memory without interruption.
LD HL, # 8000; address sample in GS
LD BC, # 8000; the length of the sample
LD DE, # 6000; address of the cell DAC (# 61XX, # 62XX,
# 63XX)
LOOP LD A, (HL); take a byte
LD (DE), A; stores each
LD A, (DE); throws in the DAC
.....; Some delay (EI: HALT or NOP'y??)
INC HL: DEC BC
LD A, B: OR C: JR NZ, LOOP
b) Playing a sample from memory using interrupts.
; Program hanging on interrupts might look like:
; DE = SMP_ADR
INT PUSH HL, AF
LD HL, # 6000; address TsAP'a
LD A, (DE): INC DE; take the next byte of the sample
LD (HL), A: LD A, (HL): INC H; D / A
LD (HL), A: LD A, (HL): INC H; D / B
LD (HL), A: LD A, (HL): INC H; D / C
LD (HL), A: LD A, (HL); D / D
POP AF, HL
EI: RET
; Naturally, the play will not be stopped - not the one on whom
the conditions, because this is just an example. And bad.
Interruption; of should not be too long, so it is advisable to
remove ; Out superfluous commands, and generally, to organize
work in g; rugomu. Make a memory location # 6000 - # 60FF, etc.
buffer in , Which we quickly cram information, and then they
will play , With his speed. And when the end, cram again!
; HL '= # 6000
INT EXX: EXA; EXA = EX AF, AF '
LD A, (HL); throws in the DAC
INC L: JR Z, FILLBUF; if the buffer was over ...
EXA: EXX
EI: RET
FILLBUF EXX: EXA
EI
FB1 LD HL, # 8000; address sample
LD BC, 256; buffer length
LD DE, # 6000; buffer address
LDIR
LD (FB1 +1), HL; keep track. Address
RET
; Despite the fact that FILLBUF called from Interrupts and
times;; solve them, nothing bad will happen if the catch all ,
Do not over until the buffer again. Also must find time ; Throw
the first byte before the break comes, otherwise ; Loser then
will click. Out of this situation , Is to create a 2-second
buffer, so long as one u; raetsya, fill the second.
; By the way, the sampling frequency in this case will 37500Gts,
, So if you have less, you'll need to modify the algorithm
; Supply frequency dividers (in FILLBUF), which will make a
, And the same byte repeated several times.
7. Debugging.
In the absence of a debugger
GS, written programs difficult to debug. But you can take
advantage of the fact that GS can play sound, track
thus the stage at which prog buggy.
We write the squeaker:
GSBEEP LD BC, # 0010, DE, # 6000
GSBEEP1 LD A, 120: LD (DE), A, A, (DE)
DJNZ $
LD A, 128: LD (DE), A, A, (DE)
DJNZ $
DEC C: JR NZ, GSBEEP1
; Here you can insert a pause, in case the two will beep Th;
cut short period of time
RET
Now you can navstavlyat in the program
appeals to GSBEEP and listen to how bipov until the crash ...
Find a bug in the algorithm by using the STS. Just need a
program for the GS placed in memory of ZX-Spectrum (for the same
addresses) and perform step by step, only the commands in a,
(xx) missing, and the battery manually enter the desired values
(out (Xx), a can just skip).
By the way, at this point has already appeared in a
low-level emulation GS Z80Stealth, there is a debugger, and so
that you can use it, though, here
just not very comfortable ...
Well, that's all for now. I hope I am clear
explained to all and showed it. Now anyone who knows the
assembler Z80, can easily deal with direct programming
GS. Who knows, may soon be a lot of
good programs under the GS? Nobody wants to
make a debugger for the Speccy to the nucleus in GS?
;-)))