PARALLEL BUS DEVICES -------------------- By BEWESOFT (Jiri Bernasek) Once (few years ago) I was in a quite strange situation: I did have EPROM 16kB, but I didn't have any great use for it! And so I decided to make a ROM-DISK. My bootable ROM-DISK with SpartaDOS wore e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e r ago in Czech... HARDWARE The hardware isn't too difficult. There is the area $D800-$DFFF available for a ROM with software, and $D1xx for I/O ports. The device is "switched on" by a bit in $D1FF port (called "our bit" in this article) - if in our bit is zero, the expansion must be inactive except the $D1FF port. When the expansion wants to generate an interrupt, it need to hold the /IRQ signal at "zero" state for a while, and then give "1" in our bit while reading from $D1FF. Once we used an interrupt, we must give "0" in our bit at $D1FF in every other cases. If I add the note that you need to hold the /MPD and /EXTSEL signals at "zero" state while our new ROM is active, it'll be easy to understand for hardware freax, I think. SOFTWARE Ok, non-hardware freaks probably waits for some software - let's go on... If we'll set 'our bit' at $D1FF to "1", there will be our new ROM at $D800-$DFFF (instead of Math-ROM or RAM), and at $D1xx we'll see our I/O ports now. Because the O.S. does support those expansions (unfortunately Q-MEG doesn't) we only need to place a little header at $D800, and a handler for the new device into the rest of our ROM. The header is this: Adress Length Function ====================================== $D800 2 Checksum (unused) $D802 1 Version number (unused) $D803 1 $80 - Identification $D804 1 Device type (unused) $D805 3 JMP SIO routine $D808 3 JMP IRQ routine $D80B 1 $91 - Identification $D80C 1 Device name (unused) $D80D 12 Common CIO-handler table $D819 3 JMP INIT routine ====================================== After every RESET the O.S. does try to read ROMs in every parallel devices. And if both the identification-bytes in the header are right, the O.S. will do this: JSR $D819!!! And what we need to do in the INIT routine which is there? Here is an example: INIT LDA $247 ;Update the list of ORA $248 ;active parallel STA $247 ;devices ;And if there is a CIO-handler: LDX $D80C ;Insert the handler LDA #$E4 ;into HATABS... LDY #$8F JSR $E486 ;And of course... RTS How it works... At $247 there is a list of every parallel devices active at the moment. And we'll simply set our bit there! Note that O.S. allways places the same value at $D1FF and $248, so the routine listed above will work correctly if we changed which 'our bit' is (in the hardware) too. If we'll use a CIO-handler, we need to insert him into HATABS. The routine at $E486 will do it for us. But we can't use the real adress of handler-table ($D80D) - we must use $E48F. (The table at $E48F points to a routine, which will turn our ROM "on" first, and then call the actual handler in it.) CIO HANDLER The table of CIO-handler (which is at $D80D) is the same as a common CIO-handler table. But the routines can't be the same. Why? Let's imagine this: We have two expansions, both with a CIO-handler. And as I said above, both those entries in HATABS points to the same adress: $E48F. So, how can the O.S. know, which expansion should be used??? The answer is: The O.S. really doesn't know!!! And what will happen then? The O.S. will simply call every expansions! So, we must check the name of a device first in every routines. And if the name is false, we'll send complaints to the O.S. by setting C-flag to zero before RTS. Here is an example: OPEN LDY $20 ;Get a device-code LDA $31A,Y ;and the name CMP $D80C ;Is it our device? BEQ OPEN2 CLC ;No! It isn't!! RTS ;Let's complain!!! OPEN2 .... ;The actual .... ;routine... SEC ;OK, we did our RTS ;job... Note that if there is no CIO-handler in our expansion, every adresses in the handler-table MUST point to this "routine": NONE CLC ;We'll complain RTS ;allways! SIO ROUTINE If somebody did call the SIO at $E459, the O.S. will jump to our "SIO" routine first! So, we can do the operation ourselves (with a high speed SIO, with a hard drive on our ports, or whatever...), or we can complain (if is it a printer operation for example) exactly the same way as we did in a CIO-handler. Note that we can't change anything in the $30x table! And we also need to place at least CLC, RTS instead of our SIO, if there isn't any... INTERRUPT To use an interrupt from a parallel device, we need to enable it first by setting our bit to "1" at $249. When the hardware did the interrupt operation described above, the O.S. will jump to $D808. In the interrupt routine we needn't to complain, because the O.S. does know which expansion is responsible for the interrupt. Note that the O.S. will call our IRQ routine as a subroutine, so we have to use RTS at its end - no RTI please! We also can't change the value in Y register! So, the routine will look like this: IRQ TYA PHA ...... ;The actual IRQ routine... PLA TAY RTS This interrupt is IRQ, so it only works if no program changed the adresses $216, $217, $238, $239, and if no program used the SEI instruction! And now a message to EVERY programmers: To make your products compatible with parallel devices, please DON'T place any DLI or VBI routine, DL, Charset etc. into $D800-$DFFF area while loading!!! Especially the problems with DLI and VBI routines are really fatal - I think that you can imagine what will happen, if there will be an unknown ROM instead of the RAM with your DLI for a while!!! This is probably the case of The Big Demo (from HTT) - I must totally remove my expansion to get this great demo working... Did somebody read this article? Hmm, I can be quite satisfied because at least mr. Editor and you did! Editor's note: Huh? Mr.Editor and you? I am Mr.Editor, so who am I? Jeezz.. isn't this a paradox?