SwiftLink-232 Application Notes (version 1.1) This information is made available from a paper document published by CMD, with CMD's express written permission. [This version includes a couple of grammatical corrections and minor changes, plus, the source code has been debugged and extended.] 1. 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 the 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 function as their software counterparts. You may need to make a few changes in your program to accommodate the differences. 2. BUFFERS Bytes received are placed in "circular" or "ring" buffers by the sample 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 free many zero-page locations, which could improve performance of pointers to large 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 "maximum 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 into the transmit buffer follows the sample interrupt handler. Using a transmit buffer allows your main program to perform other takes 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 the 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 waste's 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). 3. ACIA REGISTERS The four ACIA registers are explained in detail in the enclosed data sheets. The default location for them in our cartridge is address $DE00--$DE03 (56832--56836). 3.1. 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 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 be placed in this register. 3.2. STATUS REGISTER ($DE01) This register has dual functionality: it shows the 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, 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 ACIAs, 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 echos of commands would not appear on the user's screen. We wanted the receiver active at all times. We also wanted to give the 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 an null-modem cable possibility). 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 i sset (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 features to your code might be a good idea. 3.3. COMMAND REGISTER ($DE02) The Command Register control 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 on 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 insure that 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 the figures on the 6551 data sheet, there are small charts at the bottom of each of the labelled "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). 3.4. 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 various parameters are. See the figure in the data sheet for a complete list of 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 values given on the data sheet are doubled [this is how they are shown below] (the minimum speed is 100 bps and the maximum speed is 38,400 bps). 4. 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 "IBM(R) PC/AT(TM) 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 state it 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 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 will 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 Kbaud, because we are using a double-speed crystal. If possible, test your program at 38.4 Kbaud as well as lower baud rates. Users may find this helpful for local file transfers using the C-64/C-128 as a dumb terminal on larger systems. And, after all, low-cost 28.8 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. [NOTE: this paragraph originally read "pin 12 of U2 (a 74LS138)", but I was told that is should be changed. I don't know myself which is correct --CSB]. 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. 5. SAMPLE CODE This section has been translated into ACE-assembler format. Cut on the dotted lines to extract the code, and assemble it using the ACE assembler (ACE is a public-domain program). This program will work on both the C64 and C128. To use from BASIC: LOAD"SAMPLE",8,1 SYS8192 It is a very simple terminal program. Press the STOP key to exit from it. %%%---8<---cut-here---8<---%%% ;Sample NMI interrupt handler for 6551 ACIA on Commodore 64/128 ;(c) 1990 by Noel Nyman, Kent Sullivan, Brian Minugh, ;Geoduck Development 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 ;Kernel 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 turn 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 OLDVEC = $03fe ;innocuous location to store old NMI vector (two bytes) ; ---=== INITIALIZATION ===--- ;Call the following code as part of system initialization. ;clear all buffer pointers, buffer counters, and errors location org $2000 ;change to suit your needs 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 ;1010 == 4800 baud, change to what you want ; :::: :::.-- 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 of 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 th 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 RESTOR 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 Kernal tests. ;BE SURE THE "NEWNMI" ROUTINE IS IN PLACE BEFORE EXITING 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 teh receive ;buffer, unless you don't have NEWNMI in place. NEWVEC: sei ;A stray IRQ shouldn't cause any 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 jmp TERMINAL ;go to the example dumb-terminal subroutine ;Save two bytes to store the old vector only if you need it ; ---=== New NMI Routine Starts Here ===--- ;The code below is a simple 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 ;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 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 TEST_DCD_DSR ;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 instructions. 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 TEST_DCD_DSR or NMIEXIT. ;After processing a received byte, this sample code tests for bytes ;in the transmit buffer and sends on 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 TEST_DCD_DSR ;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 TEST_DCD_DSR ;skip if transmitter still busy 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 TEST_DCD_DSR ;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 teh work for you. The cost is the added time required to ;execute the DCD_DSR code on each NMI. TEST_DCD_DSR: ; 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 you 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 (C64 only): ; 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) ldy xmitcount ;count of bytes in transmit buffer cpy #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 ;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 ;Alternative 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 ;Sample routine to fetch a character that has been received, from the ;receive buffer. ;by Craig Bruce, 1995, adapted from above ;Will return the character in the 'A' register and the carry flag cleared if ;a character was available. If no character was available, will return with ;the carry flag set. Destroys the 'Y' register. RECVBYTE: ;fetches a byte from the receive buffer. ;there is no need to fiddle with any interrupts lda recvcount ;count of bytes in receive buffer beq RECVEMPTY ;buffer is empty, indicate to caller ldy rhead ;pointer to start of buffer lda (rbuff),y ;fetch byte out of buffer into 'A' register inc rhead ;point to next slot in buffer dec recvcount ;and add one to count of bytes in buffer clc ;indicate that we have a character rts ;return to your program RECVEMPTY: sec ;or whatever flag your program uses to tell that the ;receive buffer was empty rts ;and return ;----------------------------------------------------------------------------- ;Dumb -- very dumb -- terminal emulator. Simply polls the receive buffer and ;the keyboard and puts received data to the screen and typed data to the send ;buffer (thus, it assumes a full-duplex, echoing link). There is no ;PETSCII->ASCII conversion, no cursor, nor any other fancy features. Press ;STOP to exit. ; ;by Craig Bruce, 1995. TERMINAL: jsr RECVBYTE ;see if there is a received byte in the recv buffer bcs TERMTRYSEND ;if not, continue jsr $FFD2 ;if received byte, print it to the screen (CHROUT) TERMTRYSEND: jsr $FFE4 ;try to get a character from the keyboard (GETIN) cmp #$00 ;was there a keystroke available? beq TERMINAL ;no--go back to top of polling loop cmp #$03 ;check for STOP key beq TERMEXIT ; exit if pressed jsr SENDBYTE ;have char--put it into the transmit buffer and then jmp TERMINAL ; go back to top of polling loop TERMEXIT: lda #%0000_0010 ;disable transmitter and receiver and all interrupts sta command sei lda OLDVEC ;restore the NMI vector to its original value sta NMINV lda OLDVEC+1 sta NMINV+1 cli rts ;exit TRANSMIT_BUFFER = *+0 RECEIVE_BUFFER = *+256 %%%---8<---cut-here---8<---%%% ------------------------------------------------------------------------------ APPENDIX: 6551 ACIA HARDWARE SPECS (DATA SHEET) C= Commodore Semiconductor Group a division of Commodore Business Machines, Inc. 950 Rittenhouse Road, Nornstown, PA 19400 * 215/666-7950 * TWX 510-660-4168 (July 1987) 6551 ASYNCHRONOUS COMMUNICATION INTERFACE ADAPTER CONCEPT: The 6551 is an Asynchronous Communication Adapter (ACIA) intended to provide for interfacing the 6500/6800 microprocessor families to serial communication data sets and modems. A unique feature is the inclusion of an on-chip programmable baud-rate generator, with a crystal being the only external component required. FEATURES: * On-chip baud-rate generator: 15 programmable baud rates derived from a standard standard 1.8432 MHz external crystal (50 to 19,200 baud) [these rates are doubled in the SwiftLink]. * Programmable interrupt and status register to simplify software design. * Single +5 volt power supply. * Serial echo mode. * False start bit detection. * 8-bit bi-directional data bus for direct communication with the microprocessor. * External 16x clock input for non-standard baud rates (up to 125 Kbaud). * Programmable: word lengths; number of stop bits; and parity-bit generation and detection. * Data set and modem control signals provided. * Parity: (odd, even, none, mark, space). * Full-duplex or half-duplex operation. * 5,6,7 and 8-bit transmission. * 1-MHz, 2-MHz, and 3-MHz operation. ORDER NUMBER MXS 6551 ___ - | | +---- Frequency range | Plain = 1 MHz | A = 2 MHz | B = 3 MHz | +----------- Package Designator C = Ceramic P = Plastic 6551 PIN CONFIGURATION +---------------+ GND --| 1 28 |-- R-/W CS0 --| 2 27 |-- o2 /CS1 --| 3 26 |-- /IRQ /RES --| 4 25 |-- DB7 RxC --| 5 24 |-- DB6 XTAL1 --| 6 23 |-- DB5 XTAL2 --| 7 22 |-- DB4 /RTS --| 8 21 |-- DB3 /CTS --| 9 20 |-- DB2 TxD --| 10 19 |-- DB1 /DTR --| 11 18 |-- DB0 RxD --| 12 17 |-- /DSR RS0 --| 13 16 |-- /DCD RS1 --| 14 15 |-- Vcc +---------------+ BLOCK DIAGRAM +----------+ | TRANSMIT | | CONTROL |<------- CTS +----------+ | v +----------+ +----------+ | TRANSMIT | | TRANSMIT | /|===>| DATA |=========>| SHIFT |-------> TxD || | REGISTER | | REGISTER | || +----------+ +----------+ +---------+ || o2 --->| | || +----------+ +----------+ R-/W --->| SELECT | ||====| STATUS | | INTERRUPT|-------> /IRQ CS0 --->| AND | || | REGISTER |<-------->| LOGIC |<------- /DCD /CS1 --->| CONTROL | || +----------+ +----------+<------- /DSR RS0 --->| LOGIC | || RS1 --->| | || +----------+ +----------+ /RES --->| | ||===>| CONTROL | | BAUD-RATE|<------> RxC +---------+ || | REGISTER | | GENERATOR|<------- XTAL1 || +----------+ +----------+<------- XTAL2 || +---------+ || +----------+ +----------+ DB0 <-->| DATA- | || | RECEIVE | | RECEIVE | ... | BUS |<===||====| DATA |<=========| SHIFT |<---+--- RxD DB7 <-->| BUFFERS | || | REGISTER | | REGISTER | | +---------+ || +----------+ +-----.----+ | || | | || +----------+ +----------+ | LEGEND: \|===>| COMMAND | | RECEIVE | | | REGISTER | | CONTROL |<---+ ===> : 8-bit line +----------+ +----------+ | | ---> : 1-bit line | +--------------------------------> /DTR +-------------------------------------> /RTS MAXIMUM RATINGS ELECTRICAL CHARACTERISTICS POWER DISSIPATION vs TEMPERATURE TIMING CHARACTERISTICS INTERFACE SIGNAL DESCRIPTION /RES (Reset) During system initialization a low on the /RES input will cause internal registers to be cleared. o2 (Input Clock) The input clock is the system o2 clock and is used to trigger all data transfers between the system microprocessor and the 6551. R-/W (Read/Write) The R-/W is generated by the microprocessor and is used to control the direction of data transfers. A high on the R-/W pin allows the processor to read the data supplied by the 6551. A low on the R-/W pin allows a write to the 6551. /IRQ (Interrupt Request) The /IRQ pin is an interrupt signal from the interrupt-control logic. It is an open drain output, permitting several devices to be connected to the common /IRQ microprocessor input. Normally a high level, /IRQ goes low when an interrupt occurs. DB0--DB7 (Data Bus) The DB0--DB7 pins are the eight data lines used for transfer of data between the processor and the 6551. These lines are bi-directional and are normally high-impedance except during Read cycles when selected. CS0, /CS1 (Chip Selects) The two chip-select inputs are normally connected to the processor-address lines either directly or through decoders. The 6551 is selected when CS0 is high and /CS1 is low. RS0, RS1 (Register Selects) The two register-select lines are normally connected to the processor-address lines to allow the processor to select the various 6551 internal registers. The following table indicates the internal register-select coding: RS1 RS0 WRITE READ SL-Addr --- --- ---------------------- --------------------- ------- 0 0 Transmit Data Register Receive Data Register $DE00 0 1 Programmed Reset* Status Register $DE01 1 0 Command Register Command Register $DE02 1 1 Control Register Control Register $DE03 * for programmed reset, data is "don't care". The table shows that only the Command and Control registers are read/write. The Programmed Reset operation does not cause any data transfer, but is used to clear the 6551 registers. The Programmed Reset is slightly different from the Hardware Reset (/RES) and these differences are described in the individual register definitions. ACIA/MODEM INTERFACE SIGNAL DESCRIPTION XTAL1, XTAL2 (Crystal Pins) These pins are normally directly connected to the external crystal (1.8432 MHz) used to derive the various baud rates. Alternatively, an externally generated clock may be used to drive the XTAL1 pin, in which case the XTAL2 pin must float. XTAL1 is the input pin for the transmit clock. TxD (Transmit Data) The TxD output line is used to transfer serial NRZ (non-return-to-zero) data to the modem. The LSB (least-significant bit) of the Transmit Data Register is the first data bit transmitted and the reate of data transmission is determined by the baud rate selected. RxD (Receive Data) The RxD input line is used to transfer serial NRZ data into the ACIA from the modem, LSB first. The receiver data rate is either the programmed baud rate or the rate of an externally generated receiver clock. This selection is made by programming the Control Register. RxC (Receive Clock) The RxC is a bi-directional pin which serves as either the receiver 16x clock input or the receiver 16x clock output. The latter mode results if the internal baud rate generator is selected for receiver data clocking. /RTS (Request to Send) The /RTS output pin is used to control the modem from the processor. The state of the /RTS pin is determined by the contents of the Command Register. /CTS (Clear to Send) The /CTS input pin is used to control the transmitter operation. The enable state is with /CTS low. The transmitter is automatically disabled if /CTS is high. /DTR (Data Terminal Ready) The output pin is used to indicate the status of the 6551 to the modem. A low of /DTR indicates the 6551 is enabled and a high indicates it is disabled. The processor controls this pin via bit 0 of the Command Register. /DSR (Data Set Ready) The /DSR input pin is used to indicate to the 6551 the status of the modem. A low indicates the "ready" state and a high, "not-ready". /DSR is a high- impedance input and must not be a no-connect. If unused, it should be driven high or low, but not switched. Note: If Command Register Bit #0 = 1 and a change of state on /DSR occurs, /IRQ will be set and Status Register Bit #[5] will reflect the new level. The state of /DSR does not affect Transmitter operation [but must be low for the Receiver to operate]. [This statement reflects the SwiftLink implementation]. /DCD (Data Carrier Detect) The /DCD input pin is used to indicate to the 6551 the status of the carrier- detect output of the modem. A low indicates that the modem carrier signal is present and a high, that it is not. /DCD, like /DSR, is a high-impedance input and must not be a no-connect. Note: If Command Register Bit #0 = 1 and a change of state on /DSR occurs, /IRQ will be set and Status Register Bit #[6] will reflect the new level. The state of /DCD does not affect either Transmitter or Receiver operation. INTERNAL ORGANIZATION TRANSMIT AND RECEIVE DATA REGISTERS (SL-Addr: $DE00 / 56832) These registers are used as temporary data storage for the 6551 Transmit and Receive circuits. The Transmit Data Register is characterized as follows: * Bit 0 is the leading bit to be transmitted. * Unused data bits are the high-order bits and are "don't care" for transmission. The Receive Data Register is characterized in a similar fashion: * Bit 0 is the leading bit received. * Unused data bits are the high-order bits and are "0" for the receiver. * Parity bits are not contained in the Receive Data Register, but are stripped off after being used for external parity checking. Parity and all unused high-order bits are "0". Transmit / Receive Data Register +-----+-----+-----+-----+-----+-----+-----+-----+ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +-----+-----+-----+-----+-----+-----+-----+-----+ | data | The following figure illustrates a single transmitted or received data word, for the example of 8 data bits, parity, and 1 stop bit: "MARK"____ ________________________________________________________"MARK" | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | P | S . |____|____|____|____|____|____|____|____|____|____|____| start parity stop bit ...data bits... bit bit STATUS REGISTER (SL-Addr: $DE01 / 56833) The Status Register is used to indicate to the processor the status of various 6551 functions and is outlined here: Command Register +-----+-----+-----+-----+-----+-----+-----+-----+ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +-----+-----+-----+-----+-----+-----+-----+-----+ | irq | dcd | dsr | txr | rxr | ovr | fe | pe | +---+ | 7 | /IRQ*** : cleared by reading status register +---+ -------------------------------------------- 0 No interrupt 1 Interrupt +---+ | 6 | /DCD : non-resetable, indicates /DCD status +---+ -------------------------------------------- 0 /DCD low 1 /DCD high +---+ | 5 | /DSR : non-resetable, indicates /DSR status +---+ -------------------------------------------- 0 /DSR low 1 /DSR high +---+ | 4 | Transmit Data Register Empty: Cleared by write to Tx Data reg +---+ ------------------------------------------------------------- 0 Not empty 1 Empty +---+ | 3 | Receive Data Register Full: Cleared by read from Rx Data reg +---+ ------------------------------------------------------------- 0 Not full 1 Full +---+ | 2 | Overrun*: Self-clearing** +---+ ------------------------- 0 No error 1 Error +---+ | 1 | Framing Error*: Self-clearing** +---+ ------------------------------- 0 No error 1 Error +---+ | 0 | Parity Error*: Self-clearing** +---+ ------------------------------ 0 No error 1 Error Notes: * No interrupt generated for these conditions ** Cleared automatically after a read of RDR and the next error- free receipt of data *** Reading status reg. will clear the /IRQ bit except when transmit intr. enabled 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+ | 0 | x | x | 1 | 0 | 0 | 0 | 0 | After Hardware reset +---+---+---+---+---+---+---+---+ | x | x | x | x | x | 0 | x | x | After Software reset +---+---+---+---+---+---+---+---+ COMMAND REGISTER (SL-Addr: $DE02 / 56834) The Command Register is used to control specific Transmit/Receive functions and is shown here: Command Register +-----+-----+-----+-----+-----+-----+-----+-----+ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +-----+-----+-----+-----+-----+-----+-----+-----+ | parity | echo| tx ctrl | rxi | dtr | +---+---+---+ | 7 | 6 | 5 | PARITY CHECK CONTROLS +---+---+---+ ---------------------- x x 0 parity disabled--no parity bit generated or received 0 0 1 odd parity receiver and transmitter 0 1 1 even parity receiver and transmitter 1 0 1 mark parity transmitted, parity check disabled 1 1 1 space parity transmitted, parity check disabled +---+ | 4 | NORMAL/ECHO MODE FOR RECEIVER +---+ ------------------------------ 0 Normal 1 Echo (bits 2 and 3 must be "0") +---+---+ | 3 | 2 | Tx INTERRUPT RTS LEVEL TRANSMITTER +---+---+ ------------ --------- ------------ 0 0 Disabled High Off 0 1 Enabled Low On 1 0 Disabled Low On 1 1 Disabled Low Transmit BRK +---+ | 1 | RECEIVE INTERRUPT ENABLE +---+ ------------------------- 0 /IRQ interrupt Enabled from bit 3 of Status Register 1 /IRQ interrupt Disabled +---+ | 0 | DATA TERMINAL READY +---+ -------------------- 0 Disable Receiver and all interrupts (/DTR high) 1 Enable Receiver and all interrupts (/DTR low) 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | After Hardware reset +---+---+---+---+---+---+---+---+ | x | x | x | 0 | 0 | 0 | 0 | 0 | After Software reset +---+---+---+---+---+---+---+---+ CONTROL REGISTER (SL-Addr: $DE03 / 56835 / cpm: 0001xxxx) The Control Register is used to select the desired mode for the 6551. The word length, number of stop bits, and clock controls are all determined by the Control Register, which is shown here: Control Register +-----+-----+-----+-----+-----+-----+-----+-----+ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +-----+-----+-----+-----+-----+-----+-----+-----+ |stops| word len | src | baud rate | +---+ | 7 | STOP BITS +---+ ---------- 0 1 stop bit 1 2 stop bits 1 1 stop bit if word length== 8 bits and parity this allows for 9-bit transmission (8 data bits plus parity) 1 1.5 stop bits if word length== 5 bits and no parity +---+---+ | 6 | 5 | WORD LENGTH +---+---+ ------------ 0 0 8 bits 0 1 7 bits 1 0 6 bits 1 1 5 bits +---+ | 4 | RECEIVER CLOCK SOURCE +---+ ---------- 0 external receiver clock 1 baud rate generator +---+---+---+---+ | 3 | 2 | 1 | 0 | BAUD RATE GENERATOR +---+---+---+---+ -------------------- 0 0 0 0 16x external clock 0 0 0 1 100 baud 0 0 1 0 150 baud 0 0 1 1 219.84 baud 0 1 0 0 269.16 baud 0 1 0 1 300 baud 0 1 1 0 600 baud 0 1 1 1 1200 baud 1 0 0 0 2400 baud 1 0 0 1 3600 baud 1 0 1 0 4800 baud 1 0 1 1 7200 baud 1 1 0 0 9600 baud 1 1 0 1 14400 baud 1 1 1 0 19200 baud 1 1 1 1 38400 baud 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | After Hardware reset +---+---+---+---+---+---+---+---+ | x | x | x | x | x | x | x | x | After Software reset +---+---+---+---+---+---+---+---+ ---END---