!pr2
Another Auxiliary Memory Program...............David C. Johnson
                                            Applied Engineering

What has 640K of memory and is as cute as a button?  My Apple //c!  It didn't come with all that memory, "only" 128K of it.  Before I even powered it up for the first time, I installed a 512K Z-RAM.  Ready to take on Blue's 640K machine?  Maybe.

I've had quite a few Apple Computers, my first had Integer ROMs and a serial number in the thirty one thousands, and my current workhorse is an Apple //e with the works.  So why a //c?  Well, for one it's cute, and secondly its firmware was written by Ernie Beernink and Rich Williams, the same guys that wrote the //e Enhanced ROMs and Extended Debugging Monitor.  These guys write slick code.  Finally, I can type control-reset with one hand.

Well, what to do after getting it home?  I tried my mouse out on it, but moved it back to the //e.  My paddles and joysticks all have 16-pin plugs, so I couldn't use them.  I don't have an RGB interface for the //c yet, so the color monitor has to stay put.  That leaves my Imagewriter printer to play with.

Having two computers and only one printer is an old problem.  One usually solved with a rotary switch.  I figured that I could do a little better.  What I did is connect the Imagewriter to the //c's Printer port, and the //e's Super Serial Card (SSC) to the //c's Modem port.  I then wrote the program that follows this article.  It implements a 576K buffer for the //e, in the //c.  Now I can use the printer from the //c just by typing pr#1.  When I want to print from the //e, I just boot a disk on the //c, then type pr#1 on the //e.  However, the printing, for the //e, goes MUCH faster.  I've setup the link between the //e and the //c to transmit at 19200 baud!  Assembling a listing of the buffering program takes about 7 seconds (and half of that is writing the target file)!

The SSC is in slot 1, it is configured as follows:

     SW1: off off off off off  on  on 
     SW2:  on off off  on  on off off
     The jumper block is installed pointing towards modem

The Imagewriter's swiches are set:

     SW1:   open   open   open   open closed closed open open 
     SW2: closed closed   open   open.

The pieces are connected with two DIN 5-Pin(m) to DB-25(m) cables, Apple Model Number: A9C0308 (4-2, 2-3, 1-6, 3-7, and 5-20).  The cable from the //e to the //c is plugged into a //c System Clock which in turn is plugged into the Modem Port.

The program should work with most any serial printer, and serial card, however, if the serial card cannot "eliminate the modem", you will need a modem-eliminator cable extension, or will have to reverse pins 2 and 3 and pins 6 and 20 of the DB-25 connector.  The Apple cable I used cannot be modified.

While the listing included with this article requires a 512K Applied Engineering Z-RAM board, I have also written versions that work in a 256K Z-RAM and in a stock Apple //c.  More on these versions later.  The memory on a Z-RAM is implemented as additional banks of auxiliary memory.  Which of the auxiliary banks is the current auxiliary bank is controlled by a new hardware location at $C073.  The Z-RAM powers-up disabled, that is, with the //c's built-in auxiliary bank as the current auxiliary bank.  The //c powers-up with main memory enabled and all auxiliary memory disabled.  Once selected as the current auxiliary bank, a Z-RAM bank is switched around by all the normal soft switches in the same manner as the //c's built-in auxiliary bank.  A 512K Z-RAM has 8 additional banks and a 256K Z-RAM has 4 more.  Which additional bank is the current auxiliary bank is selected by writing an ODD number between 1 and $F (inclusive) to the bank register at $C073.  The 4 most significant data bits are ignored and any even number (usually zero) selects the //c's built-in auxiliary bank.  A 256K Z-RAM only has bank numbers 3, 7, $B, and $F.  To ease the task of writing programs that display 80 columns of text or double hires graphics, video data is always fetched from the //c's banks, even if a Z-RAM bank is the current auxiliary bank.  Because the Z-RAM plugs into the processor and MMU sockets of the //c, and since only one board may be added this way, the Z-RAM includes a Z-80 processor.  The Z-RAM is also totally compatible with the RamWorks board for the //e.

The //c's serial ports are a lot like Super Serial Cards in slots 1 and 2 of a //e.   The ports and the SSC both use the 6551 ACIA (Asynchronous Communications Interface Adapter) and the firmware is quite similar.  There is one significant difference that I found.  The SSC tells an external source of data to stop transmitting by asserting the Data Terminal Ready bit of the ACIA command register (and thus the DTR pin when the jumper block is in the terminal position), while the //c's ports control the DTR pin with the Request To Send (and transmitter control) bits.  It's right there on page 254 of The Apple //c Reference Manual Volume 1.  Compare this to the schematic on page 100 of the SSC Manual.

Because every //c has a 65C02 processor, I can write code using the new opcodes and it will work in other peoples' machines.  Of course if the code will also work in a //e, I can not be sure that it will be executed on a 65C02.  With the release of the //e enhancement kit, this situation should improve.  65802 opcodes, being new and rare, must be reserved for programs intended for use in a very few machines.

On to the program.  The target file is intended to load at $2000 in main memory. The code from lines 32 to 73 is executed in the $2000 area.  This section does all of the setup for what is to come.  The D and I flags are cleared and set respectively, ten soft switches are thrown, the screen is cleared, the remainder of the code is copied into ALL auxiliary zero pages and stacks, a text message is written to the screen, and the two ACIAs are initialized.  The code copy and message printing share a loop. Lines 66 and 70 cheat a little.  The INCs are assembled and the LDA #s are treated as comments.  They work because the would-be operands of the LDA #s are one greater than the values just loaded by the previous LDA #s.  The 'A' in line 74 is an open-apple MouseText character.  The code in aux bank 0 is then entered at label 'Scan'.

The routines 'Write' and 'Read' (lines 79 and 88), handle all access to the buffer.  In 'Write', the aux bank is selected, the address within that bank is written into the operand of a store absolute instruction (the copy in the bank just selected), and then the data byte is written.  That's a total of four bytes of information passed in internal registers.  The data byte had to be passed in the stack pointer!  It couldn't have been passed in a memory location because it would have been switched out.  'Read' is a little simpler, it returns a data byte in the Acc.  Since I'm using the S-reg for data and the aux bank 0 stack page for code, the program doesn't make any use of regular stack operations.  After re-selecting aux bank 0, 'Write' and 'Read' jump back to the code just after the jumps that 'called' them.  Even though the $2000 code copied the entire image into every aux bank, only 'Write' and 'Read' are not used as buffer in the Z-RAM banks.

Lines 99 to 108 allocate the (zero page!) variables required to keep track of the buffer.  The 'Receive' variables indicate where the next byte received will be buffered, the 'Transmit' variables indicate where the next byte to be printed is buffered, and the 'Byte.Counter' variables keep track of how full (or empty) the buffer is.  If the byte counter is zero, then the 'Transmit' variables are equal to the 'Receive' variables and the buffer is empty.  'RTS.Bit' is used to keep track of the //c's 'select' state.

Lines 110 to 128 run an indicator at the top-center of the screen and check to see if you've pressed a key.  If you press the space bar, and if the program hasn't asserted the Request To (NOT) Send bit (because the buffer is nearly full), the //e may be halted.  This works like a printer's select button.

Lines 129 to 207 handle buffering incoming data.  If the Modem ACIA detects any transmission errors, you will see an indication of this at the left end of screen line three.  If no character has been received, we go check the Printer port.  When a character has been received, we test if the buffer is almost full.  If it is, we assert RTS' (another character may already be on the way).  The byte counter is incremented.  If the buffer is completely full, we tick the third position of screen line one and go check the Printer port.  This means that the RTS' handshaking isn't working.  You will also get overrun errors.  If we have room for the character, we increment the upper left screen position, and load the character from the RxD reg into the stack pointer.  We then load the 'Receive' variables, maybe juggle the address high order nibble for the overlapping language card banks, and call 'Write'.  Upon return, the 'Receive' variables are advanced through the buffer memory, avoiding our program and invalid aux banks.  We then fall into the Printer port code.

Lines 208 to 271 handle printing buffered data as the printer can take it.  This code is similar to the code for incoming data.  Fewer things can go wrong, we of course test for an empty TxD reg and an empty buffer.  We check to see if the buffer is somewhat less than almost full, and may release RTS'.  The byte counter is decremented here.  When a character is to be printed, we increment the upper right screen position, load the 'Transmit' variables, maybe juggle, call 'Read' and stuff the character into the TxD reg.  Upon return, the 'Transmit' variables are advanced (same way), and we loop to 'Scan'.  Forever.  Reset exits the program.

The program loops VERY quickly.  It has to.  At 19200 baud, a character is received from the //e every half millisecond and at 9600 baud, a character may be printed every millisecond.  The pair of locations at the top center of the screen, that are changed every time around the loop, give a good indication of how fast things are happening.  The locations in the upper corners (my //e is to the left of the //c and the printer is to the right) are a good representation of the values of the 'Receive' and 'Transmit' variables. When buffering, the receive indicator races ahead while the transmit indicator lags behind, but since they are both initialized to blanks and the appropriate one is incremented when a character is moved, they come to rest displaying the same character when the buffer is empty.

The symbols 'Z.RAM.Banks.Avail', 'Z.RAM.Banks.Used', 'IIc.Aux.Bank.Avail' and 'BufLen' (lines 94, 96, 273-274) determine the size of the buffer.  The ADC immediate operands in lines 195 and 259 cause the buffer to advance from bank 0 to 1 to 3 to 5... to $F.  The listing is setup to use a //c's aux bank and a 512K Z-RAM.  The changes for a 256K Z-RAM are easy: change the SAVE and .tf filenames (320K), change the 8 in line 96 to a 4, change the 9 in line 274 to a 5, and change the ADC #1s in lines 195 and 259 to ADC #3s.  The changes for operating without a Z-RAM are not as simple.  I removed all the bank stuff, made the byte counter only 16 bits, and combined the code copy with the screen clear instead of the message printing.  It took about 5 minutes.  The resulting code just fit into the aux zero page!  The source code for all three versions will be on S-C Software's next quarterly disk, and I will send a paper listing of the //c only version to anyone who sends a self addressed stamped envelope to me care of Applied Engineering.  I sometimes use the //c only version even though I have a Z-RAM.  With the ProDrive disk emulation software, I can lock-out bank 0, leaving it available for double hires or a 64K buffer for my //e.  With a 512K Z-RAM, I get a 1024 block /RAM volume.

The program does not use any main memory for the buffer because when you have 576K of aux memory, why bother programming for "only" another 64K?  The //c only version, with 64K of buffer memory, is as big or bigger than most buffer boards/boxes.  If anyone writes a 128K main/aux version of the program I would appreciate a copy.
