DESIGNING AN IEEE-488 RECEIVER WITH THE SYM
Part 2: Implementing the IEEE-488 Bus on a SYM-1
Larry Isaacs, COMPUTE. Staff
This is the second part of an article describing the use of a SYM-1 to interface a PET to a Spinwriter with a serial interface. We will continue to divide the more complex functions into simpler sub-functions when necessary. Once the sub-functions are simple enough, they will be implemented. In the first part, the interface was divided into four sub-functions: INIT, PRINT, CYCLE, and INTERFACE. Implementations for PRINT and CYCLE have already been presented. Briefly, the PRINT routine handles the communication with the Spinwriter. By using the ETX/ACK protocol, the PRINT routine keeps the Spinwriter printing at its maximum speed. The CYCLE routine handles the handshaking necessary to transfer a byte from the IEEE 488 Bus to the SYM. For convenience, these routines are given again in the complete listing of the interface software found at the end of this article. Also, the hardware to connect the Spinwriter to the SYM is shown again in Figure 2.
Before we can begin work on the INTERFACE sub-function, we must first understand how the PET will try to communicate with the SYM using the IEEE 488 Bus. Now we will continue with a description of this communication procedure.
Communicating On The IEEE 488 Bus
The next step is to become familiar with how the PET communicates on the IEEE Bus. This discussion will involve two more signal lines. These are the ATN (Attention) line and the EOI (End Or Identify) line.
Each communication on the IEEE 488 Bus can be described as a sequence of three parts. In the first part the PET identifies which device it wishes to communicate with. In the second part it sends or receives the data. And finally in third part, the PET terminates the communication sequence. Each part makes use of the byte transfer cycle described previously to transfer information. However, the information transferred in the first and third parts is differentiated from the second by the state of the ATN line. During the first and third parts the ATN line is low, indicating that the bytes transferred should be treated as commands and not data.
Here is a brief description of what happens during a communication sequence with a device, or devices, which only receives data, such as our printer. I will assume that prior to the beginning of the sequence, all devices on the bus are in the inactive state, i.e. the NRFD line is high.
The sequence begins with the PET setting the ATN line low. This brings all operating devices on the bus to the active state. The PET now executes a byte transfer cycle sending the device address to each device. Only those devices whose device address matches the one sent by the PET will continue with the communication sequence. All other devices will return to the inactive state at the end of this first part. The Commodore printers use device address 24 hex. The lower 5 bits contain the device number, in this case 4. The upper three bits, “001”, indicate that the device is to receive data. A “010” in the upper three bits would indicate the device is to send data. Now the PET may end the first part by setting the ATN line high, or transfer another byte known as the secondary address before setting ATN high. The secondary address is used to address different functions or channels within the selected device.
The second part consists of the required number of byte transfer cycles to transfer the data to the device. In most cases, the PET will signal that the last data byte is being transferred by setting the EOI line low during the last cycle. Because the EOI isn't always sent, it wouldn't be a reliable signal to use for determining the end of this part of the communication sequence.
For the third part, the PET sets the ATN line low again, and executes a byte transfer cycle which sends $3F hex to all active devices. This is the UNLISTEN command, which tells all listening devices to stop receiving data.
One requirement for the interface which may not be obvious is that once the communication sequence has reached the second part, all commands except for the UNLISTEN command should be ignored. It would not be a violation of the IEEE 488 Bus Standard for the PET to activate a device which sends data at the same time as one which receives data, and have them communicate directly with each other.
There is one other IEEE signal line which should be included in the interface. This is the IFC (Interface Clear) line. Whenever this line goes low, the interface should return to the inactive state.
Now we are ready to deal with the hardware requirements for communicating on the IEEE Bus. We will be using 6522 #2 on the SYM for the necessary I/O signals since all of the I/O lines from both ports go to the A-A connector. If necessary, the 6522 supplied as 6522 #3 could be moved to the #2 socket, losing only a few features which aren't needed for this interface. The main hardware requirement concerns a requirement for the delay between ATN going low to the time when NRFD is set low by a device. The IEEE 488 Standard calls for a maximum of 200 nanoseconds for this delay. Though the PET can't operate this fast, it does operate too fast for the SYM to meet this requirement using just software. The solution to obtain the necessary speed is to selectively send the ATN signal back out the NRFD line. The SYM can then assume control of the NRFD line when it is ready. The only other hardware needed are a couple of open-collector gates for the Wire-or requirements of the NRFD and NDAC lines. The circuitry shown in Figure 1 will meet these requirements.
Interface
The main function of the INTERFACE sub-function is to handle the communication sequence for the IEEE Bus. The first decision we must make is how the INTERFACE software will know when a communication sequence has begun, or when the IFC line goes low. Since the IFC signal is supposed to reset the device regardless of its current state, this signal should be tied to an interrupt. For greater flexibility we will tie the ATN line to an interrupt as well. This will allow the SYM to do other things when not being used as an interface.
The use of interrupts now provides a basis for dividing the INTERFACE sub-function into smaller parts. Listing 4 shows my division of the INTERFACE sub-function.
Listing 4
procedure INTERFACE
procedure ATNIRQ begin … end; {handles the IEEE communication}
procedure IFCIRQ begin … end; {resets the interface}
begin {INTERFACE procedure}
repeat
if INTERRUPT=TRUE then
begin
if IRQ=ATN then ATNIRQ;
if IRQ=IFC then IFCIRQ
end
until 2+2=5 {hopefully repeat forever}
end;
At this point we are almost ready to write the assembly language for the remaining parts of the software. However, ATNIRQ needs one more division. This involves addressing the question of how much intelligence to put in the interface. One answer is to program ATNIRQ in a way that leaves the door open for expansion. This can be done easily using the secondary address to call different interface routines. The division for ATNIRQ is shown in Listing 5. The “case” statement in this listing is a multiway subroutine jump. If SECADDRS is 0 when the “case” statement is executed, the SENDASCII procedure will be executed. For other secondary addresses, the DUMPCHRS procedure will be executed.
Listing 5
procedure ATNIRQ
procedure ATNINIT; begin … end; {get ready for communication}
procedure SENDASCII; begin … end; {input data and print it}
procedure DUMPCHRS; begin … end; {ignore data}
begin {ATNIRQ statements}
CYCLE; {get device address}
if DATA=MLA then
begin
ATNINIT;
CYCLE; {get next byte, possibly a secondary address}
if ATN=LOW then
begin
SECADDRS := DATA;
CYCLE
end;
case SECADDRS of
0 : SENDASCII;
1..15 : DUMPCHRS;
end {case statement}
end {if statement}
end; {ATNIRQ}
Now we can write the assembly language for INIT, then IFCIRQ, and finally ATNIRQ. Not clearly shown by the preceeding PASCAL programs is how the machine language should actually handle the interrupts. After an interrupt occurs, the first thing the machine language must do is save the register contents. Then it must test to see what interrupt occured. If it was an ATN interrupt, then the current stack pointer must be saved and ATN interrupts disabled before continuing with the rest of the ATNIRQ routine. If the interrupt was an IFC interrupt, the IFCIRQ routine should test to see if the ATNIRQ routine was executing. If it was, the IFCIRQ routine must restore the stack pointer to the value saved by ATNIRQ and reenable the ATN interrupt before restoring the registers and returning to the interrupted program.
The full listing of the assembly language for the interface is given in Listing 6. I've tried to write the assembly language so it can be easily expanded. Just remember that when you put a different routine in SCTABLE, the first data byte will have already been fetched by CYCLE when your routine is entered.
Summary
I've tried to make this article as much an example of interface design as one describing an actual interface. Most of the material presented dealt with needed facts or the steps involved in reaching a solution. I do not wish to imply that designing an interface should proceed from start to finish as easily as this article makes it seem. It is very likely that during your design, you will come upon a piece of new information or see a different approach which would have been highly useful at some previous step. This occured a few times during this design. Sometimes it is necessary or perhaps desireable to return to that previous step and take a different path. However, if you do enough preparation and planning before you begin the design process, you shouldn't have to backup too many times.
Listing 6
0010 ; IEEE INTERFACE
0020 ; WITH HARDWARE
0030 ; VERSION 2.5
0040 ;
0050 ; CONSTANTS
0060 UNLISTEN .DE $3F
0070 BS .DE $08
0080 UNDLN .DE $5F
0090 LF .DE $0A
0100 COLON .DE $3A
0110 SPACE .DE $20
0120 COMMA .DE $2C
0130 CR .DE $0D
0140 ;
0150 ; VARIABLES
0160 COUNT .DE $E0
0170 SIGNALS .DE $E1
0180 DATA .DE $E2
0190 MLA1 .DE $E3
0200 SEC.ADDRS .DE $E4
0210 TEMP .DE $E5
0220 LENGTH .DE $E6
0230 NL.FLAG .DE $E7
0240 SCAN.CNT .DE $E8
0250 F.LEN .DE $E9
0260 SP.IEEE .DE $EA
0270 ;
0280 ; ADDRESSES
0290 ACCESS .DE $8B86
0300 TOUFL .DE $A654
0310 SDBYT .DE $A651
0320 TECHO .DE $A653
0330 OUTCHR .DE $8A47
0340 INCHR .DE $8A58
0350 CRLF .DE $834D
0360 TOUT .DE $8AA0
0370 @2ACR .DE $A80B
0380 @2DDRA .DE $A803
0390 @2DDRB .DE $A802
0400 @2PCR .DE $A80C
0410 @2IER .DE $A80E
0420 @2IORB .DE $A800
0430 @2IORA .DE $A801
0440 @2IFR .DE $A80D
0450 OUTVEC .DE $A663
0460 UIRQVC .DE $A678
0470 IND.JMP .DE $EE
0480 ;
0490 .BA $200
0200- 20 86 8B 0500 INIT JSR ACCESS ;INITIALIZATION
0203- A9 24 0510 LDA #$24
0205- 85 E3 0520 STA *MLA1 ;MY LISTEN ADDRESS
0207- A9 90 0530 INIT.SYM LDA #$90
0209- 8D 54 A6 0540 STA TOUFL ;ENABLE CRT
020C- A9 10 0550 LDA #$10
020E- 8D 51 A6 0560 STA SDBYT ;SET FOR 1200 BAUD
0211- A9 00 0570 LDA #$00
0213- 8D 53 A6 0580 STA TECHO ;OUTPUT & NO ECHO
0216- A9 A0 0590 LDA #L,TOUT ;SET OUTPUT VECTOR
0218- 8D 64 A6 0600 STA OUTVEC+$1021B- A9 8A 0610 LDA #H,TOUT
021D- 8D 65 A6 0620 STA OUTVEC+$2
0220- A9 53 0630 LDA #L,INTERFACE
0222- 8D 78 A6 0640 STA UIRQVC ;SET USER IRQ VECTOR
0225- A9 02 0650 LDA #H,INTERFACE
0227- 8D 79 A6 0660 STA UIRQVC+$1
022A- A9 02 0670 LDA #$02
022C- 85 E0 0680 STA *COUNT
022E- A9 00 0690 INITPORTS LDA #$00
0230- 8D 0B A8 0700 STA @2ACR ; NO LATCHING
0233- 8D 03 A8 0710 STA @2DDRA ;2PA7-2PA0 ARE INPUTS
0236- A9 07 0720 LDA #$07
0238- 8D 02 A8 0730 STA @2DDRB ;3PB2-3PB0 ARE OUTPUTS
023B- A9 04 0740 LDA #$04
023D- 8D 0C A8 0750 STA @2PCR ;INTERRUPTS
0240- 20 47 02 0760 JSR EN.IEEE ;ENABLE IRQS
0243- 58 0770 CLI
0244- 4C 44 02 0780 IDLE JMP IDLE ;WAIT REAL FAST
0790 ;
0800 ;
0247- 78 0810 EN.IEEE SEI
0248- A9 83 0820 LDA #$83 ;ENABLE ATN AND IFC
024A- 8D 0E A8 0830 STA @2IER ; INTERRUPTS
024D- A9 06 0840 LDA #$06
024F- 8D 00 A8 0850 STA @2IORB ;NDAC=1, NRFD=ATN
0252- 60 0860 RTS
0870 ;
0880 ;
0253- 48 0890 INTERFACE PHA ;SAVE REGISTERS
0254- 98 0900 TYA
0255- 48 0910 PHA
0256- 8A 0920 TXA
0257- 48 0930 PHA
0258- AD 0D A8 0940 LDA @2IFR
025B- 10 1D 0950 BPL EXIT.INTF
025D- 29 03 0960 IEEE.IRQ AND #$03 ;WHICH INTERRUPT?
025F- C9 01 0970 CMP #$01
0261- F0 1D 0980 BEQ ATN.IRQ
0263- C9 02 0990 CMP #$02
0265- F0 03 1000 BEQ IFC.IRQ
0267- 4C 7A 02 1010 JMP EXIT.INTF
026A- AD 01 A8 1020 IFC.IRQ LDA @2IORA ;CLEAR INTERRUPT
026D- A9 01 1030 LDA #$01
026F- 2C 0E A8 1040 BIT @2IER ;IEEE ACTIVE?
0272- D0 06 1050 BNE EXIT.INTF ;EXIT INTERFACE
0274- A6 EA 1060 IEEE.OFF LDX *SP.IEEE
0276- 9A 1070 TXS ;RESTORE STACK POINTER
0277- 20 47 02 1080 JSR EN.IEEE
027A- 68 1090 EXIT.INTF PLA
027B- AA 1100 TAX
027C- 68 1110 PLA
027D- A8 1120 TAY
027E- 68 1130 PLA
027F- 40 1140 RTI
1150 ;
1160 ;
0280- BA 1170 ATN.IRQ TSX
0281- 8E EA 00 1180 STX SP.IEEE ;SAVE STACK POINTER
0284- AD 01 A8 1190 ATNINIT LDA @2IORA ;CLEAR INTERRUPT
0287- A9 05 1200 LDA #$050289- 8D 00 A8 1210 STA @2IORB ;SET NDAC=0 NRFD=0
028C- A9 01 1220 LDA #$01
028E- 8D 00 A8 1230 STA @2IORB ;TURN OFF ATN=NRFD
0291- 8D 0E A8 1240 STA @2IER ;TURN OFF ATN IRQS
0294- 58 1250 CLI
0295- A9 00 1260 LDA #$00
0297- 85 E4 1270 STA *SEC.ADDRS ;INIT SEC. ADDRS
0299- 20 EF 02 1280 JSR CYCLE
029C- A5 E2 1290 LDA *DATA
029E- C5 E3 1300 CMP *MLA1
02A0- F0 0C 1310 BEQ DEVICE1 ;BRANCH IF MY ADDRESS
02A2- A9 02 1320 EXIT.IEEE LDA #$02
02A4- 8D 00 A8 1330 STA @2IORB ;RELEASE ATN=NRFD
02A7- 2C 00 A8 1340 @15 BIT @2IORB
02AA- 30 FB 1350 BMI @15 ;WAIT FOR ATN=1
02AC- 10 BC 1360 BPL IFC.IRQ ;BR ALWAYS
1370 ;
02AE- 20 EF 02 1380 DEVICE1 JSR CYCLE
02B1- 24 E1 1390 BIT *SIGNALS ;SECONDARY ADDRESS?
02B3- 10 09 1400 BPL @3 ;BRANCH IF ATN IS OFF
02B5- A5 E2 1410 LDA *DATA ;GET SECONDARY ADDRESS
02B7- 29 0F 1420 AND #$0F ;ALLOW 16 SEC.ADDRS'S
02B9- 85 E4 1430 STA *SEC.ADDRS
02BB- 20 EF 02 1440 JSR CYCLE ;GET FIRST CHAR.
02BE- A5 E4 1450 @3 LDA *SEC.ADDRS
02C0- 0A 1460 ASL A
02C1- AA 1470 TAX
02C2- BD CF 02 1480 LDA SCTABLE,X ;FIX POINTER TO
02C5- 85 EE 1490 STA *IND.JMP ; SELECTED ROUTINE
02C7- BD D0 02 1500 LDA SCTABLE+$1,X
02CA- 85 EF 1510 STA *IND.JMP+$1
02CC- 6C EE 00 1520 JMP (IND.JMP)
02CF- 37 03 1530 SCTABLE .SI SENDASCII ;NORMAL PRINTING
02D1- 47 03 1540 .SI DUMPCHRS
02D3- 47 03 1550 .SI DUMPCHRS
02D5- 47 03 1560 .SI DUMPCHRS
02D7- 47 03 1570 .SI DUMPCHRS
02D9- 47 03 1580 .SI DUMPCHRS
02DB- 47 03 1590 .SI DUMPCHRS
02DD- 47 03 1600 .SI DUMPCHRS
02DF- 47 03 1610 .SI DUMPCHRS
02E1- 47 03 1620 .SI DUMPCHRS
02E3- 47 03 1630 .SI DUMPCHRS
02E5- 47 03 1640 .SI DUMPCHRS
02E7- 47 03 1650 .SI DUMPCHRS
02E9- 47 03 1660 .SI DUMPCHRS
02EB- 47 03 1670 .SI DUMPCHRS
02ED- 47 03 1680 .SI DUMPCHRS
1690 ;
1700 ;
02EF- A9 03 1710 CYCLE LDA #$03
02F1- 8D 00 A8 1720 STA @2IORB ;NRFD=1 NDAC=0
02F4- 2C 00 A8 1730 @1 BIT @2IORB ;TEST DAV
02F7- 70 FB 1740 BVS @1 ;BRANCH IF DAV=1
02F9- 6A 1750 ROR A
02FA- 8D 00 A8 1760 STA @2IORB ;NRFD=0 NDAC=0
02FD- AD 01 A8 1770 LDA @2IORA
0300- 49 FF 1780 EOR #$FF
0302- 85 E2 1790 STA *DATA
0304- AD 00 A8 1800 LDA @2IORB0307- 85 E1 1810 STA *SIGNALS
0309- A9 00 1820 LDA #$00
030B- 8D 00 A8 1830 STA @2IORB ;NRFD=0 NDAC=1
030E- 2C 00 A8 1840 @2 BIT @2IORB
0311- 50 FB 1850 BVC @2 ;BRANCH IF DAV=0
0313- A9 01 1860 LDA #$01
0315- 8D 00 A8 1870 STA @2IORB ;NRFD=0 NDAC=0
0318- 60 1880 RTS
0319- 20 47 8A 1890 PRINT JSR OUTCHR ;PRINT AND INC. COUNT
031C- E6 E0 1900 INC *COUNT
031E- D0 0C 1910 BNE RETURN
0320- A9 03 1920 ACK LDA #$03 ;ASCII ETX
0322- 20 47 8A 1930 JSR OUTCHR
0325- 20 58 8A 1940 JSR INCHR ;WAIT FOR ACK
0328- A9 02 1950 LDA #$02
032A- 85 E0 1960 STA *COUNT
032C- 60 1970 RETURN RTS
1980 ;
1990 ;
032D- A5 E2 2000 @18 LDA *DATA
032F- 29 7F 2010 AND #$7F
0331- 20 19 03 2020 JSR PRINT
0334- 20 EF 02 2030 NEXT JSR CYCLE
0337- 24 E1 2040 SENDASCII BIT *SIGNALS
0339- 10 F2 2050 BPL @18 ;BR IF ATN=1
033B- A5 E2 2060 LDA *DATA
033D- C9 3F 2070 CMP #UNLISTEN
033F- D0 F3 2080 BNE NEXT
0341- 4C A2 02 2090 JMP EXIT.IEEE
2100 ;
2110 ;
0344- 20 EF 02 2120 NEXT2 JSR CYCLE
0347- 24 E1 2130 DUMPCHRS BIT *SIGNALS
0349- 10 F9 2140 BPL NEXT2
034B- A5 E2 2150 LDA *DATA
034D- C9 3F 2160 CMP #UNLISTEN
034F- D0 F3 2170 BNE NEXT2
0351- 4C A2 02 2180 JMP EXIT.IEEE
0354- 00 2190 .BY $0
2200 .EN
SYM to Spinwriter Hardware

Editor's Note: For those of you who don't have issue 1, we're reprinting these two charts. RCL
TABLE 1
| NAME | SET BY | DESCRIPTION |
| DIO1-DIO8 | Talker | Data Input/Output. These lines carry the commands and data. |
| NRFD | Listener | Not Ready for Data. When low, it means the device is not ready to receive data. It is set high when the device is ready. |
| DAV | Talker | Data Valid. When high, it means the data on the data lines is not valid. It is set low once all NRFD goes high and valid data has been placed on the data lines. |
| NDAC | Listener | Not Data Accepted. When low, it means that the data has not been accepted. It is set low once DAV goes low and the data has been latched. |
| ATN | Talker | Attention. Signals that the byte on the DIO lines is a command. |
| EOI | Talker | End Or Identify. Signals that the last data byte is being transferred. |
| IFC | Interface Clear. Resets all devices. |
Figure 1
