PET To PET Communication Over The User Port
John Winn
Department of Chemistry
University of California at Berkeley
If you (or you and a friend) have access to two PETs, you may have wanted to connect the two together and transfer data from one to the other. The built-in IEEE bus is not suitable, since each PET is a bus controller and the rules allow only one controller on the bus. You could buy any of a number of attachments for serial, parallel or modem input/output, but the simplest method is to interconnect the PET's through the built-in parallel user port. Here's how it's done, using fairly simple BASIC and twelve wires.
First, what hardware is required? The user port connections are on the bottom row of the PC output edge connector. Looking at the rear of the PET, these are labelled A through N with keying slots sawed between A and B and between L and M. A and N are ground connections. C through L are the eight parallel data lines. Each will correspond, in effect, to one of the eight bits in a memory byte. Connection B is called "CA1"; it will be used to signal the presence of data to be read by the receiving PET. Connection M, called "CB2," will control (signal) CA1 on the other PET. (How this is done will be clearer later on.) To connect the two ports together, use two edge connector plugs, wiring A to A, N to N, C through L to C through L, but wire B on one connector to M on the other and vice versa (i.e. CA1 on one to CB2 on the other). The total length of the cable should not be more than about 20 feet. (Longer distances would require external "line drivers" to keep the signal from degrading.)
To control these dozen wires, various PEEKs and POKEs are used. One PET will transmit, and the other will receive at any one time, although each can do both. To send one byte, the transmitter will first activate the eight data lines. Then it will signal the receiver that the byte is set to be read. The receiver will read the byte and signal back to the transmitter that it has done so and is ready for the next.
Suppose we want to send one character from one PET to the other. Program 1 gives the program for the transmitter and Program 2, for the receiver. Line 20 in each program shows how the direction of data transfer is controlled. Line 40 of the transmitter program shows how one byte (ASC(A$)) is placed on the data lines. Meanwhile, the receiver is stuck on its line 40, waiting for bit two of memory location 59469 to be a one instead of a zero. This transition will signal the receiver that it can read the data lines. The signal is sent (from CB2 of the transmitter to CA1 of the receiver) by lines 60 and 70 of the transmitter program. Line 60 forces the three most significant bits of memory location 59468 to be ones. (The other bits are unchanged.) Line 70 forces the third most significant bit back to zero, forcing the first two to be ones and leaving the low order five bits (which are used for other things) as they were. This sequence turns CB2 on, then off.
stuck on line 90 waiting for the receiver to signal back that it has read the data. The receiver signals with lines 70 and 80. It then prints the received character on its screen and goes after another byte. The transmitter will get the signal and ask for another character to send, and the process will repeat.
Most applications will involve the transfer of more than just one character. Transmitting a whole string of many characters or a floating point number requires more elaborate programs, but they will be based on these simple versions. To send a string, the length of the string must be sent first, and then the string can be sent character by character. To send a floating point number, the simplest technique seems to be to use one BASIC variable at a known location in memory as an intermediary buffer, as is done in the programs described below.
You Could WAIT
Two other concerns arise. The first is the initial synchronization of the data transfer. This is perhaps best taken care of by a one byte "preamble" sent at the beginning of the program just to clean out any unsuspected data or transfer signals. The second concern is the ability to interrupt the transmission gracefully should something go wrong. (Along this same line, it is worth pointing out that line 90 of Program 1 and line 40 of Program 2 could be written using WAIT statements. But, since WAITs are not interruptable, except by pulling the plug, this is a bit dangerous.) The easiest way to interrupt a program without stopping it directly is to use the SHIFT key in the way described below.
Programs 3 and 4 give more elaborate programs which send a string of arbitrary length and arbitrary number of random floating point numbers. They both use the SHIFT key to signal an interrupt. (With Original ROM's, location 516 is zero if the SHIFT key is up, and one if it is down. With Upgrade ROM's, it's location 152.) The transmitter sends a preamble — one "%" — to guarantee synchronization. The character is arbitrary, but it should be as unique (or obscure) as possible.
The floating point buffer variable, called QQ in each program, must be the first defined variable of the program. This is so its location in memory can be found easily. At the beginning of variable data storage, one finds two bytes for the two character name of the first variable followed by five bytes representing the floating point number itself. Variables start at memory location 256*PEEK(43) + PEEK(42) in Upgrade ROM's (256*PEEK(125) + PEEK(124) in Original ROM's); hence, variable SQ in each program gives the location, two bytes along from the start, for QQ's five data bytes.
Data are transmitted (or received) in subroutines 1000 and 2000. Starting at 1000 is the subroutine for transmitting or receiving the five bytes of QQ. Transmitting or receiving only one byte (variable D in the program) is done by the subroutine starting at line 2000. Note that this subroutine is called by the first one.
Interruption requires that you hold down the SHIFT key until the program can branch to line 3000. Both the transmitter and the receiver have to be interrupted separately, but either can be interrupted first.
These programs illustrate the main techniques needed for more useful and interesting applications. For many games ("Battleship" comes to mind), the transfer rate of the BASIC code is fast enough, around 10 bytes per second or so.
ML For Fast Transfer
For much greater speed, machine language code is needed. Program 5 is a machine language version of the BASIC code in Programs 3 and 4, implemented in a slightly different way. Line 10 sets up a variable, D%, for receiving single bytes. It must be the first variable defined in the program, and the PEEKs must be changed to 125 and 124 for Original ROM's. The POKE 2,3 statement sets part of the linkage for the USR function. Line 20 POKEs the machine language code into the second cassette buffer. Line 30 puts the address of the low-order byte of D% into this code and sets D% back to zero. (Note: POKEX, PEEK(Y) does not work on Original ROM's. That's why line 30 is written the way it is.) The DATA statements contain the machine language for Upgrade ROM's. For Original ROM's, change the two occurrences (lines 1035 and 1057) of 94 both to 176. They locate the floating point accumulator used by USR.
To set the program into the transmit mode (line 100), POKE 1,91 first to complete the USR linkage for transmission. Next, send a one byte preamble ("%" is used here again) to insure synchronization. To send individual bytes (line 200), POKE them into location 832 and call SYS826. To transmit a floating point number (line 300), pass the number (or variable) as the argument of USR. Since USR has to be set equal to something, it can safely be set equal to the variable being passed or to any other variable which you want to equate to the variable being passed.
Of course, when one program is set up to transmit, the other must be set up to receive. First, (line 400), POKE 1, 139 to complete the USR linkage for reception. Next, look for the preamble and warn yourself (line 440) if it was not received as expected. The FOR-NEXT loop in 420–430 should never go past I = 2. To receive individual bytes (line 500), call SYS873, and find the byte in the variable D%. To receive a floating point number (line 600), equate the variable you wish to input to USR. The argument to USR is not important here, nor is it disturbed if a variable is used.
In most programs, lines 100–120 and 400–440 would best be made subroutines which could be called to switch the program from one mode to the other at will. The main disadvantage of this program is that it cannot be easily interrupted. Data synchronization between the two PETs must be exact or one will finish first, leaving the other hung up. One or more direct SYS826 or SYS873 commands from the un-hung PET will, eventually, clear the other. (Which SYS you use will depend on the state – transmitter or receiver – of the hungup PET.
Transmission Rate
The data rate is quite good. Sending 2000 numbers in a command FOR I = 1 TO 2000: X = USR(I): NEXT takes about 8.6 seconds. That works out to (2000x6)/8.6 = 1400 bytes per second. In this test, the receiver just read the numbers, but did nothing with them. When the receiver stuck the numbers into an array, the time went up to 12.5 seconds.
Finally, if you want to locate the machine language somewhere other than 826 to 917 (or $033A to $0395), the only six numbers in DATA which change are the thirty-ninth (64), fortieth (3), forty-second (58), forty-third (3), eighty-fifth (69), and eighty-sixth (3). These, in pairs, are low and high order absolute address bytes (i.e. 64 + 3*256 = 832). They will have to be changed along with the various POKE locations in BASIC (and the numbers POKEd into locations 1 and 2) if the program is relocated. [It is suggested that 4.0 users move the routine to avoid DOS usage of the bottom of this buffer. — Ed.]