Enough Is Enough
I lied. Just a little. I admit it! Last month I said you could turn your old 400/800 computer into a peripheral without any hardware changes. Well that is only 80% correct because while the computer goes untouched, you will need to build or modify cables (more on that later). Now, like I promised you, here is Atari Zucchini.
Zucchini is a hardware project.
Zucchini is a software project.
Zucchini allows connection of a slave Atari to a host Atari through the serial port.
Zucchini drives your printer directly through the joystick ports (remember them?).
Zucchini is a 1 K machine-language program on a boot disk. This makes it modifiable to create any number of new peripherals and means it runs without DOS.
Zucchini allows you to off load text at high speed and run the printer at low speed via its buffer.
Zucchini allows you to print text as graphics to list your BASIC programs including all those unprintable ATASCII characters.
Now that you know a little bit about what Zucchini is, let's look at how to build it and how it works.
The Right ConnectionsBefore we get into the cable modifications, let's look at the job we need the cables to do. In order for an Atari to act as a peripheral we need to make several switches. The Data-In and Data-Out lines need to be reversed, the COMMAND line from the host computer needs to be attached to the INTERRUPT pin on the slave's serial port, and the Clock In and Clock Out completely disconnected. I/O is asynchronous, and so the clock lines are not needed. You can either cross the slave's COMMAND to the host INTERRUPT or you can leave it detached (see XL/XE addendum).
So that leaves us with three lines that need to be changed around. The problem is that you cannot simply exchange the pins in your cable because you need to boot load the Zucchini program before your extra computer can work as a slave. This requires the cable be ''normal" to load Zucchini and "altered" to run it. What you need to do is add a multi-pole switch to the cable that connects your Zucchini 800 to your host computer. You can flip the switch one way to load and the other way to use as a peripheral (see Figure 1).
One other problem which you will incur: both host and slave have +5 volts available on Pin 10 of the serial plug. If both computers are connected together through these pins, then one can pull power from the other if it is turned off thereby overloading power supplies. If you simply cut this wire or disconnect the pin in your cable, you will not be able to boot load the program because the disk drive will not operate unless it senses +5 volts on Pin 10. So you also need to modify the cable that normally hooks to your host computer by cutting the lead from Pin 10. This blocks power from flowing between the two computers but it also means that you must boot up Zucchini first and that Zucchini must be powered up for you to use the disk drive with your host. Similarly the 800/400's have + 12 volts on Pin 12, but you should disconnect this since it is unused.
If you are lazy like me, you will want to build an automatic switching mechanism. The 4053 CMOS IC suits the bill. Originally designed to be an electronic switch for audio signals, it seems to work just fine for this purpose. After all, there is not much difference between high-frequency audio and 19,200 baud.
This circuit connects the Zucchini serial port up in normal fashion, but will switch signals automatically to the slave position when the cassette control line is turned on by ZUCCHINISOFT after it boot loads. This circuit has a diode in the power lead to block power influx from the host. In order to prevent power egress, you need to insert another diode into the first cable from the host where you cut it before. I used the circuit board and added another 13-pin serial socket to allow additional peripherals to be added (such as the 410 cassette or my ALPHACOM 42 printer which do not have their own additional receptacles). I cannibalized a disk drive cable which I happened to have laying around, but you can build your own Zucchini interface using ribbon cable and new plugs. See the end of this article for sources.
Next you will need to build the printer cable. You need three joystick cables and a 36-pin male, Centronics printer plug. Solder the wires according to this diagram:
You can use IDC plugs and ribbon cables, but I found the joystick replacement cables as easy as any to use. When done, plug the 36-pin plug into your printer and the joystick cables into their proper outlets on your Zucchini. Run the following program to test the cable:
10 DIM WORD$(20):WORD$="ZUCC
30 P=PEEK(PACTL):POKE PACTL,P
-4:POKE PORTA,255:POKE PACTL,
40 P=PEEK(PBCTL):POKE PBCTL,P
-4:POKE PORTB,1:POKE PACTL,P
50 FOR S=1 TO LEN(WORD$):POKE
60 IF TRIG(0)=1 THEN 60
70 POKE PORTB,0:POKE PORTB,1:
You should recognize how Lines 20-40 convert the joysticks to output. TRIG(0) is used as the "Busy'' indicator for the printer and will read 0 when the printer is idle and 1 if busy or disconnected. If it works, then you are ready to go on to the programming.
The ProgramIf you subscribe to ANALOG on disk you can load Zucchini.BAS and skip on down a ways. But if you are not lucky enough to have the disk, then type in the BASIC listing and save it to disk as Zucchini.BAS before running. Now run the program. If the data is correct then the program will halt and give you a message to remove your program disk and insert a new, blank disk which will be formatted by your BASIC program. Then press return and the boot file will write to the disk. If you left your program disk in the drive you would have just lost everything!
If you want to understand the program better or modify it for your own purposes, then type in the source code and list to disk as ZUCCHINI.SRC before assembling. Now assemble and check for errors. When error-free then re-list to disk. At this point format a blank disk to hold your final assembly. Now for the final assembly, change the variable ORIGIN to $0700 and assemble again. Since the program assembles into DOS's space, you cannot save, list, or otherwise use DOS once you have done your final assembly. At the end of the assembled program is a routine to transfer the program to disk as a boot file, so go to BUG and run at the address of ENDPRO. If you want to modify your program then all changes must be between START and ENDPRO. You need to set ORIGIN about 2K above the end of your source code to allow space for assembly so as to not overwrite the source. You can delete all the commentary to create more free buffer space. When your modified program is debugged, LIST to disk and follow the above procedure to create the boot disk.
Eating ZucchiniTo use your new interface, connect up your Zucchini 800 to the serial bus using your Zucchini interface and then connect the host computer with the modified regular cable.
Load the ZUCCHINISOFT boot disk into Drive 1 and power up your Zucchini 800. It takes about two seconds to load and the console speaker will beep once when it is ready, but will beep continuously if the printer is not online. Once your Zucchini is online with the printer running, remove the disk and proceed to boot up the host computer as usual. Now you can use your printer just as you normally would using LPRINT or PRINT #1. The big advantage is the speed with which the computer is freed up. Try this:
10 DIM WORD$(5200),AB$(26)
30 FOR S=1 TO 5200 STEP26:WO
40 OPEN #1,8,0,"P:":PRINT#1;
It took one minute, 22 seconds to print WORD$ with my AXIOM AT-846 interface and my Gemini 10X, but with my Zucchini 800 it took only six seconds to transfer WORD$ to the interface and only one minute, eight seconds to print. It took one minute to send last month's article to the interface and about eight or nine minutes to print. This is quite a time saver. But of even more value is this little trick. When the Zucchini interface is idle, press ESCAPE once. Now load a BASIC program into your host and then type LIST "P:''. Pretty neat, huh? In case you missed it, your program is listed verbatim to the printer in graphics, not text, in 38-column format, exactly as it appears on screen with inverse characters, graphics characters and so on. So what you say; you have several other programs to do this. Ah, but this one solves two problems. First is that it takes forever to print such a listing with a lister program while the Zucchini interface will take the program as text as fast as the host can send it. A typical program may take 30 to 90 seconds to dump but may take a half hour to print! Moreover, the carriage-return problem has finally been solved.
What is the carriage-return problem? ATARI uses ATASCII 155 (inverse ESCAPE) as the carriage return, but the printer and the rest of the world recognizes CHR$ (13) as a carriage return. So your interface must convert any bytes of value 155 to 13. When you go to print graphics, you will still convert any bit patterns of value 155 to 13. This plays havoc with your graphics representation of the inverse "A" character and requires you to alter the character set or to replace all 155s with some other number. My AT-846 interface has a jumper option to ignore the conversion, but then you don't get any carriage returns unless you add a CHR$ (13) at the end of everything you print or by adding your own printer handler. So the only way to avoid this problem is Zucchini. To return to standard text, press ESCAPE again and when the interface is idle the conversion will occur.
How much actual buffer space you have depends on your computer configuration. The OS uses memory up to Page 6 or 1536 bytes. Zucchini uses about 1200 bytes, so your buffer space starts at $0BB1. A stock ATARI 400 will have about 13.5K free buffer space and an 800 with 48K will actually have 46K buffer space or about 18 pages of text. I have only been able to fill up the buffer twice: once when I sent all three parts of this series at once (25 pages of text), and the other when printing a full-page poster from print shop. If you should be able to, the interface will merely cause the host computer to timeout for 28 seconds while the buffer empties a bit. When the retry occurs the host will then refill the buffer and timeout again. This continues until the host is done sending. You will also note that the printer slows down a bit while data is being transferred. About the only thing that can go wrong is if the printer is offline and the buffer fills up. Then a real timeout can occur giving ERROR 138. RESET will empty memory and restart the program with console beep and all.
Zucchini PowerWhile all this is impressive enough it only touches the surface of possibilities. The real power of the Zucchini interface is that it is programmable! Examples...
There are many possible variations on the theme of printer interfaces that would allow some interesting possibilites. You still have three trigger lines and seven PORTB pins to play with. You can attach a second printer to PORTA and use TRIG1 for BUSY and bit 2 of PORTA for a strobe. Now you have two printers on the same interface. Both the XL and XE OSs support multiple "P:" devices which have device IDs of $40 plus the printer number -1. So "P4:" would have an ID of $43. You could have one printer interface that would respond to OPEN #1,8,0,"P2:" or could drive both a P: and P2: from the same interface. But why stop there. You could probably run four printers simultaneously from the same source. My XL supports up to P9: devices! Great if you do mailings!
You could have several keyboard selectable fonts.
Besides the standard print mode there are two other print modes supportable by the printer handler. OPEN #1,8,68,"P:" changes the AUX1 byte of the command frame from "N"(78) to "D"(68) and sends data in 20-byte frames. Originally this was for double-wide text. Similarly, OPEN #1,8,83,"P:" sends an "S"(83) for sideways (??) printing with 29-byte frames. You can use this for software selectable special effects by adding code to recognize this.
Your Zucchini can be programmed to convert ESCAPE or control code sequences from one format to another. Theoretically you could get your EPSON to respond just like an NEC printer or any other printer for that matter.
Other possibilities? How about a ramdisk? You can use Device ID of $37 for "D8:". How about a slave terminal to your main computer? If you cross connect your COMMAND line from your slave to INTERRUPT on the main computer, your slave can signal the host it wants to send data. In fact it is possible to do networking or timesharing!
You can produce an 850 emulator with four serial ports from the joysticks. Have you ever seen an 850 with a built-in 46K buffer? Or perhaps a buffer between your modem which can run out of the joystick (already available commercially).
You can even create your own new kind of peripheral such as a large-scale security system with several "X:" devices all checking in with a central Atari computer.
I could keep going on and on, but I am going to leave it up to you, the readers, to come up with the ideas. Please send them in. Let's create a Zucchini net.
How It WorksIf you don't want to know all the details of how Zucchini works, you can stop here, but if you have that insatiable desire to know how things work, or if you plan to modify the program then read on.
The first part of the program is all housekeeping and initialization. It sets up memory pointers and changes interrupt vectors to point to our own routines. ANTIC DMA is turned off, and we replace the keyboard handler with one of our own to detect the escape key. In addition we insert our own SIO interrupt handlers. After the initialization the printer is tested and the beep routines are used to signal readiness. RAM is partitioned with the program starting at $0700 or Page 7, the holding buffer at Page 6 and the remaining RAM above the program for the main buffer. RAMTOP is used as the top end pointer and LOMEM the bottom (LOMEM was moved during the boot to point to the end of the program).
The main printer loop has two pointers which control the flow of data. Refer to the Zucchini flow chart.
BUFEND points to the last byte loaded from the host computer and BUFPNT points to the current byte to be printed. So long as BUFPNT equals BUFEND the loop will idle. When new data is loaded by the receive routines, BUFEND is pushed up in RAM, and the printer loop begins transfer to the printer until the pointers are the same again. When the COMMAND line goes to 0 signaling a COMMAND frame, it causes an IRQ interrupt because of the connections we made in the cables and the new interrupt handlers. This interrupt sets the RCVFLG to 1. This flag will signal the main loop to break out and go to the RECEIV loop. There are two places in the main loop where this flag is tested: the idle loop at the beginning and the printer wait loop. If the flag is set at either point then the RECEIV loop is entered.
Receive LoopThe buffer size is set for four bytes and POKEY initialized for reception. An idle loop is entered and Zucchini waits until the RECeive COMplete flag is set indicating that all four bytes and checksum have been received. Then the PA1 interrupt is altered to respond to the positive transition of COMMAND as it returns to Logic 1. Once the command frame has been received the program tests for the proper device ID number. If wrong, then you get dumped back to the main loop and there is no response to the host computer. If the ID is correct, then the program sends an ACK and tests for WRITE or STATUS. If it is a status command then a separate routine is entered that sends a CMP followed by the status frame of five bytes after which control returns to the main loop. If a WRITE was called for, then the program moves to the next phase. POKEY is then set up for a 40-byte data frame, and then you go into another holding loop until RECOMP is set again. If there are no errors in data reception, then an ACK is sent and the data is moved to the main buffer.
As each byte is moved, a temporary pointer is advanced until the whole 40 bytes is transferred, an EOL is encountered, or you run out of free buffer space. If the process can be completed then BUFEND is set to the value of the temporary pointer and a CMP byte is sent. If the main buffer gets filled, then the temporary pointer will be equal to BUFPNT at some point during the transfer process. If this occurs, then the program returns to the main loop without sending the CMP byte or updating BUFEND. The result is that the host computer does a timeout for 28 seconds, during which up to 2000 characters can be emptied from the buffer to make more space. Without updating BUFEND, no data was really transferred to the main buffer. If a pointer runs up to the top of RAM, it is "wrapped around" to the bottom of the buffer RAM. This means that every free byte in the main buffer is always available and as material is emptied from the buffer, more space is made available for use.
Besides the main-line program there are many interrupt routines including the VSERIN, VSEROR, VSEROC and the PA1 or INTERRUPT routines. Additionally, there is a routine to set a stage-one VBLANK timer to a half second for the timeout value so if the host computer blows up in the middle of data transfer, the program will not lock up but return to printing. You can see from this system that only a portion of time spent in data transfer is actually spent in receiving data. So long as both the host and the computer agree, you could change the host's print buffer and your Zucchini to expect 256 byte data frames or do burst I/O. This would greatly increase transfer speed.
The interrupt routines work essentially the same as the stock ones in the O.S., and you could save some RAM by using them. For some reason, though, I always got a checksum error when I used the stock-receive interrupt but the stock send routines work okay. The description of how these interrupts work was covered in last month's article. One additional interrupt program uses POKEY's keyboard interrupt vector and senses a pressing of the ESCAPE key setting the flag to produce graphics from the text for program listings. The graphics program itself takes the ATASCII code of the text data and obtains the bit values of the letter from the character set in ROM and shuffles it around to produce the 8-bit graphics values for the printer to use. You could have several, different character sets which load from disk at boot time and are selected from the keyboard.
Final NotesIf you are a hardware genius and you have an EPROM programmer, you could place Zucchini into an EPROM and put it in cartridge to plug into the computer, since Zucchini does not need any language. But how you assemble a program into the same space as your assembler while the assembler is running is one problem I have yet to figure out! Of course you would lose 6-7K of buffer memory because a cartridge locks out 8K of RAM even though the program uses 1 K. You can increase your buffer size slightly by using PRNBUF, the normal printer buffer at $03C0, for temporary buffer and beginning assembly at $0480 thus gaining about 600 bytes. However, you cannot use any floating point math or basic. But then you should not have the BASIC cartridge in place when you boot up Zucchini.
This program was originally intended for use with a 400/800 computer but can be used with an XL or XE: read the addendum attached. I hope you found Zucchini as interesting and exciting as I have. I also hope you can find many new recipes. Next month we will pick up some of the remaining loose ends of the serial port and show you a few tricks with the cassette player.
858 E. Congress Park Dr.
Centerville, OH 45459-40721
Atari Serial Cable, 6 ft. #83-365 $5.80
Atari Serial Plug only #83-360 $1.20
Joystick Plug & Cord #83-070 $2.05
Atari Serial P.C. Socket #83-140 $1.45
36 Pin Centronics male #83-310 $1.95
905 S. Vermont Ave.
Los Angeles, CA 90006
Carries all parts except the Serial plugs and cables. D1, D2, IC1, IC2, and R1 should be available at any electronics store or TV shop.
XL/XE AddendumIf you happen to have a leftover XL or XE computer you can take advantage of the extra memory available to you. In the XL/XE you can switch out the O.S. ROM and have a large extra block of RAM available. Your program will have to work around the hardware registers though. This is okay because we really don't use any of the O.S. except the character set and the interrupts. The XE will do the same thing but also has its 64K of extra memory banks. Along with the advantages, you have several problems to contend with. First is the question of joysticks. In these computers PORTB is not available externally and is used internally to control memory bank selection. That leaves you with only two joysticks and no strobe. So since you have only eight output bits you must do one of three things:
1) Use seven bits for data and one for strobe. This is okay for text but prevents you from printing italics or special graphics characters depending on your printer. Also most graphic dump programs are designed for eight bits, not seven. Clearly this is not a desireable solution.
2) Rewire your Zucchini interface to free the slave's COMMAND line to use as a strobe. Once Zucchini is booted the COMMAND line is not needed and can be diverted to the printer cable and be used as the strobe. You will also need to change any programming relating to PBCTL; otherwise you could really muck up things. PBCTL will then control memory banks and so on. You will not be able to drive multiple printers with this configuration.
3) Use the 8-bit port adapter in ANALOG #44, July 1986. This device also has the advantage of allowing two printers to be used.
The second problem to overcome is that once the O.S. is turned off to reveal the underlying RAM, you have no interrupt handlers, and so none of the Zucchini software can work without crashing. You need to supply the interrupt vectors in the last eight bytes of RAM to point to your own interrupt processors and change all the global interrupt vectors for the IRO interrupts. If you have a copy of the O.S. source code, you are home free, so get a copy. Then you need to set RAMTOP to somewhere below your routines and turn off the BASIC and MATH ROMs. You can bypass much of this by disabling the NMI and IRQ while accessing the RAM for printing or data transfer, then returning to the normal O.S. to process the interrupts. Using these techniques you can use a 130XE with about 120 K of RAM available for buffer space. Now where in the world can you get any other printer buffer with this capacity for $150?
WN 0 REM ******************************
QC 1 REM * ZUCCHINI PRINTER INTERFACE *
WZ 2 REM * by Lee S. Brilliant M.D. *
YB 3 REM * Converts a 4001800 to an *
ML 4 REM * interface/buffer. *
WS 5 REM ******************************
NL 6 REM
WI 10 DATA 0,10,0,7,6,7,169,177,141,231,2
NH 20 DATA 169,26,133,10,169,7,133,11,24,
FD 30 DATA 166,11,141,172,11,141,170,11,1
MA 40 DATA 141,0,211,169,52,141,2,211,169
IJ 50 DATA 3,211,162,0,169,120,141,4,2,16
YY 60 DATA 141,10,2,169,9,141,11,2,169,10
EL 70 DATA 15,2,169,41,141,12,2,169,10,14
VL 80 DATA 2,169,10,141,9,2,173,231,2,133
YM 90 DATA 2,133,204,141,157,11,169,40,14
CH 100 DATA 32,127,10,173,16,208,240,13,1
KO 110 DATA 195,10,76,144,7,169,255,32,19
DK 120 DATA 240,6,32,229,8,76,173,7,165,2
IJ 130 DATA 204,205,157,11,208,14,173,165
PB 140 DATA 11,76,173,7,165,204,197,106,1
NB 150 DATA 232,2,133,204,76,173,7,230,20
XR 160 DATA 240,3,76,34,8,160,0,177,203,2
VM 170 DATA 5,8,76,173,7,141,0,211,173,16
IV 180 DATA 240,246,32,229,8,76,8,8,206,1
YJ 190 DATA 234,96,160,0,140,163,11,177,2
DV 200 DATA 172,160,11,208,3,76,249,7,169
AE 210 DATA 288,250,238,160,11,173,160,11
LK 220 DATA 160,11,208,11,162,3,189,173,1
GA 230 DATA 171,11,16,8,41,127,141,171,11
JD 240 DATA 201,32,144,6,24,105,224,76,12
EY 250 DATA 133,202,162,3,6,201,38,202,20
AQ 260 DATA 202,133,202,169,0,141,171,11,
DW 270 DATA 7,177,281,24,45,155,11,240,1,
EZ 280 DATA 78,155,11,173,171,11,77,163,1
WP 290 DATA 160,11,173,160,11,201,38,208,
DV 300 DATA 32,5,8,76,173,7,104,170,104,1
EU 310 DATA 5,141,3,211,96,152,72,138,72,
LK 320 DATA 11,32,174,9,169,7,141,3,211,1
PE 330 DATA 11,208,15,173,172,11,240,246,
OP 340 DATA 240,196,173,0,6,201,64,240,3,
HN 350 DATA 87,240,23,201,83,208,6,32,21,
AN 360 DATA 230,10,76,169,9,160,65,76,230
AX 370 DATA 159,11,32,174,9,172,170,11,24
MD 380 DATA 169,9,32,230,10,76,214,8,32,5
KJ 390 DATA 156,11,133,205,173,157,11,133
LT 400 DATA 205,197,203,208,9,165,206,197
DG 410 DATA 197,106,144,13,173,231,2,133,
VE 420 DATA 9,189,0,6,232,145,205,201,155
GW 430 DATA 205,141,15f%,11,165,206,141,15
KV 440 DATA 141,158,11,141,162,11,141,161
QL 450 DATA 88,11,173,167,11,208,10,173,1
TK 460 DATA 11,32,136,11,96,152,72,173,15
JJ 470 DATA 140,140,170,11,41,32,208,5,16
UI 480 DATA 240,20,173,13,210,205,161,11,
IV 490 DATA 167,11,104,168,104,64,173,13,
JH 500 DATA 109,161,11,105,0,141,161,11,2
TN 510 DATA 225,169,1,141,158,11,76,2,10,
EK 520 DATA 11,204,159,11,144,17,173,168,
GI 530 DATA 16,141,14,210,76,86,10,185,0,
UX 540 DATA 11,105,0,141,161,11,104,168,1
KR 550 DATA 169,1,141,168,11,76,86,10,169
JT 560 DATA 247,141,14,210,133,16,104,64,
YQ 570 DATA 154,141,38,2,169,10,141,39,2,
HD 580 DATA 32,92,228,169,0,141,172,11,88
QQ 590 DATA 173,9,210,201,28,208,19,173,1
QZ 600 DATA 186,10,238,165,li,i69,0,141,1
DH 610 DATA 208,250,96,141,159,11,141,162
NO 620 DATA 141,31,208,32,188,10,140,31,2
OF 630 DATA 206,159,11,208,228,96,152,72,
TW 640 DATA 141,162,11,32,123,11,160,2,14
HN 650 DATA 104,168,140,13,210,173,169,11
VN 660 DATA 204,9,76,209,9,160,65,32,230,
GI 670 DATA 128,172,170,11,240,2,169,129,
HJ 680 DATA 6,169,31,141,2,6,169,4,141,15
UC 690 DATA 141,162,11,141,169,11,141,168
RS 700 DATA 13,210,141,161,11,76,5,11,169
SR 710 DATA 141,10,210,160,224,132,16,140
LM 720 DATA 6,169,160,157,1,210,202,202,1
EC 730 DATA 50,2,141,15,210,76,101,11,169
VH 740 DATA 0,162,6,157,1,210,202,202,16,
XM 750 DATA 0,0,1,0,0,0,0,0,0,0,0,0,0,1,4
BG 760 DATA 27,169,10,141,162,11,169,1,14
KU 770 DATA 169,8,141,4,3,169,7,141,5,3,1
UX 780 DATA 8,141,11,3,32,83,228,48,30,17
CA 790 DATA 4,3,173,5,3,185,8,141,5,3,238
NJ 800 DATA 11,3,206,162,11,208,221,104,9
UU 1000 ? "":FOR LINE=10 TO 800 STEP 10:
TOTAL=0:? "TESTING LINE ";LINE:FOR N=1
TO 16:READ D:TOTAL=TOTAL+D:NEXT N
IB 1010 READ D:IF TOTAL=D THEN 1030
YM 1020 ? " ** ERROR IN LINE ";LINE
DD 1030 CKSUM=CKSUM+TOTAL:NEXT LINE:IF CK
SUM<>134459 THEN ? " ** CHECSUM ER
KG 1040 ? " DATA IS CORRECT. INSERT
BLANK DISK AND PRESS .":? :? "
GG 1050 POKE 764,255
YS 1060 IF PEEK(764)<>12 THEN 1060
HC 1070 ? :? " ":XIO
254,#1,0,0,"D:":? :? "
YZ 1200 RESTORE :POKLOC=1792:FOR TIME=1 T
0 80:FOR S=1 TO 16:READ D:POKE POKLOC,
KN 1210 NEXT S:READ D:GOSUB 2000:NEXT TIM
E:? :? " ":A=USR(299
3):? :? " *** DONE ***":END
AS 2000 IF PEEK(755)=2 THEN POKE 755,0:RE
WR 2010 POKE 755,2:RETURN
28 .TITLE ZUCCHINI PRINTER
INTERFACE BUFFER CONVERSION
50 ;* BY LEE BRILLIANT M.D. *
98 ;PROGRAM CONVERTS A 400/800
95 ;COMPUTER TO A PRINTER INTERFACE-
0100 ;BUFFER AND PROGRAM LISTER.
0110 ;PRESS ESC TO CHANGE TO GRAPHICS
0115 ;PRINTING OF TEXT.
0130 ;USES SERIAL PORT TO CONNECT WITH
0135 ;MAIN SYSTEM.
0140 ;USES JOYSTICK PORTS TO INTERFACE
0145 ;WITH PRINTER.
0180 ;* RAM ASSIGNMENTS *
0210 AUDC1 = $D201
0220 AUDCTL = $D288
0230 AUDF3 = $D204
0240 AUDF4 = $D206
0250 BUFF = $0600
0260 BUFPNT = $CB
0270 CDTMAI = $0226
0280 CHBAS = $02F4
0290 CHLOC = $C9
0360 CIOV = $E456
0310 CONSOL = $D01F
0320 CRITIC = $42
0330 DOSVEC = S0A
0340 ICCOM = $0342
0350 IROEN = $020E
0360 KBCODE = $D209
0370 MEMLO = $02E7
0380 PACTL = $D302
0390 PBCTL = $D303
0400 POKMSK = $10
0410 PORTA = $D300
0420 PORTS = $D301
0438 RAMTOP = $6A
0440 SDMCTL = $022F
0450 SERIN = $D20D
0460 SEROUT = $D20D
0470 SETVBV = $E45C
0480 SKREST = $D20A
0490 SKCTL = $D20F
0580 SKSTAT = SD20F
0510 SSKCTL = $0232
0520 TBUFPT = $CD
0530 TRIGS = $D010
0540 VINTER = $0204
8550 VKEYBD = $0208
0560 VSERIN = $020A
0570 VSEROC = $020E
0580 VSEROR = $020C
8620 ;* VALUES *
0660 ACK = $41
0670 CHKERR = $8F
8680 CMPLET = $43
0690 EOL = $9B
0700 ERR = $45
0710 FRMERR = $8C
0720 LENGTH = $26
0730 NAK = $4E
0740 OVRRUN = $8E
0750 TOUTER = $8A
0770 ORIGIN = $6600
0810 ;* BEGIN PROGRAM *
0840 *= ORIGIN
0860 .BYTE 0 ;HEADER
0865 ; NUMBER OF SECTORS TO BOOT
0870 .BYTE ENDPRO-ORIGIN+127/128
0880 .WORD ORIGIN ;BOOT LOCATION
0890 .WORD RESET ;INIT WARMSTART
0915 LDA #ENDPRO&255 ;RESRVE SPACE
0920 STA MEMLO ;FOR PROGRAM
0930 LDA #ENDPRO/256
8940 STA MEMLO+1
0950 LDA #START&255 ;PLACE IN
0955 ; RESET CHAIN
0960 STA DOSVEC
0970 LDA #START/256
0980 STA DOSVEC+1
1010 START LDA #0 ;INITIALIZE O.S.
1020 STA SDMCTL
1030 STA RCVFLG ;ZERO ALL FLAGS
1040 STA TIMFLG
1850 STA STATUS
1060 STA PACTL ;RESET PORTS
1070 STA PBCTL
1080 LDA #255 ;ALL PINS OUTPUT
1090 STA PORTA
1100 LDA #52 ;FIX OUTPUTS AND
1105 ; SET INTERRUPT
1110 STA PACTL
1120 LDA #1
1130 STA PORTB ;ONE PIN OUT FOR
1135 ; STROBE
1140 LDA #5 ;FIX OUTPUTS AND
1145 ; SET INTERRUPT
1150 STA PBCTL
1160 LDX #0
1170 LDA #COMINT&255 ;CHANGE
1175 ; INTERRUPT VECTORS
1180 STA VINTER ;FOR COMMND FRAME
1190 LDA UCOMINT/256
1200 STA VINTER+1
1210 LDA URCVINT&255
1220 STA VSERIN ;SERIAL IN READY
1230 LDA URCVINT/256
1240 STA VSERIN+1
1250 LDA USNDINT&255
1260 STA VSEROC ;SERIAL OUT
1265 ; COMPLETE
1270 LDA #SNDINT/256
1280 STA VSEROC+1
1290 LDA #SNDFRM&255
1300 STA VSEROR ;SERIAL OUT RESET
1310 LDA #SNDFRM/256
1320 STA VSEROR+1
1330 LDA #KEYBD&255
1340 STA VKEYBD ;KEYBOARD
1345 ; INTERRUPT VECTOR
1350 LDA VKEYBD/256
1360 STA VKEYBD+1
1370 LDA MEMLO ;SET BUF POINTERS
1380 STA BUFPNT
1390 STA BUFEND
1400 LDA MEMLO+1
1410 STA BUFPNT+l
1420 STA BUFEND+1
1430 LDA #$28 ;SERIAL BAUD RATE
1440 STA AUDF3
1450 LDA #$00
1460 STA AUDF4
1470 PRNTON JSR SETVBX ;SET 1/2
1475 ; SECOND TIMEOUT
1480 PR1 LDA TRIG0 ;IS PRINTER ON?
1490 BEQ BEEP ;YES 50 BRANCH
1500 LDA TIMFLG ;WAIT FOR TIMEOUT
1510 BEQ PR1
1520 LDA #$A0 ;SET BEEP TONE
1530 JSR TONE
1540 JMP PRNTON ;TRY AGAIN
1550 BEEP LDA #$FF ;READY BEEPER
1560 JSR TONE
1570 STA COUNT
1610 ; MAIN LOOP FOR PRINTER
1640 MNLOOP LDA RCVFLG ;INCOMING DATA?
1650 BEQ TSTPNT
1660 JSR RECEIV
1670 JMP MNLOOP
1680 TSTPNT LDA BUFPNT ;ALL DATA SENT?
1690 CMP BUFEND ;COMPARE END DATA
1695 ; WITH CURRENT POINTER
1700 BNE TSTEND
1710 LDA BUFPNT+1
1720 CMP BUFEND+1
1730 BNE TSTEND
1740 LDA GRWANT ;GRAPHIC OR TEXT?
1750 CMP GRFLG
1760 BEQ MNLOOP
1770 STA GRFLG ;YES, SET FLAG
1780 JMP MNLOOP ;NO, GO BACK
1790 TSTEND LDA BUFPNT+l ;END OF RAM?
1800 CMP RAMTOP
1810 BCC INCPNT ;OK
1820 LDA MEMLO ;MOVE POINTERS
1825 ; TO START OF RAM
1830 STA BUFPNT
1840 LDA MEMLO+1
1850 STA BUFPNT+1
1860 JMP MNLOOP ;LOOP BACK
1870 INCPNT INC BUFPNT ;INC POINTER
1880 BNE GRTST
1890 INC BUFPNT+1
1900 GRTST LDA GRFLG ;GRAPHICS?
1910 BEQ GETCH ;NO, PRINT TEXT
1920 JMP GRPRNT ;YES, GRAPHICS
1930 GETCH LDY #0 ;GET A CHARACTER
1940 LDA (BUFPNT),Y
1950 PREOL CMP #EOL ;CONVERT ATASCII
1955 ; EOL TO PROPER EOL
1960 BNE PRNTC1
1970 LDA #$0D
1980 PRNTC1 JSR PRINT ;PRINT & RETURN
1990 JMP MNLOOP
2000 PRINT STA PORTA ;BITS TO PRINTER
2010 WTBUSY LDA TRIGS ;PRINTER BUSY?
2020 BEQ PRNTCH ;NO, BRANCH
2830 LDA RCVFLG ;YES, TEST FOR
2035 ; INCOMING DATA
2040 BEQ WTBUSY
2850 JSR RECEIV ;GO RECEIVE
2860 JMP WTBUSY
2070 PRNTCH DEC PORTB ;SET STROBE
2080 NOP ;WAIT FOR PRINTER
2090 INC PORTB ;RESET STROBE
2110 DELAY NOP ;DUMMY DELAY
2130 GRPRNT LDY #0 ;PRINT TEXT
2135 ; AS GRAPHICS
2140 STY INVFLG
2150 LDA (BUFPNT),Y ;GET A CHAR
2160 STA TEMP
2170 CMP #EOL ;EOL?
2180 BNE TSTCNT ;NO BRCH TO PRINT
2190 LDY CCOUNT ;YES-LINE FULL?
2200 BNE LINFIL ;NO SO FILL
2210 JMP PREOL ;YES SO PRINT EOL
2220 LINFIL LDA #0 ;FILL OUT PRINTER
2225 ; LINE WITH 0s
2230 LDX #8
2240 FILOOP JSR PRINT ;8 0'S PER CHAR
2260 BNE FILOOP
2270 INC CCOUNT
2280 LDA CCOUNT
2290 CMP #LENGTH ;ALL LINE SENT?
2300 BNE LINFIL
2310 JMP EXIT
2320 TSTCNT LDA CCOUNT ;NEW LINE?
2330 BNE CONVRT ;NO 50 BRANCH
2340 LDX #3
2350 SNDCOD LDA CODEX ;SEND ESCPE SEQ
2360 JSR PRINT
2380 BPL SNDCOD
2390 CONVRT LDA TEMP ;ASCII TO ATASCII
2400 BPL CONVR1
2410 AND #$7F
2420 STA TEMP
2430 DEC INVFLG
2440 CONVR1 CMP #96
2450 BCS X8
2460 CMP #32
2470 BCC ADD64
2490 ADC #224
2500 JMP X8
2510 ADD64 ADC #64
2520 X8 STA CHLOC ;MULTIPLY X8 TO
2525 ; GET CHSET OFFSET
2530 LDA #0
2540 STA CHLOC+1
2550 LDX #3
2560 ROT ASL CHLOC
2570 ROL CHLOC+1
2590 BNE ROT
2610 LDA CHBAS ;ADD TO CHBASE TO
2615 ; FIND LOC. IN RAM
2620 ADC CHLOC+1
2630 STA CHLOC+1
2640 SHIFT LDA #0 ;CREATE NEW BYTE
2645 ; FROM BITS OF
2650 STA TEMP ;EACH BYTE IN CHR
2660 LDA #$88
2670 STA BITMSK ;SELECT WHICH BIT
2680 LDX #7
2690 SLOOP1 LDY #7
2700 SLOOP2 LDA (CHLOC),Y ;SAME BIT IN
2705 ; EACH BYTE
2720 AND BITMSK
2730 BEQ NXTBIT
2750 NXTBIT ROR TEMP
2770 BPL SLOOP2
2780 LSR BITMSK ;NEXT BIT
2790 LDA TEMP
2800 EOR INVFLG ;INVERSE CHAR?
2810 JSR PRINT
2830 BPL SLOOP1
2840 INC CCOUNT
2850 LDA CCOUNT ;ALL CHARS SENT?
2860 CMP #LENGTH
2870 BNE EXIT2
2880 EXIT LDA #0 ;RESET CCOUNT
2890 STA CCOUNT
2900 LDA #$0D ;SEND EOL
2910 JSR PRINT
2920 EXIT2 JMP MNLOOP
2960 ;* RETURN *
2990 RETURN PLA ;RESTORE REGS
3030 LDA #0
3040 STA RCVFLG ;ZERO FLAG
3050 LDA #5
3060 STA PBCTL ;RESTORE VINTER
3110 ;* RECEIVE ROUTINE *
3150 RECEIV TYA ;RECEIVE A DATA
3155 ; FRAME
3160 PHA ;SAVE REGISTERS
3190 JSR SETVBX ;SET TIMEOUT
3200 LDA #4
3210 STA BUFSIZ ;# OF BYTES IN
3215 ; IN COMMAND FRAME
3220 JSR GETFRM
3230 CNGINT LDA #7 ;CHANGE VINTER TO
3235 ; RESPOND
3240 STA PBCTL ;TO + TRANSITION
3250 LDA #0
3260 STA RCVFLG
3270 INTWAT LDA RCVFLG ;WAIT FOR END
3275 ; OF COMMAND FRAME INTERRUPT
3280 BNE TSTDEV ;YES SO BRANCH
3290 LDA TIMFLG ;TIMEOUT?
3300 BEQ INTWAT ;NO-KEEP WAITING
3310 JMP RETURN ;YES-RETURN
3320 LDY STATUS ;OPERATION OK?
3330 CPY #CHKERR ;YES SO BRANCH
3340 BEQ RETURN ;ERROR SO GO BACK
3350 TSTDEV LDA BUFF ;FIRST BYTE IS
3355 ; DEVICE ID
3360 CMP #$40 ;RIGHT DEVICE ID?
3370 BEQ THISDV ;YES SO BRANCH
3380 JMP RETURN ;NO SO RETURN
3390 THISDV LDA BUFF+1 ;CHECK COMMAND
3400 CMP #$57 ;WRITE?
3410 BEQ FRMOK ;YES SO GO ON
3420 CMP #$53 ;STATUS?
3430 BNE WNGCOM ;NO-WRONG COMMAND
3440 JSR GDSTAT ;SEND STAT FRAME
3450 JMP RETURN ;BACK TO MNLOOP
3460 WNGCOM LDY #NAK ;WRONG COMMAND
3470 JSR SDSTAT
3480 JMP COMPLT ;SEND A COMPLETE
3485 ; AND RETURN
3490 SNDACK LDY #ACK ;SEND AN ACK
3500 JMP SDSTAT
3510 FRMOK JSR SNDACK ;COMMND FRAME OK
3520 LDA #40 ;DATA BUF SIZE=40
3530 STA BUFSIZ
3540 JSR GETFRM ;GO RECEIVE FRAME
3550 LDY STATUS
3560 BEQ MOVBUF ;FRAME OK-BRANCH
3570 LDY #ERR
3580 JSR SDSTAT
3590 JMP COMPLT
3600 SNDSTS JSR SDSTAT ;NO 50 SEND
3605 ; ERROR AND RETURN
3610 JMP RETURN
3620 MOVBUF JSR SNDACK ;ACK FRAME
3630 LDY #0
3640 LDX #0
3650 LDA BUFEND ;SET TEMP POINTER
3655 ; TO END OF MAIN BUFFER
3660 STA TBUFPT
3670 LDA BUFEND+1
3680 STA TBUFPT+1
3690 INBFPT INC TBUFPT
3700 BNE MOVBF1
3710 INC TBUFPT+1
3720 LDA TBUFPT ;MAIN BUFF FULL?
3730 CMP BUFPNT
3740 BNE MOVBYT ;NO SO BRANCH
3750 LDA TBUFPT+1
3760 CMP BUFPNT+1
3770 BNE MOVBYT
3780 JMP RETURN ;MAIN BUFFER
3785 ; FULL SO RETURN
3790 ;RETURN WITHOUT CMP SENT CAUSES
3795 ;HOST TO TIMEOUT FOR UP TO 56
3800 ;THIS GIVES TIME FOR BUFFER TO
3805 ;EMPTY SOME.
3810 ;CAN BE CHANGED BY ALTERING
3815 ;COMMAND RESPONSE FRAME BYTE 3
3820 MOVBYT LDA TBUFPT+1 ;END OF RAM?
3830 CMP RAMTOP
3840 BCC GETBYT ;NO SO BRANCH
3850 LDA MEMLO ;RESET POINTER
3855 ; TO START OF BUFFER
3860 STA TBUFPT
3870 LDA MEMLO+1
3880 STA TBUFPT+1
3890 JMP MOVBF1
3900 GETBYT LDA BUFF,X ;MOVE A BYTE
3905 ; FROM TEMP
3920 STOBYT STA (TBUFPT),Y
3930 CMP #EOL ;CARRIAGE RETURN?
3940 BEQ ENDMOV ;YES-STOP DATA
3950 CPX #40 ;NO THEN WHOLE
3955 ; BUFF MOVED?
3960 BNE INBFPT ;NO-GET NEXT BYTE
3970 ENDMOV LDA TBUFPT ;YES SO UPDATE
3975 ; BUFFER POINTERS
3980 STA BUFEND
3990 LDA TBUFPT+1
4000 STA BUFEND+1
4010 COMPLT LDY #CMPLET ;SEND COMPLETE
4015 ; BYTE, AND RETURN
4020 JMP SNDSTS
4040 ; GET A FRAME
4070 GETFRM LDA #0 ;GET A DATA FRAME
4080 STA BUFRFL ;ZERO VARIABLES
4090 STA COUNT
4100 STA CHKSUM
4110 STA RECOMP
4120 STA STATUS
4130 JSR RECVEN ;ENABLE RECEIVE
4140 CKTIME LDA RECOMP ;RECV COMPLETE?
4150 BNE GOBACK ;YES, RETURN
4160 LDA TIMFLG ;TIMEOUT?
4170 BEQ CKTIME ;NO SO WAIT AGAIN
4180 TIMOUT LDA #TOUTER ;TIMEOUT
4190 STA STATUS
4200 GOBACK JSR DISABLE
4250 ; INTERRUPT ROUTINES
4270 ;RECEIVE INTERRUPT DRIVEN
4275 ; BY VSERIN
4290 RCVINT TYA ;EACH TIME THERE
4295 ; IS A BYTE READY
4300 PHA ;THIS ROUTINE
4305 ; EXECUTES.
4310 LDA SKSTAT ;CHECK FOR SERIAL
4315 ; ERRORS
4320 STA SKREST ;RESET ERR REGS
4330 BMI NTFRM
4340 LDY #FRMERR
4350 STY STATUS
4360 NTFRM AND #$20
4370 BNE NTOVRN ;OVERRUN ERR?
4380 LDY #OVRRUN
4390 STY STATUS
4400 NTOVRN LDA BUFRFL
4410 BEQ NOTDON ;TEMP BUF FULL?
4420 LDA SERIN ;CHECKSUM OK?
4430 CMP CHKSUM
4440 BEQ SRETRN
4450 LDY #CHKERR
4460 STY STATUS
4470 SRETRN INC RECOMP ;SET RECEIVE
4475 ; COMPLETE FLAG
4480 INTDON PLA ;RESTORE Y
4510 RTI ;RETURN FROM
4515 ; INTERRUPT
4520 NOTDON LDA SERIN ;GET A BYTE
4530 LDY COUNT
4540 STA BUFF,Y ;PUT IN TEMP BUF
4560 ADC CHKSUM ;TOTAL CHECKSUM
4570 ADC #0
4580 STA CHKSUM
4600 STY COUNT ;INC BUFF COUNTER
4610 CPY BUFSIZ
4620 BMI INTDON ;BUFF FULL?
4630 LDA #1 ;YES SET FLAG
4640 STA BUFRFL
4650 JMP INTDON
4680 ;SERIAL OUTPUT DATA REQUEST
4690 ;DRIVEN BY VSEROR
4700 SNDFRM TYA ;SAVE Y
4720 INC COUNT ;ALL DATA OUT?
4730 LDY COUNT
4740 CPY BUFSIZ
4750 BCC ENDSND ;NO SO SEND
4760 LDA CHKSNT ;CHECKSUM SENT?
4770 BEQ SNDCHK ;NO SO SEND
4780 LDA POKMSK ;YES SO CHANGE
4785 ; INTERRUPTS
4790 ORA #$08
4800 STA POKMSK
4810 STA IRGEN
4820 JMP IRETRN
4836 ENDSND LDA BUFF,Y ;SEND A BYTE
4840 STA SEROUT
4860 ADC CHKSUM ;ADD TO CHECKSUM
4870 ADC #0
4880 STA CHKSUM
4890 IRETRN PLA ;RETURN FROM
4895 ; INTERRUPT
4930 SNDCHK LDA CHKSUM ;SEND CHECKSUM
4940 STA SEROUT
4950 LDA #1 ;AND SET FLAG
4960 STA CHKSNT
4970 JMP IRETRN
4990 ;SERIAL OUTPUT INTERRUPT
5000 ;DRIVEN BY VSEROC
5010 SNDINT LDA #1 ;SETS FLAG AFTER
5015 ; CHECKSUM SENT
5020 STA SNDFLG
5030 LDA POKMSK ;RESTORE INTRRPTS
5040 AND #$F7
5050 STA IRQEN
5060 STA POKMSK
5118 ;COMMAND FRAME INTERRUPT
5120 ;DRIVEN BY VINTER
5130 COMINT LDA #1 ;SET FLAG WHEN
5135 ; WHEN COMMAND FRAME SET
5140 STA RCVFLG
5190 ;SET TIMEOUT INTERRUPT
5210 LDA #STIMOT&255 ;SET POINTER
5215 ; TO ROUTINE
5220 STA CDTMA1
5230 LDA #STIMOT/256
5240 STA CDTMAI+1
5250 LDX #0 ;SET VB INTERRUPT
5260 LDY #30 ;1/2 SECOND WAIT
5270 LDA #1
5290 JSR SETVBV
5300 LDA #0
5310 STA TIMFLG ;ZERO TIMEOUT FLG
5350 STIMOT LDA #1 ;SETS FLAG AFTER
5355 ; TIMEOUT
5360 STA TIMFLG
5400 ;NEW KEYBOARD INTERRUPT HANDLER
5410 ;SETS FLAG ONLY IF ESC PRESSED
5420 KEYED LDA KBCODE ;GET ICODE
5425 ; FOR KEYPRESS
5430 CMP #28 ; ESCAPE?
5440 BNE KRETRN ;NO, RETURN
5460 BEQ WANTGR
5470 DEC GRWANT
5480 JMP KRETRN
5490 WANTGR INC GRWANT
5500 LDA #0
5510 STA CCOUNT ;CHAR COUNT=0
5520 KRETRN PLA
5570 CONTDN DEK ;SHORT INTERVAL
5575 ; TIMER
5580 BNE CONTDN
5600 BNE CONTDN
5640 ;SPEAKER TONE ROUTINE
5650 TONE STA BUFSIZ
5660 STA COUNT
5670 TONE1 LDX COUNT ;SET VALUES
5680 LDY #1 ;FOR TIMER
5690 LDA #255
5700 STA CONSOL ;MOVE SPEAKER
5710 JSR CONTDN ;TIME DELAY
5720 STY CONSOL ;MOVE OTHER WAY
5740 LDX COUNT ;DELAY
5750 JSR CONTDN
5770 BNE TONE1 ;KEEP GOING
5850 SDSTAT TYA ;SAVE Y
5880 STA SNDFLG
5890 STA BUFSIZ
5900 STA COUNT
5910 JSR SENDEN ;ENABLE POKEY
5920 LDY #2 ;1 MILLISECOND
5925 ; DELAY
5930 STY CHKSNT