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 (Fi...
Amiga7878