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):
UNL=63 -> UNL(odd)=191
MTA=85 -> MTA(odd)=213
MLA=53 -> MLA(odd)=181
LISTEN 1=33 -> LISTEN 1(odd)=161
TALK 1=65 -> TALK 1(odd)=193
UNT 1= 95 ->UNT(odd)=223
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.