Classic Computer Magazine Archive COMPUTE II ISSUE 2 / JUNE/JULY 1980


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.


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}
    if INTERRUPT=TRUE then
          if IRQ=ATN then ATNIRQ;
          if IRQ=IFC then IFCIRQ
   until 2+2=5 {hopefully repeat forever}

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
      CYCLE; {get next byte, possibly a secondary address}
      if ATN=LOW then
            SECADDRS := DATA;
      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.


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
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
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


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