HP-85 to HP 7970E

Connecting an HP-85 to an HP 7970 tape drive via HP-IB

The HP 7970E magnetic tape drive has an optional HP-IB interface. My particular unit has it. It seemed natural to connect it to my HP-85, which also talks HP-IB natively. But this turned out to be much more challenging than expected :

  • The HP-85 mass storage ROM does not support the HP 7970E

  • The HP-85 is too slow to keep up with the HP 7970E data rate

  • The HP-85 HP-IB implementation differs fundamentally from that of the HP 7970E in its treatment of parity

  • The HP 7970E HP-IB protocol is complicated and not clearly documented.

This page explains how I was able to solve the issues, and have the HP 85 read from the big tape. The video demonstrates the result.

HP 7970E HP-IB Protocol

The HP7970E HP-IB interface is an optional feature of the HP 7970E, and the interface protocol is described in this document:

(note: the online document hosted at hpmuseum.net is missing a critical page, and it took me a while to find it. The document linked above is "repaired" and complete).

The protocol is complex and difficult to understand, mostly because the documentation is quite poor. Doing simple operations requires a series carefully orchestrated commands, using advanced HP-IB features such as secondary commands, parallel poll responses, and EOI tagging.

Timing is critical. Since the tape HP-IB interface only has a 128 byte R/W buffer, and the tapes moves quite rapidly at 45 IPS, data has to be written in or read out of the buffer to keep up with the tape, or the buffer will overflow (on read) or underflow (on write). The tape then stops with a timing error. Unfortunately the HP 85 is not fast enough to keep up with the tape, even using its advanced buffered TRANSFER FHS command. So the HP-85 will be limited to reading tapes formatted with short records of 128 bytes length maximum, or read only the first 128 bytes of longer records. Writing to the tape from the HP-85 is not possible (or at least, I have not succeeded).

Command sequencing has to be followed strictly. Most tape commands have to be followed by waiting for a Parallel Poll response, then reading the DSJ (Device Specific Jump) to acknowledge the parallel poll receipt, and based on the DSJ return value (0 for no error, 1 for error), read the 3 status bytes. Skipping any of these steps will hang the whole process.

Parity turned out to be a huge and unexpected problem (diagnosing it was difficult, the details are in my handwritten lab notes). Parity is an optional feature of HP-IB. Not only does the HP 7970E expect odd parity, but it reacts very poorly if a parity error is detected: the HP 7970 simply hangs the entire HP-IB bus, even on a command not addressed to it. So the optional parity bit (DIO 8) must be set properly. Which the HP-85 does not do: although there is a control bit for setting parity in the interface registers, setting it did absolutely nothing in my testing. If a command is sent with wrong (even) parity, the HP 7970 never releases the NDAC line (Not Data ACknowledge), which hangs the whole HP-IB bus. To make things more complicated, odd parity is only enforced on command words, but data words can be any parity.

If the only device on the HP-IB bus is the HP 7970 tape, you can get around it by sending raw binary command codes, as will be shown below. But in my setup I also had a printer and a floppy disk on the bus, both of which are controlled by ROM over which you have no control. It is frequently sending even parity command words, resulting in the 7970 hanging the bus. I ended up making a small FPGA contraption that continuously monitors the HP-IB bus and corrects the parity bit on the fly.

Demo programs for reading from the HP 7970 to the HP-85

Here are the two HP-85 programs I wrote to access the HP 7970E over the HP-IB interface. The first one is the minimum needed to read 100 tape records. The second one is the full fledged program used in my ASCII demo video, capable of reading a printing a whole file, position the tape by rewinding, skipping forward and backwards a file, and print excerpts of a file record by record, forward or backward.

The code looks quite incomprehensible at first, but should make sense after you read the line by line description below. It uses many of the more advanced commands described in the HP 85 IO programming reference guide:

I will give page references to this guide in the explanations below.

If you are unfamiliar with HP-IB at the very low level we will be using here, this HP Journal article is one of the best descriptions I have found:

Walk-through the basic interface example code

The full code is here: HP 7970 to HP 85 basic interface and reprinted below:


1 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!2 ! !!! HP7970E INTERFACE PROGRAM !!!!!!!!!!!!!!!!!!!!!!!!!!!!!3 ! !!! ASSUMES TAPE ADDRESS = 1 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!4 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!5 !10 CLEAR20 DISP "HP7970 INTERFACE PROGRAM"25 ! !!! INTERFACE INIT !!!!!!!!!!!!!!!!!!30 RESET 7 ! Reset HPIB interf40 CONTROL 7,16 ; 128 ! send EOI on data end50 ON TIMEOUT 7 GOTO 100060 SET TIMEOUT 7;1000 ! 1s timeout70 DIM T$[136] ! 128 char buf75 DIM U$[128] ! READ STRING80 IOBUFFER T$ ! use for tsfr90 ! !!! TAPE DRIVE INIT !!!!!!!!!!!!!!!!!100 ! !! sel 0/ppoll/DSJ/Status 110 A=FNS0 ! sel unit 0114 GOSUB 1100 ! read PPOLL115 D=FND ! read dsj116 GOSUB 1400 ! wait tape online117 ! !!! Tape Rewind and Status !!!!!!!!!!118 DISP "REWINDING TAPE..."120 A=FNR ! rewind124 GOSUB 1100 ! ppoll loop130 ! DISP "READING DSJ"140 D=FND ! D$=FND$150 DISP "DSJ =";D ! DISP D$160 DISP "STATUS:"170 S=FNS180 DISP DTH$(S1);" ";DTB$(S1)181 DISP DTH$(S2);" ";DTB$(S2)182 DISP DTH$(S3);" ";DTB$(S3)190 IF BIT(S2,2) THEN GOSUB 1200 ! wait for rewind195 GOSUB 1400 ! wait for tape online200 ! !!! READ TAPE RECORDS !!!!!!!!!!!!!!!\\996 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!997 ! !! SUBROUTINE AREA998 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!999 !1000 ! !!!!!!!!!!!!!!!!!!!1001 ! !!! HP-IB TIMEOUT 1002 ! !!!!!!!!!!!!!!!!!!!1005 DISP "HP-IB TIMEOUT"1010 DISP "Resetting interface..."1020 RESET 71030 END1100 ! !!!!!!!!!!!!!!!!!!!1101 ! !!! PPOLL WAIT LOOP 1102 ! !!!!!!!!!!!!!!!!!!!1105 ! DISP ""1110 FOR J=1 TO 1001120 A=PPOLL(7)1123 ! DISP A;".";1130 IF BIT(A,6) THEN RETURN ! assumes tape addr = 11140 NEXT J1145 DISP "PPOL timed out"1150 RETURN1200 ! !!!!!!!!!!!!!!!!!!!!!!!1201 ! !!! REWIND COMPLETE 1202 ! !!!!!!!!!!!!!!!!!!!!!!!1210 DISP "Tape rewinding..."1220 S=FNS ! read status1230 IF BIT(S2,2) THEN GOTO 12201240 DISP "Rewind done."1250 RETURN1300 ! !!!!!!!!!!!!!!!!!!!!!!1301 ! !!! DSJ AND STATUS PRINT ON ERROR1302 ! !!!!!!!!!!!!!!!!!!!!!!1310 D=FND1320 IF D=0 THEN RETURN1330 S=FNS ! read status1335 IF BIT(S2,4) THEN RETURN ! ignore timing errors1340 DISP "DSJ=1! Status is:"1350 DISP DTH$(S1);" ";DTB$(S1)1360 DISP DTH$(S2);" ";DTB$(S2)1370 DISP DTH$(S3);" ";DTB$(S3)1375 RETURN ! continue for now1380 DISP "BYE" @ END1400 ! !!!!!!!!!!!!!!!!!!!!!1401 ! !!! WAIT TAPE ONLINE1402 ! !!!!!!!!!!!!!!!!!!!!!1410 IF BIT(FNS,0) THEN RETURN1415 BEEP1420 DISP "TAPE NOT ONLINE!"1430 DISP "PRESS CONT WHEN READY"1440 PAUSE1450 IF BIT(FNS,0) THEN RETURN1460 GOTO 14201996 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1997 ! !! TAPE FUNCTION AREA1998 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1999 !2000 ! !!!!!!!!!!!!!!!!!!!!2001 ! FNS0 - select unit 02002 ! !!!!!!!!!!!!!!!!!!!!2005 DEF FNS0 = ! sel unit 02010 SEND 7 ; CMD 191,213,161,97 DATA 12020 FNS0=1 ! dummy return val2030 FN END2100 ! !!!!!!!!!!!!!!!!!!!!2101 ! FNR - tape rewind2102 ! !!!!!!!!!!!!!!!!!!!!2105 DEF FNR ! rewind2110 SEND 7 ; CMD 191,213,161,97 DATA 132120 FNR=12130 FN END2200 ! !!!!!!!!!!!!!!!!!!!!2201 ! FND$ - dsj with xfer2202 ! !!!!!!!!!!!!!!!!!!!!2205 DEF FND$ = ! read DSJ2210 SEND 7 ; CMD 191,181,193,1122215 IOBUFFER T$ ! reset buf2220 TRANSFER 7 TO T$ FHS ; EOI2230 FND$=T$ ! return DSJ2240 FN END2300 ! !!!!!!!!!!!!!!!!!!!!2301 ! FND - dsj with enter2302 ! !!!!!!!!!!!!!!!!!!!!2305 DEF FND = ! read DSJ2310 SEND 7 ; CMD 191,181,193,1122320 ENTER 7 USING "#%,B" ; D2330 FND=D2340 SEND 7 ; CMD 223 ! UNTALK2350 FN END2400 ! !!!!!!!!!!!!!!!!!!!!2401 ! FNS - read Status2402 ! !!!!!!!!!!!!!!!!!!!!2405 DEF FNS = ! read status2410 SEND 7 ; CMD 191,181,193,972420 ENTER 7 USING "#%,B" ; S1,S2,S32430 FNS=S12440 SEND 7 ; CMD 223 ! untalk2450 FN END2500 ! !!!!!!!!!!!!!!!!!!!!2501 ! FNF - read record fwd2502 ! !!!!!!!!!!!!!!!!!!!!2510 DEF FNF2520 SEND 7 ; CMD 191,213,161,97 DATA 82530 FNF=12540 FN END2600 ! !!!!!!!!!!!!!!!!!!!!!!!2601 ! FNG - get data2602 ! !!!!!!!!!!!!!!!!!!!!!!!2605 DEF FNG$2610 SEND 7 ; CMD 191,181,193,2242620 ! ENTER 7 USING "#%,#%K" ; U$ ! slower ENTER based version2621 IOBUFFER T$2623 TRANSFER 7 TO T$ FHS ; COUNT 128 EOI2625 SEND 7 ; CMD 223 ! untalk2630 FNG$="" ! dummy return value2650 FN END

Initializing the HP-IB interface


30 RESET 7 ! Reset HPIB interf40 CONTROL 7,16 ; 128 ! send EOI on data end50 ON TIMEOUT 7 GOTO 100060 SET TIMEOUT 7;1000 ! 1s timeout70 DIM T$[136] ! 128 char buf75 DIM U$[128] ! READ STRING80 IOBUFFER T$ ! use for tsfr

This first snippet of code configures the HP-85 HP-IB interface module. It is assumed to be on its default IO address select code 7. The tape is assumed to be on the HP-IB address 1, and the printer on HP-IB address 9.

  • 30 RESET 7 : this simply resets the HP-85 interface and releases all the control lines in case the bus was previously hanged up.

  • 40 CONTROL 7,16;128 : This cryptic line is very important. See p115 of the IO manual. The 7970 expects all data fields to tag the last data byte by raising the EOI line (End or Identify). The interface is told to do this automatically by writing a 1 on bit 8 of control register 16. 128 decimal is 10000000 in binary, therefore setting bit 8 to 1 as desired.

  • 50 ON TIMEOUT 7 GOTO 1000

  • 60 SET TIMEOUT 7;3000 : these two lines establish a timeout of 3 seconds if the interface becomes unresponsive (tape is off line, runs out, read error, etc...). This is needed because we will use a buffered TRANSFER statement that could hang up waiting for characters. The error recovery routine starts at address 1000.

  • 70 DIM T$[136] : this declares T$, the IO buffer that will be used in the fast I/O TRANSFER statements. It is dimensioned to hold 128 characters (the max length of the tape record we can read) plus 8 control characters needed for the buffer management (see p. 280 of the IO programming guide).

  • 75 DIM U$[128] : this declares U$, an alternate 128 character string buffer that can be used for the slower data input method using the ENTER statement. That's how I did it first experimentally, because it is simpler. I later moved to using the buffered transfer method that uses the T$ buffer, but left this earlier simpler plumbing in for info, with this definition and a line commented out in the function FNG$.

  • 80 IOBUFFER T$ : this upgrades our previously defined T$ string buffer to a full-fledged IO buffer, and clears it. It will used later in TRANSFER input statements.

Selecting Device 0, and waiting for tape online


110 A=FNS0 ! sel unit 0114 GOSUB 1100 ! read PPOLL115 D=FND ! read dsj116 GOSUB 1400 ! wait tape online

Before the tape drive can be accessed at all, it needs to be explicitly selected. There are 4 possible selectable units (1 master and 3 slaves). We assume our 7970 is configured as tape unit 0 (the 0 button on its front panel is pushed in/lit), and direct all further messages to unit 0 by sending this selection command.

  • 110 A=FNS0 : this calls the function FNS0, defined later in the program, which sends the command to select device 0. This function is further detailed below.

  • 114 GOSUB 1100 : after every command, a wait for a PPOLL (Parallel Poll) response is required

  • 115 D=FND : after a PPOLL return, a read of the DSJ (Device Specific Jump) is required to acknowledge and de-assert the parallel poll, and the value returned will be 0 or 1 depending on the error status. Function FND implements the read, and returns its value into variable D. The FND function is described further below.

  • 116 GOSUB 1400 : Go to the subroutine that waits for status to show that the "online" button has been pressed. Also detailed below.

FNS0 "select device 0" function subroutine and the odd parity saga


2005 DEF FNS0 = ! sel unit 02010 SEND 7 ; CMD 191,213,161,97 DATA 12020 FNS0=1 ! dummy return val2030 FN END

This small function follows a template that is the same for all the tape commands used in the program. The select device 0 command syntax is detailed in p. 2-18 of the tape HP-IB interface manual. It says to send a secondary HP-IB command 011000001 (97 in decimal), then data 1, accompanied by the EOI signal. The easy (and far clearer) way to do this on the HP-85 is to build a custom HP-IB command, as detailed on p. 105 of the HP-85 IO programming manual. This would normally be written like:

  • SEND 7; UNL MTA LISTEN 1 SCG 97 DATA 1

SEND 7 sends the output command on the HP-IB interface #7, UNL is an unlisten command to all devices, MTA (My Talk Address) makes the controller the talker, LISTEN 1 makes the tape on address 1 the listener. These are generic steps to prepare for sending a command from the controller to the tape. Finally, SCG 97 sends the secondary command 97 (binary 011000001), and DATA 1 sends the data 1 (with EOI because of our previous setup in line 40), which is the "Select Tape 0" tape function.

However, if you try this perfectly correct sequence just like I did, the 7970 will hang the bus! It took me a long while to figure out why. It turns out that the commands generated by the HP-85 do not use nor set parity bit (bit 8 of the command word), which it simply leaves at 0. However the 7970 expects odd parity, and is very pig-headed about it. It will hang the bus if a command with the wrong parity is received. Which is very impolite and not nice at all, and will definitely ruin your day.

For example, on the HP 85, UNL sends code 77 Octal (63 Decimal / 00111111 Binary). Which is an even parity word. This code will not be accepted by the 7970, which will keep the NDAC (Not Data Acknowledge) signal low forever, which in turn hangs the HP-IB bus. Great. However, if we just flip the parity bit 8 to 1 and send 10111111 instead, now the parity is odd and the 7970 will accept it, release the NDAC line, and everything is fine. Since setting bit 8 is the same as adding 128, we can calculate what the correct value of the code has to be: UNL(odd) = UNL + 128 = 63+128 = 191.

So all HP-85 default command codes have to be recalculated and replaced by their odd parity equivalents. Fortunately we only use 7 of them, which are re-calculated below (all values in decimal):

  1. UNL=63 -> UNL(odd)=191

  2. MTA=85 -> MTA(odd)=213

  3. MLA=53 -> MLA(odd)=181

  4. LISTEN 1=33 -> LISTEN 1(odd)=161

  5. TALK 1=65 -> TALK 1(odd)=193

  6. UNT 1= 95 ->UNT(odd)=223

  7. SCG 97 = 97 is odd already

The HP-85 mercifully includes a facility to send arbitrary codes, using the SEND 7; CMD code1, code2, .... syntax. That's how we arrive at the final line of code:

2010 SEND 7 ; CMD 191,213,161,97 DATA 1

which is just "odd parity" hard coded version of

2010 SEND 7; UNL MTA LISTEN 1 SCG 97 DATA 1

we were trying to send in the first place.

So back to our 4-line function, which should now be understandable:

  • 2005 DEF FNS0 : calling name of function is FNS0

  • 2010 SEND 7 ; CMD 191,213,161,97 DATA 1 : sends UNL(odd), MTA(odd), LISTEN 1(odd), SCG 97 and DATA 1 codes. Since already we set up the interface register on line 40, the EOI line will automatically be set with the 1 data code. Yeah! We have finally been able to communicate with the HP 7970E.

  • 2020 FNS0=1 : a function needs to return a value, so we arbitrarily return 1

  • 2030 FN END : end of function

Other commands are implemented in a similar manner, using functions SENDing pre-calculated CMD and DATA codes of the correct parity.


Parallel Poll wait loop subroutine


1110 FOR J=1 TO 1001120 A=PPOLL(7)1123 ! DISP A;".";1130 IF BIT(A,6) THEN RETURN ! assumes tape addr = 11140 NEXT J1145 DISP "PPOL timed out"1150 RETURN

You have to wait for a parallel poll response from the tape drive after every command and before doing anything else. The Parallel Poll wait routine is a wait loop that returns when the tape asserts its poll line, or if more than 100 poll iterations have occurred, whichever is sooner. The code is straightforward, except for the location of the polled bit to test:

  • 1110 FOR J=1 TO 100 : try a maximum of 100 times.

  • 1120 ! DISP A;"."; : line commented out, was used for debug during development to inspect the PPOLL return value

  • 1120 A=PPOLL(7) : check for parallel poll response on HP-IB interface 7

  • 1130 IF BIT(A,6) THEN RETURN : Bit 6 is the bit assigned to HPIB address 1 (our tape drive) in a parallel poll (don't ask). Return immediately if it has been set.

  • 1140 NEXT J : try to poll 100 times

  • 1145 DISP "PPOL timed out" : if we don't have a timely tape drive answer answer, something probably went wrong, display warning, but keep on going.

  • 1150 RETURN : return

FND: Read DSJ function


2305 DEF FND ! read DSJ 2310 SEND 7 ; CMD 191,181,193,1122320 ENTER 7 USING "#%,B" ; D2330 FND=D2340 SEND 7 ; CMD 223 ! UNTALK2350 FN END

You have to read DSJ after doing a parallel poll. This does two important things: it informs the tape you are servicing the poll, so the tape can de-assert its poll line. It also lets you know if the command resulted in an error by returning a value of 0 (no error) or 1 (error).

  • 2305 DEF FND : Function to read DSJ

  • 2310 SEND 7 ; CMD 191,181,193,112 : sends codes for UNL(odd), MLA(odd), TALK 1(odd), SCG 112 (read DSJ secondary command)

  • 2320 ENTER 7 USING "#%,B" ; D : reads the DSJ value into variable D, using format "#%,B". B means binary read, % means that EOI is used as terminator, and # that linefeed is not a terminator.

  • 2330 FND=D : set the function return value to the DSJ value we read

  • 2340 SEND 7 ; CMD 223 : we need to send an untalk code UNT 1(odd) to the tape, since we made it the talker on line 2310

  • 2350 FN END : function return

Wait for tape online


1410 IF BIT(FNS,0) THEN RETURN1415 BEEP1420 DISP "TAPE NOT ONLINE!"1430 DISP "PRESS CONT WHEN READY"1440 PAUSE1450 IF BIT(FNS,0) THEN RETURN1460 GOTO 1420

This code snippet makes sure the tape is online before going any further. The user needs to press the online button after loading the tape, or it will stay unresponsive. It's very easy to forget. This code reads the online status, and if not online, stops and reminds to press the darn "online" button on the tape. The user also needs to press CONT on the HP-85 to get it going again.

  • 1410 IF BIT(FNS,0) THEN RETURN : call FNS, the function that reads the status bytes, described below. Testing bit 0 will tell if the user has pressed the online button

  • 1415 BEEP : remind forgetful user

  • 1420 DISP "TAPE NOT ONLINE!"

  • 1430 DISP "PRESS CONT WHEN READY"

  • 1440 PAUSE : stop, user has to press CONT to restart after he puts the tape online

  • 1450 IF BIT(FNS,0) THEN RETURN : test again, return if online

  • 1460 GOTO 1420 : not online, go back.

FND: Status byte read function


2405 DEF FNS = ! read status2410 SEND 7 ; CMD 191,181,193,972420 ENTER 7 USING "#%,B" ; S1,S2,S32430 FNS=S12440 SEND 7 ; CMD 223 ! untalk2450 FN END

There are three status bytes, which are read into common variables S1, S2 and S3 by calling this functions. They need to be further inspected by the calling program. The structure is exactly the same as the previous function FND:

  • 2405 DEF FNS : function name

  • 2410 SEND 7 ; CMD 191,181,193,97 : sends UNL, MLA, TALK 1, SCG 97 (read status secondary command)

  • 2420 ENTER 7 USING "#%,B" ; S1,S2,S3 : The tape drive returns 3 bytes, with the last one marked with EOI. B specifies binary format, #% specify end marker is EOI and not Linefeed.

  • 2430 FNS=S1 : set the function value to S1

  • 2440 SEND 7 ; CMD 223 : sends UNTALK 1(odd). Untalk is needed after making the tape the talker.

  • 2450 FN END : return

Tape Rewind, Status Display


118 DISP "REWINDING TAPE..."120 A=FNR ! rewind124 GOSUB 1100 ! ppoll loop130 ! DISP "READING DSJ"140 D=FND ! D$=FND$150 DISP "DSJ =";D ! DISP D$160 DISP "STATUS:"170 S=FNS180 DISP DTH$(S1);" ";DTB$(S1)181 DISP DTH$(S2);" ";DTB$(S2)182 DISP DTH$(S3);" ";DTB$(S3)190 IF BIT(S2,2) THEN GOSUB 1200 ! wait for rewind195 GOSUB 1400 ! wait for tape online

The code then goes on to perform a tape rewind. By now this should start to make sense. On line 120, the FNR function is called which sends the pre-calculated CMD codes for a rewind command. Then the mandatory PPOLL wait loop is called on 124, followed by the mandatory DSJ read on line 140, followed by the status byte read on line 170. The status bytes are then displayed onscreen in Hex and Binary format (lines 180 to 182). Bit 2 of S2 (which contains status byte 2), the "rewind in progress" bit, is tested repeatedly until rewind is complete.

Read 100 records


205 DISP "READING 100 RECORDS"210 FOR I=1 TO 100220 A=FNF230 GOSUB 1100 ! ppoll wait233 A=FND ! read DSJ before status always235 S=FNS ! check status for end of file or EOT236 IF BIT(S1,5) THEN DISP "END OF TAPE" @ GOTO 990237 IF BIT(S1,7) THEN DISP "END OF FILE" @ GOTO 990238 IF BIT(S2,3) THEN DISP "TAPE RUNAWAY" @ GOTO 990250 A$=FNG$260 DISP T$ ! DISP U$ ! U$ if using ENTER based version of FNG$270 GOSUB 1300 ! dsj check280 NEXT I990 DISP "DONE" @ END

Finally we get to read some data off the tape. It takes two commands to accomplish a read. First you have to issue a read forward motion through the FNF function on line 220, followed by the usual parallel poll wait, DSJ read, and status read. If all is well, you then have to issue the actual data read command through the FNG$ string function on line 250. We'll discuss these two functions below.

FNF - Read forward function


2510 DEF FNF2520 SEND 7 ; CMD 191,213,161,97 DATA 82530 FNF=12540 FN END

Line 2520 sends a UNL, MTA, LISTEN 1, Secondary Tape Command (97), which is generic for all tape functions. This time we send 8 as the DATA byte, which selects the read forward function and starts moving the tape.

FNG$ - Read data function


2605 DEF FNG$2610 SEND 7 ; CMD 191,181,193,2242620 ! ENTER 7 USING "#%,#%K" ; U$ ! slower ENTER based version2621 IOBUFFER T$2623 TRANSFER 7 TO T$ FHS ; COUNT 128 EOI2625 SEND 7 ; CMD 223 ! untalk2630 FNG$="" ! dummy return value2650 FN END

Line 2610 sends an UNL, MLA, TALK1 and a SCG 224, the secondary command for reading data.

Line 2620, which is commented out, is a remnant of previous debug code, left for information. It shows how to make a simple data read using the regular ENTER statement into the previously declared string U$. However, ENTER fetches and processes data character by character. It is simple and foolproof, but very slow.

Instead, in the final code, we implement a fast TRANSFER FHS into a previously declared IO buffer T$. This is the fastest way to input data on the HP-85:

  • 2621 IOBUFFER T$ : set the previously declared T$ string array as the current IO transfer buffer, and clear it

  • 2623 TRANSFER 7 TO T$ FHS ; COUNT 128 EOI : initiate a fast (FHS) buffered transfer using T$. Terminate either when 128 characters have been received, or when an EOI (end of data) is encountered, which ever occurs first. If neither of these conditions occurs, the timeout we set on line 50 and 60 will kick in and get us unstuck.

But even with a TRANSFER FHS, the HP-85 is not fast enough to keep up with the mighty mainframe tape. The tape drive will generate a timing error because we were too slow to empty its 128 character buffer in time after our read command. This also causes the tape to automatically stop in the next inter-record gap. However, since we made sure the records written on the tapes were not longer than 128 characters, we have not lost any information. The 128 characters read from the record did all fit in the tape memory buffer, so we just finish reading them at our leisure now that the tape has stopped automatically. We just ignore the timing error and start a new read command for the next record as if nothing had happened. One can read the whole content on the tape error free on the HP-85, albeit quite slowly, with the tape stopping briefly in-between each record waiting for the HP-85 to catch up. This works only for a tape written with record length of less than 128 bytes. If the records are longer than 128 bytes, it is only possible to read the first 128 characters of each record with our pokey HP-85. It is just not fast enough to keep up with the tape for the longer records.

ASCII demo full program code and FPGA parity sniffer

You should now be able to walk through the more complete ASCII art demo program I used to make the video. It uses the same techniques but implements additional niceties such as skipping over files forward and backward, reading records forward and backward, properly detecting the end of tape, peeking at the beginning of the files, etc.. It also implements a function key-driven user interface.

But there is a catch. The ASCII art demo program used in the video expects a line printer on HP-IB address 9, to print the content read from the tape, resulting in a pretty ASCII art picture. But there is no way to control the parity of the HP-IB print commands sent by the HP-85, since they are generated by the built-in ROM. And even parity commands will cause the 7970 to hang the bus. So normally this arrangement would not work, unless the printer was on a different HP-IB bus interface altogether. I managed to make it work with a single HP-IB bus interface, but I had to build an external FPGA-driven parity correcting device and put it on the bus to add odd parity on-the-fly. Building an FPGA contraption is probably more trouble than it's worth for most people. However you could achieve the same result by simply putting the printer on a second HP-IB interface plugged in in the HP-85, and leaving the HP 7970 tape as the only device on the bus on the other interface.

1 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!2 ! !!! ASCII ART PRINT DEMO PROGRAM !!!!!!!!!!!!!!!!!!!!!!!3 ! !!! ASSUMES 7970E TAPE AT ADDRESS 1 !!!!!!!!!!!!!!!!!!!!!!!4 ! !!! AND 2631G PRINTER AT ADDRESS 9 !!!!!!!!!!!!!!!!!!!!!!!5 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!6 !10 CLEAR20 DISP "HP7970 INTERFACE PROGRAM"25 ! !!! INTERFACE INIT !!!!!!!!!!!!!!!!!!30 RESET 7 ! Reset HPIB interf32 DISP "TAPE MUST BE ON ADDR 1"33 DISP "PRINTER MUST BE ON ADDR 9"35 PRINTER IS 709,13240 CONTROL 7,16 ; 128 ! send EOI on data end50 ON TIMEOUT 7 GOTO 100060 SET TIMEOUT 7;3000 ! 1s timeout70 DIM T$[136] ! 128 char buf75 DIM U$[128] ! READ STRING80 IOBUFFER T$ ! use for tsfr90 ! !!! TAPE DRIVE INIT !!!!!!!!!!!!!!!!!100 ! !! sel 0/ppoll/DSJ/Status 110 A=FNS0 ! sel unit 0114 GOSUB 1100 ! read PPOLL115 D=FND ! read dsj116 GOSUB 1400 ! wait tape online120 ! !!! TAPE REWIND AND STATUS !!!!!!!!!130 GOSUB 1500 ! rewind140 DISP "READY"190 ! !!!!! FUNCTION KEYS DEF !!!!!!!!!!!!!200 ON KEY# 1,"PRINT" GOTO 800210 ON KEY# 8,"REW" GOTO 600220 ON KEY# 4,"EXIT" GOTO 700230 ON KEY# 2,"FILE+" GOTO 1700240 ON KEY# 3,"FILE-" GOTO 1800250 ON KEY# 6,"REC+" GOTO 2000260 ON KEY# 7,"REC-" GOTO 2300300 GOTO 993 ! draw keys600 ! !! REWIND KEY FUNC !!!!!!605 CLEAR610 GOSUB 1500620 GOTO 993700 CLEAR @ DISP "BYE" @ END800 ! !!! READ TAPE RECORDS !!!!!!!!!!!!!!!805 DISP "READING TAPE RECORDS"810 FOR I=1 TO 300815 GOSUB 1600 ! read 1 record816 IF E=1 THEN CLEAR @ DISP "END OF TAPE" @ GOTO 990817 IF E=2 THEN GOTO 850818 IF E=3 THEN CLEAR @ DISP "TAPE RUNAWAY" @ DISP "PLEASE REWIND TAPE" @ GOTO 990820 ! DISP T$ ! DISP U$ ! U$ if using ENTER based version of FNG$825 PRINT T$;! print each record830 NEXT I840 DISP "EXCEEDED MAX RECORDS PER FILE" @ GOTO 990850 ! error 2, EOF encountered851 IF I=1 THEN 870855 DISP "PRESS K1 TO PRINT NEXT FILE"860 GOTO 990870 ! got EOF as first record = double EOF, end of data875 DISP "END OF DATA"880 GOSUB 1900 ! back one recordto allow file- to work885 GOTO 993899 ! !!! WAIT LOOP !!!!!!!!!!!990 PRINT ! empty print buffer993 KEY LABEL ! redraw labels995 GOTO 995 ! wait for function key996 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!997 ! !! SUBROUTINE AREA998 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!999 !1000 ! !!!!!!!!!!!!!!!!!!!1001 ! !!! HP-IB TIMEOUT 1002 ! !!!!!!!!!!!!!!!!!!!1005 DISP "HP-IB TIMEOUT"1008 BEEP1010 DISP "Resetting interface..."1020 RESET 71030 DISP "DONE" @ END1100 ! !!!!!!!!!!!!!!!!!!!1101 ! !!! PPOLL WAIT LOOP 1102 ! !!!!!!!!!!!!!!!!!!!1105 ! DISP ""1110 FOR J=1 TO 10001120 A=PPOLL(7)1123 ! DISP A;".";1130 IF BIT(A,6) THEN RETURN ! assumes tape addr = 11140 NEXT J1145 DISP "PPOL timed out"1150 RETURN1200 ! !!!!!!!!!!!!!!!!!!!!!!!1201 ! !!! REWIND COMPLETE 1202 ! !!!!!!!!!!!!!!!!!!!!!!!1210 DISP "Tape rewinding..."1220 S=FNS ! read status1230 IF BIT(S2,2) THEN GOTO 12201240 DISP "Rewind done."1250 RETURN1300 ! !!!!!!!!!!!!!!!!!!!!!!1301 ! !!! DSJ AND STATUS PRINT ON ERROR1302 ! !!!!!!!!!!!!!!!!!!!!!!1310 D=FND1320 IF D=0 THEN RETURN1330 S=FNS ! read status1335 IF BIT(S2,4) THEN RETURN ! ignore timing errors1340 DISP "DSJ=1! Status is:"1350 DISP DTH$(S1);" ";DTB$(S1)1360 DISP DTH$(S2);" ";DTB$(S2)1370 DISP DTH$(S3);" ";DTB$(S3)1375 RETURN ! continue for now1380 DISP "BYE" @ END1400 ! !!!!!!!!!!!!!!!!!!!!!1401 ! !!! WAIT TAPE ONLINE1402 ! !!!!!!!!!!!!!!!!!!!!!1410 IF BIT(FNS,0) THEN RETURN1415 BEEP1420 DISP "TAPE NOT ONLINE!"1430 DISP "PRESS CONT WHEN READY"1440 PAUSE1450 IF BIT(FNS,0) THEN RETURN1460 GOTO 14201500 ! !!!!!!!!!!!!!!!!!!!!!!!!1501 ! !!! TAPE REWIND SUB1502 ! !!!!!!!!!!!!!!!!!!!!!!!!1510 DISP "REWINDING TAPE..."1515 A=FNR ! rewind1520 GOSUB 1100 ! ppoll loop1525 ! DISP "READING DSJ"1530 D=FND ! D$=FND$1535 DISP "DSJ =";D ! DISP D$1540 DISP "STATUS:"1545 S=FNS1550 DISP DTH$(S1);" ";DTB$(S1)1553 DISP DTH$(S2);" ";DTB$(S2)1555 DISP DTH$(S3);" ";DTB$(S3)1558 IF BIT(S2,2) THEN GOSUB 1200 ! wait for rewind completed1560 GOSUB 1400 ! wait for tape online1565 RETURN1600 ! !!!!!!!!!!!!!!!!!!!!!!!!1601 ! READ 1 RECORD FWD SUB1602 ! !!!!!!!!!!!!!!!!!!!!!!!!1605 E=0 ! error flag1610 A=FNF ! read record fwd1615 GOSUB 1100 ! ppoll wait1620 A=FND ! read DSJ before status always1625 S=FNS ! check status for end of file or EOT1630 IF BIT(S1,5) THEN E=1 @ RETURN ! end of tape1635 IF BIT(S1,7) THEN E=2 @ RETURN ! end of file1640 IF BIT(S2,3) THEN E=3 @ RETURN ! tape runaway1645 A$=FNG$ ! read the data1650 GOSUB 1300 ! dsj check1655 RETURN1700 ! !!!!!!!!!!!!!!!!!!!!!!!!1701 ! NEXT FILE ROUTINE1702 ! !!!!!!!!!!!!!!!!!!!!!!!!1705 CLEAR @ KEY LABEL1706 DISP "Looking for next file..."1710 A=FNT(11) ! tape cmd 111715 GOSUB 1100 ! ppoll loop1720 GOSUB 1300 ! check DSJ1725 IF D THEN DISP "NO MORE DATA" @ GOTO 9931730 GOSUB 1600 ! read 1 record1740 IF E=1 THEN DISP "END OF TAPE" @ GOTO 9931745 IF E=2 THEN DISP "END OF RECORDING" @ GOTO 1765 ! double EOF, backup one1750 IF E=3 THEN DISP "TAPE RUNAWAY" @ GOTO 9931755 P=POS(T$,CHR$(13)) ! find CR position1757 DISP "Found file: ";1760 DISP T$[1,P] ! display up to CR1765 A=FNT(10) ! go back one record1770 GOSUB 1100 ! ppoll1775 GOSUB 1300 ! DSJ1780 GOTO 9931800 ! !!!!!!!!!!!!!!!!!!!!!!!!1801 ! !! PREVIOUS FILE ROUTINE1802 ! !!!!!!!!!!!!!!!!!!!!!!!!1805 CLEAR @ KEY LABEL @ DISP "Looking for previous file..."1810 A=FNT(12) ! tape cmd 121815 GOSUB 1100 ! ppoll loop1820 D=FND ! check DSJ1825 IF D THEN DISP "AT BEGINNING OF TAPE" @ GOTO 18501830 A=FNT(12) ! once more to really go back1835 GOSUB 1100 ! ppoll loop1840 D=FND ! check DSJ1845 IF D THEN DISP "AT BEGINNING OF TAPE"1850 GOSUB 1600 ! read 1 record1855 IF E=1 THEN DISP "END OF TAPE" @ GOTO 9931860 IF E=2 THEN GOTO 1850 ! read again past EOF marker1865 IF E=3 THEN DISP "TAPE RUNAWAY" @ GOTO 9931870 P=POS(T$,CHR$(13)) ! find CR position1875 DISP "Found file: ";1880 DISP T$[1,P] ! display up to CR1885 GOSUB 1900 ! go back one record1890 GOTO 9931900 ! !!!!!!!!!!!!!!!!!!!!!!!!1901 ! !! GO BACK 1 RECORD SUB1902 ! !!!!!!!!!!!!!!!!!!!!!!!!1910 A=FNT(10) ! go back one record1915 GOSUB 1100 ! ppoll1920 GOSUB 1300 ! DSJ1930 RETURN2000 ! !!!!!!!!!!!!!!!!!!!!!!! 2001 ! !! RECORD + FUNC KEY2002 ! !!!!!!!!!!!!!!!!!!!!!!!!2005 CLEAR2007 DISP "Reading next record:"2010 GOSUB 1600 ! read record2015 IF E=1 THEN CLEAR @ DISP "EOT"2020 IF E=2 THEN DISP "EOF" @ GOTO 9932025 IF E=3 THEN DISP "NO DATA - TAPE RUNAWAY" @ GOTO 9932030 DISP T$ ! display record data2040 GOTO 9932100 ! !!!!!!!!!!!!!!!!!!!!!!!!2101 ! !! RECORD- FUNCTION KEY2102 ! !! using fwd read 2103 ! !!!!!!!!!!!!!!!!!!!!!!!!2110 CLEAR @ DISP "Reading previous record:"2115 GOSUB 1900 ! backup 1 record2120 GOSUB 1600 ! read record2125 IF E=1 THEN CLEAR @ DISP "EOT"2130 IF E=2 THEN DISP "EOF" @ GOTO 9932135 IF E=3 THEN DISP "NO DATA - TAPE RUNAWAY" @ GOTO 9932140 DISP T$ ! display record data2145 GOSUB 1900 ! backup 12150 GOSUB 1900 ! backup 22160 GOTO 9932200 ! !!!!!!!!!!!!!!!!!!!!!!!!2201 ! READ 1 RECORD BACKWD SUB2202 ! !!!!!!!!!!!!!!!!!!!!!!!!2205 E=0 ! error flag2210 A=FNB ! read record bkwd2215 GOSUB 1100 ! ppoll wait2220 A=FND ! read DSJ before status always2225 S=FNS ! check status for end of file or BOT2230 IF BIT(S1,6) THEN E=1 @ RETURN ! beginning of tape2235 IF BIT(S1,7) THEN E=2 @ RETURN ! end of file marker2240 IF BIT(S2,3) THEN E=3 @ RETURN ! tape runaway2245 A$=FNG$ ! read the data2250 GOSUB 1300 ! dsj check2255 RETURN2300 ! !!!!!!!!!!!!!!!!!!!!!!! 2301 ! !! RECORD- FUNC KEY2302 ! !! using read bkwd 2303 ! !!!!!!!!!!!!!!!!!!!!!!!!2305 CLEAR2307 DISP "Reading prev record backwards:"2310 GOSUB 2200 ! read record2315 IF E=1 THEN CLEAR @ DISP "BOT"2320 IF E=2 THEN DISP "EOF" @ GOTO 9932325 IF E=3 THEN DISP "NO DATA - TAPE RUNAWAY" @ GOTO 9932330 DISP T$ ! display record data2340 GOTO 9934000 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!4005 ! !! TAPE FUNCTION AREA4010 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!4015 !4020 ! !!!!!!!!!!!!!!!!!!!!4025 ! FNS0 - select unit 04030 ! !!!!!!!!!!!!!!!!!!!!4035 DEF FNS0 = ! sel unit 04040 SEND 7 ; CMD 191,213,161,97 DATA 14045 FNS0=1 ! dummy return val4050 FN END4055 ! !!!!!!!!!!!!!!!!!!!!4060 ! FNR - tape rewind4065 ! !!!!!!!!!!!!!!!!!!!!4070 DEF FNR = ! rewind4075 SEND 7 ; CMD 191,213,161,97 DATA 134080 FNR=14085 FN END4090 ! !!!!!!!!!!!!!!!!!!!!4095 ! FND$ - dsj with xfer4100 ! !!!!!!!!!!!!!!!!!!!!4105 DEF FND$ = ! read DSJ4110 SEND 7 ; CMD 191,181,193,1124115 IOBUFFER T$ ! reset buf4120 TRANSFER 7 TO T$ FHS ; EOI4125 FND$=T$ ! return DSJ4130 FN END4135 ! !!!!!!!!!!!!!!!!!!!!4140 ! FND - dsj with enter4145 ! !!!!!!!!!!!!!!!!!!!!4150 DEF FND = ! read DSJ4155 SEND 7 ; CMD 191,181,193,1124160 ENTER 7 USING "#%,B" ; D4165 FND=D4170 SEND 7 ; CMD 223 ! UNTALK4175 FN END4180 ! !!!!!!!!!!!!!!!!!!!!4185 ! FNS - read Status4190 ! !!!!!!!!!!!!!!!!!!!!4195 DEF FNS = ! read status4200 SEND 7 ; CMD 191,181,193,974205 ENTER 7 USING "#%,B" ; S1,S2,S34210 FNS=S14215 SEND 7 ; CMD 223 ! untalk4220 FN END4225 ! !!!!!!!!!!!!!!!!!!!!4230 ! FNF - read record fwd4235 ! !!!!!!!!!!!!!!!!!!!!4240 DEF FNF4245 SEND 7 ; CMD 191,213,161,97 DATA 84250 FNF=14255 FN END4260 ! !!!!!!!!!!!!!!!!!!!!4265 ! FNG - get data4270 ! !!!!!!!!!!!!!!!!!!!!4275 DEF FNG$4280 SEND 7 ; CMD 191,181,193,2244285 ! ENTER 7 USING "#%,#%K"; U$ ! slower safer version using ENTER4290 IOBUFFER T$4295 TRANSFER 7 TO T$ FHS ; COUNT 128 EOI4300 SEND 7 ; CMD 223 ! untalk4305 FNG$="" ! dummy return value4310 FN END4315 ! !!!!!!!!!!!!!!!!!!!!!!!!4320 ! FNT(X) - generic tape cmd4325 ! X=9 record fwd4330 ! X=10 record bkd4335 ! X=11 file fwd4340 ! X=12 file bkd4345 ! !!!!!!!!!!!!!!!!!!!!!!!4350 DEF FNT(X)4355 SEND 7 ; CMD 191,213,161,97 DATA X4360 FNT=1 ! dummy4365 FN END4370 ! !!!!!!!!!!!!!!!!!!!!4375 ! FNB - read record bkd4380 ! !!!!!!!!!!!!!!!!!!!!4385 DEF FNB4390 SEND 7 ; CMD 191,213,161,97 DATA 154395 FNB=14400 FN END