SwiftLink-232 Application Notes Revision T v1.0.0 Introduction The SwiftLink-232 ACIA cartridge replaces the Commodore Kernal RS-232 routines with a hardware chip. The chip handles all the bit level processing now done in software by the Commodore Kernal. The ACIA may be accessed by polling certain memory locations in the I/O block ($D000 - $DFFF) or through interrupts. The ACIA may be programmed to generate interrupts in the following situations: 1) when a byte of data is received 2) when a byte of data may be transmitted (i. e. the data register is empty) 3) both (1) and (2) 4) never The sample code below sets up the ACIA to generate an interrupt each time a byte of data is received. For transmitting, two techniques are shown. The first technique consists of an interrupt handler which enables transmit interrupts when there are bytes ready to be sent from a transmit buffer. There is a separate routine given that manages the transmit buffer. In the second technique, which can be found at the very end of the sample code, neither a transmit buffer or transmit interrupts are used. Instead, bytes of data are sent to the ACIA directly as they are generated by the terminal program. Note: The ACIA will always generate an interrupt when a change of state occurs on either the DCD or DSR line (unless the lines are not connected in the device's cable). The 6551 ACIA was chosen for several reasons, including low cost and compatibility with other Commodore (MOS) integrated circuits. Commodore used the 6551 as a model for the Kernal software. Control, Command, and Status registers in the Kernal routines partially mimic their hardware counterparts in the ACIA. Note: If you're using the Kernal software registers in your program, be sure to review the enclosed 6551 data sheet carefully. Several of the hardware register locations do not perform the same functions as their software counterparts. You may need to make a few changes in your program to accommodate the differences. Buffers Bytes received are placed in "circular" or "ring" buffers by the sample receive routine below, and also by the first sample transmit routine. To keep things similar to the Kernal RS-232 implementation, we've shown 256 byte buffers. You may want to use larger buffers for file transfers or to allow more screen processing time. Bypassing the Kernal routines frees many zero page locations, which could improve performance of pointers to larger buffers. If your program already directly manipulates the Kernal RS-232 buffers, you'll find it very easy to adapt to the ACIA. If you use calls to the Kernal RS-232 file routines instead, you'll need to implement lower level code to get and store buffer data. Briefly, each buffer has a "head" and "tail" pointer. The head points to the next byte to be removed from the buffer. The tail points to the next free location in which to store a byte. If the head and tail both point to the same location, the buffer is empty. If (tail + 1) = head, the buffer is full. The interrupt handler described below will place received bytes at the tail of the receive buffer. Your program should monitor the buffer, either by comparing the head and tail pointers (as the Commodore Kernal routines do), or by maintaining a byte count through the interrupt handler (as the attached sample does). When bytes are available, your program can process them, move the head pointer to the next character, and decrement the counter if you use one. You should send a "Ctrl-S" (ASCII 19) to the host when the buffer is nearing capacity. At higher baud rates, this "maximum size" point may need to be lowered. We found 50 to 100 bytes worked fairly well at 9600 baud. You can probably do things more efficiently (we were using a very rough implementation) and set a higher maximum size. At some "minimum size", a "Ctrl-Q" (ASCII 17) can be sent to the host to resume transmission. To transmit a byte using the logic of the first transmit routine below, first make sure that the transmit buffer isn't full. Then store the byte at the tail of the transmit buffer, point the tail to the next available location, and increment the transmit buffer counter (if used). The 6551 transmit interrupt occurs when the transmit register is empty and available for transmitting another byte. Unless there are bytes to transmit, this creates unnecessary interrupts and wastes a lot of time. So, when the last byte is removed from the buffer, the interrupt handler in the first transmit routine below disables transmit interrupts. Your program's code that stuffs new bytes into the transmit buffer must re-enable transmit interrupts, or your bytes may never be sent. A model for a main code routine for placing bytes in the transmit buffer follows the sample interrupt handler. Using a transmit buffer allows your main program to perform other tasks while the NMI interrupt routine takes care of sending bytes to the ACIA. If the buffer has more than a few characters, however, you may find that most of the processor time is spent servicing the interrupt. Since the ACIA generates NMI interrupts, you can't "mask" them from the processor, and you may have timing difficulties in your program. One solution is to eliminate the transmit buffer completely. Your program can decide when to send each byte and perform any other necessary tasks in between bytes as needed. A model for a main code routine for transmitting bytes without a transmit buffer is also shown following the sample interrupt handler code. Feedback from developers to date is that many of them have better luck not using transmit interrupts or a transmit buffer. Although it's possible to eliminate the receive buffer also, we strongly advise that you don't. The host computer, not your program, decides when a new byte will arrive. Polling the ACIA for received bytes instead of using an interrupt-driven buffer just wastes your program's time and risks missing data. For a thorough discussion of the use of buffers, the Kernal RS-232 routines, and the Commodore NMI handler, see "COMPUTE!'s VIC-20 and Commodore 64 Tool Kit: Kernal", by Dan Heeb (COMPUTE! Books) and "What's Really Inside the Commodore 64", by Milton Bathurst (distributed in the US by Schnedler Systems). ACIA Registers The four ACIA registers are explained in detail in the enclosed data sheets. The default location for them in our cartridge is addresses $DE00 - $DE03 (56832 - 56836). Data Register ($DE00) This register has dual functionality: it is used to receive and transmit all data bytes (i.e. it is a read/write register). Data received by the ACIA is placed in this register. If receive interrupts are enabled, an interrupt will be generated when all the bits for a received byte have been assembled and the byte is ready to read. Transmit interrupts, if enabled, are generated when this register is empty (available for transmitting). A byte to be transmitted can then be placed in this register. Status Register ($DE01) This register has dual functionality: it show the status of various ACIA settings when read, but when written to (data = anything [i.e. don't care]), this register triggers a reset of the chip. As the enclosed data sheet shows (Figure 8), the ACIA uses bits in this register to indicate data flow and errors. If the ACIA generates an interrupt, bit #7 is set. There are four possible sources of interrupts: 1) receive (if programmed) 2) transmit (if programmed) 3) if a connected device changes the state of the DCD line 4) if a connected device changes the state of the DSR line Some programmers have reported problems with using bit #7 to verify ACIA interrupts. At 9600 bps and higher, the ACIA generates interrupts properly, and bits #3-#6 (described below) are set to reflect the cause of the interrupt, as they should. But, bit #7 is not consistently set. At speeds under 9600 bps, bit #7 seems to work as designed. To avoid any difficulties, the sample code below ignores bit #7 and tests the four interrupt source bits directly. Bit #5 indicates the status of the DSR line connected to the RS-232 device (modem, printer, etc.), while bit #6 indicates the status of the DCD line. Note: The function of these two bits is reversed from the standard implementation. Unlike many ACIA's, the 6551 was designed to use the DCD (Data Carrier Detect) signal from the modem to activate the receiver section of the chip. If DCD is inactive (no carrier detected), the modem messages and echoes of commands would not appear on your user's screen. We wanted the receiver active at all times. We also wanted to give you access to the DCD signal from the modem. So, we exchanged the DCD and DSR signals at the ACIA. Both lines are pulled active internally by the cartridge if left unconnected by the user (i.e., in a null-modem cable possibly). Bit #4 is set if the transmit register is empty. Your program must monitor this bit and not write to the data register until the bit is set (see the sample XMIT code below). Bit #3 is set if the receive register is full. Bits #2, #1, and #0, when set, indicate overrun, framing, and parity errors in received bytes. The next data byte received erases the error information for the preceding byte. If you wish to use these bits, store them for processing by your program. The sample code below does not implement any error checking, but the Kernal software routines do, so adding these features to your code might be a good idea. Command Register ($DE02) The Command register (Figure 6) controls parity checking, echo mode, and transmit/receive interrupts. It is a read/write register, but reading the register simply tells you what the settings of the various parameters are. You use bits #7, #6, and #5 to choose the parity checking desired. Bit #4 should normally be cleared (i.e. no echo). Bits #3 and #2 should reflect whether or not you are using transmit interrupts, and if so, what kind. In the first sample transmit routine below, bit #3 is set and bit #2 is cleared to disable transmit interrupts (with RTS low [active]) on startup. However, when a byte is placed in the transmit buffer, bit #3 is cleared and bit #2 is set to enable transmit interrupts (with RTS low). When all bytes in the buffer have been transmitted, the interrupt handler disables transmit interrupts. Note: If you are connected to a RS-232 device that uses CTS/RTS handshaking, you can tell the device to stop temporarily by bringing RTS high (inactive): clear both bits #2 and #3. Bit #1 should reflect whether or not you are using receive interrupts. In the sample code below, it is set to enable receive interrupts. Bit #0 acts as a "master control switch" for all interrupts and the chip itself. It must be set to enable any interrupts-- if it is cleared, all interrupts are turned off and the receiver section of the chip is disabled. This bit also pulls the DTR line low to enable communication with the connected RS-232 device. Clearing this bit causes most Hayes- compatible modems to hang up (by bringing DTR high). This bit should be cleared when a session is over and the user exits the terminal program to ensure no spurious interrupts are generated. One fairly elegant way to do this is to perform a software reset of the chip (writing any value to the Status register). Note: In Figures 6, 7, and 8 on the 6551 data sheet, there are small charts at the bottom of each that are labeled "Hardware Reset/Program Reset". These charts indicate what values the bits of these registers contain after a hardware reset (like toggling the computer's power) and a program reset (a write to the Status register). Control Register ($DE03) You use this register to control the number of stop bits, the word length, switch on the internal baud rate generator, and set the baud rate. It is a read/write register, but reading the register simply tells you what the settings of the various parameters are. See Figure 7 of the data sheet for a complete list of the parameters. Be sure that bit #4, the "clock source" bit, is always set to use the on-chip crystal-controlled baud rate generator. You use the other bits to choose the baud rate, word length, and number of stop bits. Note that our cartridge uses a double-speed crystal, so the values given in Figure 7 should be doubled (i.e. the minimum speed is 100 bps and the maximum speed is 38,400 bps). ACIA Hardware Interfacing The ACIA is mounted on a circuit board designed to plug into the expansion (cartridge) port. The board is housed in a cartridge shell with a male DB-9 connector at the rear. The "IBMN PC/ATTM standard" DB- 9 RS-232 pinout is implemented. Commercial DB-9 to DB-25 patch cords are readily available, and are sold by us as well. Eight of the nine lines from the AT serial port are implemented: TxD, RxD, DTR, DSR, RTS, CTS, DCD, & GND. RI (Ring Indicator) is not implemented because the 6551 does not have a pin to handle it. CTS and RTS are not normally used by 2400 bps or slower Hayes-compatible modems, but these lines are being used by several newer, faster modems (MNP modems in particular). Note that although CTS is connected to the 6551, there is no way to monitor what its state is-- the value does not appear in any register. The 6551 handles CTS automatically: if it is pulled high (inactive) by the connected RS-232 device, the 6551 stops transmitting (clears the "transmit data register empty" bit [#4] in the status register). The output signals are standard RS-232 level compatible. We've tested units with several commercial modems and with various computers using null modem cables at up to 38,400 bps without difficulties. In addition, there are pull-up resistors on three of the four input lines (DCD, DSR, CTS) so that if these pins are not connected in a cable, those three lines will pull to the active state. For example, if you happen to use a cable that is missing the DCD line, the pull-up resistor would pull the line active, so that bit #6 in the status register would be cleared (DCD is active low). An on-board crystal provides the baud rate clock signal, with a maximum of 38.4 K baud, because we are using a double-speed crystal. If possible, test your program at 38.4Kb as well as lower baud rates. Users may find this helpful for local file transfers or using the C-64/C-128 as a dumb terminal on larger systems. And, after all, low cost 19.2 Kb modems for the masses are just around the corner. Default decoding for the ACIA addresses is done by the I/O #1 line (pin 7) on the cartridge port. This line is infrequently used on either the C-64 or C-128 and should allow compatibility with most other cartridge products, including the REU. The circuit board also has pads for users with special needs to change the decoding to I/O #2 (pin 10). This change moves the ACIA base address to $DF00, making it incompatible with the REU. C-128 users may also elect to decode the ACIA at $D700 (this is a SID chip mirror on the C-64). Since a $D700 decoding line is not available at the expansion port, the user would need to run a clip lead into the computer and connect to pin 12 of U3 (a 74LS138). We have tried this and it works. $D700 is an especially attractive location for C-128 BBS authors, because putting the SwiftLink there will free up the other two memory slots for devices that many BBS sysops use: IEEE and hard drive interfaces. Although we anticipate relatively few people changing ACIA decoding, you should allow your software to work with a SwiftLink at any of the three locations. You could either (1) test for the ACIA automatically by writing a value to the control register and then attempting to read it back or (2) provide a user-configurable switch/ poke/menu option. The Z80 CPU used for CP/M mode in the C-128 is not connected to the NMI line, which poses a problem since the cleanest software interface for C-64/C-128 mode programming is with this interrupt. We have added a switch to allow the ACIA interrupt to be connected to either NMI or IRQ, which the Z80 does use. The user can move this switch without opening the cartridge. Sample Code ;Sample NMI interrupt handler for 6551 ACIA on Commodore 64/128. ;(c) 1990 by Noel Nyman, Kent Sullivan, Brian Minugh, ;Geoduck Developmental Systems, and Dr. Evil Labs. ; ---=== EQUATES ===--- base = $DE00 ;base ACIA address data = base status = base+1 command = base+2 control = base+3 ;Using the ACIA frees many addresses in zero page normally used by ;Kernal RS-232 routines. The addresses for the buffer pointers were ;chosen arbitrarily. The buffer vector addresses are those used by ;the Kernal routines. rhead = $A7 ;pointer to next byte to be removed from ;receive buffer rtail = $A8 ;pointer to location to store next byte received rbuff = $F7 ;receive buffer vector thead = $A9 ;pointer to next byte to be removed from ;transmit buffer ttail = $AA ;pointer to location to store next byte ;in transmit buffer tbuff = $F9 ;transmit buffer xmitcount = $AB ;count of bytes remaining in transmit (xmit) buffer recvcount = $B4 ;count of bytes remaining in receive buffer errors = $B5 ;DSR, DCD, and received data errors information xmiton = $B6 ;storage location for model of command register ;which turns both receive and transmit ;interrupts on xmitoff = $BD ;storage location for model of command register ;which turns the receive interrupt on and the ;transmit interrupts off NMINV = $0318 ;Commodore Non-Maskable Interrupt vector ; ---=== INITIALIZATION ===--- ;Call the following code as part of system initialization. ;clear all buffer pointers, buffer counters, and errors location lda #$00 sta rhead sta rtail sta thead sta ttail sta xmitcount sta recvcount sta errors ;store the addresses of the buffers in the zero page vectors lda #TRANSMIT.BUFFER sta tbuff + 1 lda #RECEIVE.BUFFER sta rbuff + 1 ;The next four instructions initialize the ACIA to arbitrary values. ;These could be program defaults, or replaced by code that picks up ;the user's requirements for baud rate, parity, etc. ;The ACIA "control" register controls stop bits, word length, the ;choice of internal or external baud rate generator, and the baud ;rate when the internal generator is used. The value below sets the ;ACIA for one stop bit, eight bit word length, and 4800 baud using the ;internal generator. ; .------------------------- 0 = one stop bit ; : ; :.-------------------- word length, bits 6-7 ; ::.------------------- 00 = eight bit word ; ::: ; :::.------------ clock source, 1 = internal generator ; :::: ; :::: .----- baud ; :::: :.---- rate ; :::: ::.--- bits ; :::: :::.-- 0-3 lda #%0001 1010 sta control ;The ACIA "command" register controls the parity, echo mode, transmit ;and receive interrupt enabling, hardware "BRK", and (indirectly) the ;"RTS" and "DTR" lines. The value below sets the ACIA for no parity ;check, no echo, disables transmit interrupts, and enables receive ;interrupts (RTS and DTR low). ; .------------------------- parity control, ; :.------------------------ bits 5-7 ; ::.----------------------- 000 = no parity ; ::: ; :::.----------------- echo mode, 0 = normal (no echo) ; :::: ; :::: .------------ transmit interrupt control, bits 2-3 ; :::: :.----------- 10 = xmit interrupt off, RTS low ; :::: :: ; :::: ::.------- receive interrupt control, 0 = enabled ; :::: ::: ; :::: :::.--- DTR control, 1 = DTR low lda #%0000 1001 sta command ;Besides initialization, also call the following code whenever the user ;changes parity or echo mode. ;It creates the "xmitoff" and "xmiton" models used by the interrupt ;handler and main program transmit routine to control the ACIA ;interrupt enabling. If you don't change the models' parity bits, ;you'll revert to "default" parity on the next NMI. ;initialize with transmit interrupts off since ;buffer will be empty sta xmitoff ;store as a model for future use and #%1111 0000 ;mask off interrupt bits, keep parity/echo bits ora #%0000 0101 ;and set bits to enable both transmit and ;receive interrupts sta xmiton ;store also for future use ;The standard NMI routine tests the key, CIA #2, and checks ;for the presence of an autostart cartridge. ;You can safely bypass the normal routine unless: ; * you want to keep the user port active ; * you want to use the TOD clock in CIA #2 ; * you want to detect an autostart cartridge ; * you want to detect the RESTORE key ; ;If you need any of these functions, you can wedge the ACIA ;interrupt handler in ahead of the Kernal routines. It's probably ;safer to replicate in your own program only the Kernal NMI functions ;that you need. We'll illustrate bypassing all the Kernal tests. ;BE SURE THE "NEWNMI" ROUTINE IS IN PLACE BEFORE EXECUTING THIS CODE! ;A "stray" NMI that occurs after the vector is changed to NEWNMI's ;address will probably cause a system crash if NEWNMI isn't there. Also, ;it would be best to initialize the ACIA to a "no interrupts" state ;until the new vector is stored. Although a power-on reset should ;disable all ACIA interrupts, it pays to be sure. ;If the user turns the modem off and on, an interrupt will probably be ;generated. At worst, this may leave a stray character in the receive ;buffer, unless you don't have NEWNMI in place. NEWVEC sei ;A stray IRQ shouldn't cause problems ;while we're changing the NMI vector, but ;why take chances? ;If you want all the normal NMI tests to occur after the ACIA check, ;save the old vector. If you don't need the regular stuff, you can ;skip the next four lines. Note that the Kernal NMI routine pushes ;the CPU registers to the stack. If you call it at the normal address, ;you should pop the registers first (see EXITINT below). ; lda NMINV ;get low byte of present vector ; sta OLDVEC ;and store it for future use ; lda NMINV+1 ;do the same ; sta OLDVEC+1 ;with the high byte ;come here from the SEI if you're not saving ;the old vector lda #$NEWNMI ;and do the same with sta NMINV+1 ;the high byte cli ;allow IRQs again ;continue initializing your program ::: ::::::: ;program initialization continues ;Save two bytes to store the old vector only if you need it OLDVEC ; dfb 2 ;reserve two bytes to hold old NMI vector ; ---=== New NMI Routine Starts Here ===--- ;The code below is a sample interrupt patch to control the ACIA. When ;the ACIA generates an interrupt, this routine examines the status ;register which contains the following data. ; .------------------ high if ACIA caused interrupt; ; : not used in code below ; : ; :.---------------- reflects state of DCD line ; :: ; ::.-------------- reflects state of DSR line ; ::: ; :::.------------ high if xmit data register is empty ; :::: ; :::: .--------- high if receive data register full ; :::: : ; :::: :.------- high if overrun error ; :::: :: ; :::: ::.----- high if framing error ; :::: ::: ; :::: :::.--- high if parity error ; status xxxx xxxx NEWNMI ; sei ;the Kernal routine already does this before ;jumping through the NMINV vector pha ;save A register txa pha ;save X register tya pha ;and save Y register ;As discussed above, the ACIA can generate an interrupt from one of four ;different sources. We'll first check to see if the interrupt was ;caused by the receive register being full (bit #3) or the transmit ;register being empty (bit #4) since these two activities should receive ;priority. A BEQ (Branch if EQual) tests the status register and ;branches if the interrupt was not caused by the data register. ;Before testing for the source of the interrupt, we'll prevent more ;interrupts from the ACIA by disabling them at the chip. This prevents ;another NMI from interrupting this one. (SEI won't work because the ;CPU can't disable non-maskable interrupts.) ;At lower baud rates (2400 baud and lower) this may not be necessary. ;But, it's safe and doesn't take much time, either. ;The same technique should be used in any parts of your program where ;timing is critical. Disk access, for example, uses SEI to mask IRQ ;interrupts. You should turn off the ACIA interrupts during disk access ;also to prevent disk errors and system crashes. ;First, we'll load the status register which contains all the interrupt ;and any received data error information in the 'A' register. lda status ;Now prevent any more NMIs from the ACIA ldx #%0000 0011 ;disable all interrupts, bring RTS inactive, ;and leave DTR active stx command ;send to ACIA-- code at end of interrupt ;handler will re-enable interrupts ;Store the status register data only if needed for error checking. ;The next received byte will clear the error flags. ; sta errors ;only if error checking implemented and #%0001 1000 ;mask out all but transmit and ;receive interrupt indicators ;If you don't use a transmit buffer you can use ; ; and #%0000 1000 ; ;to test for receive interrupts only and skip the receive test shown ;below. beq TESTDCDDSR ;if the 'A' register=0, either the interrupt was not caused by the ;ACIA, or the ACIA interrupt was caused by a change in the DCD or ;DSR lines, so we'll branch to check those sources. ;If your program ignores DCD and DSR, you can branch to ;the end of the interrupt handler instead: ; ; beq NMIEXIT ; ;Test the status register information to see if a received byte is ready ;If you don't use a transmit buffer, skip the next two lines. RECEIVE ;process received byte and #%0000 1000 ;mask all but bit #3 beq XMITCHAR ;if not set, no received byte - if you're ;using a transmit buffer, the interrupt must ;have been caused by transmit. So, branch to ;handle. lda data ;get received byte ldy rtail ;index to buffer sta (rbuff),y ;and store it inc rtail ;move index to next slot inc recvcount ;increment count of bytes in receive buffer ;(if used by your program) ;Skip the "XMIT" routines below if you decide not to use a transmit ;buffer. In that case, the next code executed starts at TESTDCDDSR or ;NMIEXIT. ;After processing a received byte, this sample code tests for bytes ;in the transmit buffer, and sends one if present. Note that, in this ;sample, receive interrupts take precedence. You may want to reverse the ;order in your program. ;If the ACIA generated a transmit interrupt and no received byte was ;ready, status bit #4 is already set. The ACIA is ready to accept ;the byte to be transmitted and we've branched directly to XMITCHAR ;below. ;If only bit #3 was set on entry to the interrupt handler, the ACIA may ;have been in the process of transmitting the last byte, and there may ;still be characters in the transmit buffer. We'll check for that now, ;and send the next character if there is one. Before sending a character ;to the ACIA to be transmitted, we must wait until bit #4 of the status ;register is set. XMIT lda xmitcount ;if not zero, characters still in buffer ;fall through to process xmit buffer beq TESTDCDDSR ;no characters in buffer-- go to next check ;or ; ; beq NMIEXIT ; ;if you don't check DCD or DSR in your program. XMITBYTE lda status ;test bit #4 and #%00010000 beq XMITBYTE ;wait for bit #4 to be set XMITCHAR ;transmit a character ldy thead lda (tbuff),y ;get character at head of buffer sta data ;place in ACIA for transmit ;point to next character in buffer inc thead ;and store new index dec xmitcount ;subtract one from count of bytes ;in xmit buffer lda xmitcount beq TESTDCDDSR ;or ; ; beq NMIEXIT ; ;if you don't check DCD or DSR in your program ;If xmitcount decrements to zero, there are no more characters to ;transmit. The code at NMIEXIT turns ACIA transmit interrupts off. ;If there are more bytes in the buffer, set up the 'A' register with ;the model that turns both transmit and receive interrupts on. We felt ;that was safer, and not much slower, than EORing bits #3 and #4. Note ;that the status of the parity/echo bits is preserved in the way ;"xmiton" and "xmitoff" were initialized earlier. lda xmiton ;model to leave both interrupts enabled ;If you don't use DCD or DSR bne NMICOMMAND ;branch always to store model in command ;register ;If your program uses DCD and/or DSR, you'll want to know when the state ;of those lines changes. You can do that outside the interrupt handler ;by polling the ACIA status register, but if either of the lines ;changes, the chip will generate an NMI anyway. So, you can let the ;interrupt handler do the work for you. The cost is the added time ;required to execute the DCRDSR code on each NMI. TESTDCDDSR ; pha ;only if you use a transmit buffer, 'A' holds ;the proper mask to re-enable interrupts on ;the ACIA :: :: ;appropriate code here to compare bit #6 (DCD) :: ;and/or bit #5 (DSR) with their previous states :: ;which you've already stored in memory and take :: ;appropriate action :: ; pla ;only if you pushed it at the start of the ; ;DCD/DSR routine ; bne NMICOMMAND ;'A' holds the xmiton mask if it's not zero, ;implying that we arrived here from xmit ;routine not used if you're not using a ;transmit buffer. ;If the test for ACIA interrupt failed on entry to the handler, we ;branch directly to here. If you don't use additional handlers, the ;RESTORE key (for example) will fall through here and have no effect on ;your program or the machine, except for some wasted cycles. NMIEXIT lda xmitoff ;load model to turn transmit interrupts off ;and this line sets the interrupt status to whatever is in the 'A' ;register. NMICOMMAND sta command ;That's all we need for the ACIA interrupt handler. Since we've pushed ;the CPU registers to the stack, we need to pop them off. Note that we ;must do this EVEN IF YOU JUMP TO THE KERNAL HANDLER NEXT, since it will ;push them again immediately. You can skip this step only if you're ;proceeding to a custom handler. EXITINT ;restore things and exit pla ;restore 'Y' register tay pla ;restore 'X' register tax pla ;restore 'A' register ;If you want to continue processing the interrupt with the Kernal ;routines, ; jmp (OLDVEC) ;continue processing interrupt with Kernal ;handler ;Or, if you add your own interrupt routine, ; jmp YOURCODE ;continue with your own handler ;If you use your own routine, or if you don't add anything, BE SURE to ;do this last: rti ;return from interrupt instruction ;to restore the flags register the CPU pushes to the stack before ;jumping to the Kernal code. It also returns you to the interrupted part ;of your program. ------------------------------------------------------------------------ ;Sample routine to store a character in the buffer to be transmitted ;by the ACIA. ;(c) 1990 by Noel Nyman, Kent Sullivan, Brian Minugh, ;Geoduck Developmental Systems, and Dr. Evil Labs. ;Assumes the character is in the 'A' register on entry. Destroys 'Y'-- ;push to stack if you need to preserve it. SENDBYTE ;adds a byte to the xmit buffer and sets ;the ACIA to enable transmit interrupts (the ;interrupt handler will disable them again ;when the buffer is empty) lda xmitcount ;count of bytes in transmit buffer cmp 255 ;max buffer size beq NOTHING ;buffer is full, don't add byte ldy ttail ;pointer to end of buffer sta (tbuff),y ;store byte in 'A' at end of buffer inc ttail ;point to next slot in buffer inc xmitcount ;and add one to count of bytes in buffer lda xmiton ;get model for turning on transmit interrupts sta command ;and tell ACIA to do it rts ;return to your program NOTHING lda #$00 ;or whatever flag your program uses to tell that ;the byte was not transmitted rts ;and return ------------------------------------------------------------------------ ;Sample routine to transmit a character from main program when not ;using a transmit buffer. ;(c) 1990 by Noel Nyman, Kent Sullivan, Brian Minugh, ;Geoduck Developmental Systems, and Dr. Evil Labs. ;Assumes the character to be transmitted is in the 'A' register on ;entry. Destroys 'Y', push to stack if you need to preserve it. SENDBYTE tay ;remember byte to be transmitted TESTACIA lda status ;bit #4 of the status register is set if ;the ACIA is ready to transmit another byte, ;even if transmit interrupts are disabled and #%0001 0000 beq TESTACIA ;wait for bit #4 to be set sty data ;give byte to ACIA rts ;and exit ÿÿÿ