Adventurer #13
31 марта 2002 |
|
Exchange of experience - Direct Programming General Sound.
(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? ;-)))
Other articles:
Similar articles:
В этот день... 21 November