Super Serial Card

Programming the Super Serial Card.

[Originally published in the Apple-Dayton Journal, September, 1987]

The most common method by which personal computers talk to the outside world is through a serial connection. Apple's Super Serial Card (SSC) is the archetype of interface cards that provide such a serial interface to the Apple II series of computers. While there is a wide variety of high quality communication software for the Apple, occasionally a project requires custom software. This article describes a set of low level routines that are useful for working with the SSC.

Although the SSC has built-in firmware for accessing the serial port from high level languages like BASIC and Pascal, it is sometimes useful to manipulate the card directly for reasons of security or efficiency. In regard to security, electronic vandalism is a fact of life, and any system that runs unattended (eg. an electronic bulletin board) must have total control over incoming data. With respect to efficiency, data might arrive at the SSC faster than the computer can process and display it. One useful technique is to allow the SSC to signal an interrupt to the Apple's 6502 central processing unit (CPU) whenever a byte of data is ready. When the CPU is thus interrupted, a special interrupt routine is invoked to collect the data and store it in a temporary buffer. When control returns to the interrupted program, it can process any data that has been stored in the buffer in the interim. With a sufficiently large buffer, even a complex program can keep up with high data rates.

The interrupt technique works quite well as long as the main program allows itself to be interrupted often enough not to lose any incoming data. A problem arises in that the internal program for scrolling the screen on some Apple's disables interrupts for relatively long periods of time. The original (unenhanced) Apple //e, for instance, is limited to 300 bits per second (bps) in 80 column mode. Shortening the scrolling window to less than 18 lines or asking the remote system to delay after each carriage return (by sending null characters) may allow rates up to 1200 bps. Some terminal programs (eg. ASCII Express Pro) employ a custom screen driver program which is more generous with interrupt time. Certain 80-column cards (eg. Videx) use special hardware to make scrolling faster thus allowing more frequent interrupts. On the //c and the enhanced //e, the scrolling code allows interrupts after each line is scrolled; it can generally keep up at 1200 bps. On the IIgs, the combination of faster CPU and improved firmware allows scrolling to keep up at speeds as high as 9600 bps.

The program tty uses an interrupt routine to handle incoming data from the SSC. It is written in Pascal using the compiler from Kyan Software which generates native 6502 code and permits in-line assembly (signaled by the directive "#a"). The main program emulates a dumb terminal by repeatedly examining the keyboard and the interrupt routine's buffer. If there are incoming characters waiting, one is un-buffered and printed on the screen; if a key has been pressed, the corresponding character is sent out the serial port. Any keypress is also echoed to the screen and the escape character is treated as a special signal to halt the program.

The SSC uses a 6551 Asynchronous Communications Interface Adapter (ACIA) to implement the serial port. The ACIA's registers appear as four slot dependent addresses in the Apple's memory: data, status, command, and control. The registers themselves are coded as constants in the procedure openport. It is tempting to remove the resulting slot dependence by coding the slot as a variable and using an indexed addressing mode to manipulate the registers. Unfortunately, a quirk in the 6502's addressing logic precludes doing things so simply. Interested readers should examine the routine ACIAOUT at $CC02 in the SSC firmware for a workable (if obscure) solution to this problem.

Apple's ProDOS operating system makes explicit provision for establishing interrupt routines. After initializing two buffer pointers to zero, the procedure openport calls the ProDOS Machine Language Interface (MLI) to install the address of the SSC interrupt routine in the system. Openport also initializes the ACIA for 1200 bps, eight data bits, one stop bit and no parity. Note that the ACIA control register must be set explicitly to allow the SSC to generate interrupts. Apple's Super Serial Card Installation and Operating Manual gives full details on the meaning of each of the status, command and control bits. Other values for these registers may be found in the following tables: Status, Command and Control.

The interrupt routine itself, labeled irq, begins with a cld instruction which helps ProDOS verify that the interrupt procedure is valid. The code then examines bit seven of the ACIA status register to see if the interrupt came from the SSC. Although the 6502 supports only one level of maskable interrupt, ProDOS permits each of several interrupting devices to have its own service routine. Returning with the carry set allows ProDOS to give any other installed interrupt routine a chance to claim the event. After checking that the receive register has a complete byte, the routine examines the lowest three bits of the status register for any error: frame, parity or overrun. If such an error occurs, the data register contents are discarded and a "?" character is substituted to mark the error. If no error is identified, the data is stored in the next available buffer location and the buffer pointer is incremented. Notice that when the pointer is incremented past 255 ($FF) it wraps around to zero. This is the essence of the circular queue, sometimes called a ring buffer. The interrupt code ends by clearing the carry (clc) to tell ProDOS that this interrupt has been handled.

The main program never calls the interrupt routine directly. To learn if there is data waiting, tty calls the function getport. This function returns true if, and only if, the buffer pointers are unequal, ie. if there is data in the buffer. Interrupts are disabled (sei) while the function is manipulating the buffer pointers. The next byte of data, if any, is returned in the variable parameter ch. The identifier _sp is Kyan's symbol for the execution stack pointer. Since ch is passed by reference, it is the address of ch that appears in bytes six and seven relative to _sp rather than the variable itself. Hence the address of ch is pulled from the stack and stored in a temporary variable named _t. Indirect indexed addressing is then used to store the data in ch. Finally, the function result (1 = true) is stored in byte five relative to _sp and interrupts are once again enabled (cli) in preparation for the next cycle.

The procedure putport is much more simple. It checks bit four of the status register to see if the transmit data register is empty and loops until the ACIA is able to send another byte. The data to be sent is passed by value in the parameter ch, hence a copy of the data (rather than its address as above) is found at byte five relative to the stack pointer.

The function keypress is similar to getport in that it is true when there is a character waiting, and it passes that character back to the main program in a variable parameter. The difference is that keypress checks the keyboard at location $C000 instead of the interrupt buffer. When a character is found at the keyboard, keypress also clears the keyboard strobe at $C010 in preparation for the next keystroke.

The procedure closeport instructs the MLI to remove the tty interrupt routine from ProDOS's list of potential interrupt candidates. If the ACIA interrupt routine's address were left in the chain, it might be called inappropriately to service interrupts from other interrupting devices such as a mouse, clock or another serial port. Failing to call closeport when tty terminates is guaranteed to cause a catasrophic system crash.

While the program tty emulates a primitive dumb terminal, the routines used in the program offer a foundation for any communication software. The rest, as they say, is left as an exercise for the reader.

Acknowledgement

Special thanks to Apple-Dayton member Jim Hopper and Lloyd Greene for invaluable assistance in the preparation of this article.

References

Apple ][ Super Serial Card Installation and Operating Manual, Apple Computer, Inc., 20525 Mariani Avenue, Cupertino, CA 95014, 1981.

Kyan Pascal, a Programming Language, Kyan Software, 1850 Union Street #183, San Francisco, CA 94123, 1985.

Leventhal, L., 6502 Assembly Language Programming, Osborne/McGraw-Hill, 630 Bancroft Way, Berkeley, CA 94710, 1979.

Worth, D., P. Lechner, Beneath Apple ProDOS, Quality Software, 21601 Marilla Street, Chatsworth, CA 91311, 1984.

FAQ: Super Serial Card Jumper & DIP Switch Settings

Listing 1.

program tty;
{by John B. Matthews, 5/13/87}
{Kyan Pascal, Apple ][, SSC slot 2}
var ch: char; quit: boolean;

procedure openport;
begin
#a
slot    equ $20 ;slot 2
data    equ $C088+slot
status  equ $C089+slot
command equ $C08A+slot
control equ $C08B+slot
 lda #0 ;init buffer pointers
 sta bs
 sta be
 jsr _mli ;install interrupt handler
 db $40
 dw irqlist
 lda #$18 ;1 stop, 8 data, 1200 bps
 sta control
 lda #9 ;no parity, enable IRQ
 sta command
 lda data ;throw out garbage
#
end;

{SSC 6551 ACIA rcv interrupt handler}
#a
irq cld
 lda status
 bmi mine
 sec ;not my interrupt
 rts
mine tay ;save status
 and #8 ;data reg full?
 beq claimed
 tya ;get status
 and #7 ;parity, frame or overrun?
 beq read
 lda data
 lda #'?
 bne save
read lda data
save ldy be
 sta buf,y
 iny
 sty be
claimed clc
 rts
irqlist db 2,0
 dw irq
bs db 0 ;buffer start
be db 0 ;buffer end
buf ds 256 ;circular queue
#

procedure closeport;
begin
#a
 lda #2
 sta command
 lda #1
 sta irqlist
 jsr _mli
 db $41
 dw irqlist
 lda #2
 sta irqlist
#
end;

function getport(var ch:char):boolean;
begin
 getport:= false;
#a
 sei
 ldy bs
 cpy be
 beq nochar
 ldy #6
 lda (_sp),y
 sta _t
 iny
 lda (_sp),y
 sta _t+1
 ldy bs
 lda buf,y
 iny
 sty bs
 ldy #0
 sta (_t),y
 ldy #5
 lda #1
 sta (_sp),y
nochar cli
#
end;

procedure putport(ch:char);
begin
#a
tdre lda status
 and #$10 ;bit 4 set when Tx empty
 beq tdre
 ldy #5
 lda (_sp),y
 sta data
#
end;

function keypress(var ch:char):boolean;
begin
 keypress:= false;
#a
 bit $C000
 bpl nokey
 ldy #6
 lda (_sp),y
 sta _t
 iny
 lda (_sp),y
 sta _t+1
 lda $C000
 and #$7F
 ldy #0
 sta (_t),y
 sta $C010
 ldy #5
 lda #1
 sta (_sp),y
nokey
#
end;

begin {main}
 quit:= false;
 openport;
 repeat
  if getport(ch) then write(ch);
  if keypress(ch) then begin
    putport(ch);
    write(ch);
    quit:= ch=chr(27)
   end;
 until quit;
 closeport
end. {main}

* Nominal baud rates based on a standard 1.8432 MHz external crystal. Bits 0-3 clear: 1,843,200 Hz / 16 = 115,200 baud

Copyright 1987, 2003 John B. Matthews

Distribution permitted under the terms of the GPL: http://www.gnu.org/copyleft/gpl.html.

Last updated 19-Sep-2008