Skip to main content

Reading program cards on the HP-97

HP65 Card reader image

As I mentioned in my last post, the card reader on my HP-97 isn't working. I was able to power the motor directly, bypassing the bad Sense Amp chip, but the calculator still always went into an error state. With a mind to get to the bottom of the problem, and a hope to preserve the contents of the "Stat-Pac" that was included with my calculator, I instrumented the connection between the Sense Amplifier and the CRC (Card Reader Controller).

Capturing Card data

Instrumenting the CRC

Essentially, I connected the motor to my lab power supply, and set it for 2.5 volts. Armed the Saleae, triggering it off of the change of state on one of the read heads, and run a card through. I scanned both sides of the roughly 25 cards in the Stat-Pac. Then I saved each of these traces in both Saleae capture files, and CSVs.

Saleae capturing CRC data

This ended up taking about 30 minutes to complete, but I ended up making more work for myself. I should have split each file into a side 1 and side 2 CSV file while I was doing the work to begin with. A quirk about how the data is encoded on the card makes it hard to uniformly process the files when they are run through the reader one-after-another.

Processing low-level signaling

To begin, I split the first card into two separate CSV files, one for each side. Then I began writing a quick rust program to process the transitions (output of Saleae) into bits, then nibbles, then words, and finally attempt to check it against the checksum on the card.

Later, I decided that I should bite bullet and split all the traces up into their side a and b. This took another 30 minutes of mindless clicking, but I think it was well worth it. I discovered that two scans had missing bits, likely due to missed phase inversions. It's unclear whether these are real issues on the card, or if they are spurious.

Image from the HP Journal regarding the recording scheme

The link to the rust program above is to the repository that contains the rust program source, and all of my card scans, if you'd like to follow along.

Throughout this process, a guy that goes by Teenix has been providing invaluable assistance, both in his website, and messages on the HP Museum. His Classic Notes document is not only the best resource for understanding the low-level signaling on the card, but also the meaning of that signaling. Another fantastic resource is the May 1974 edition of the HP Journal. It's focused on the HP-65, but much of it still applies..

Card data header, as captured from the device

The image above is an example of a captured card header. The data is written via flux inversions on track A and B (RA and RB are "Read A" and "Read B" respectively). A flux inversion on track A is a 1 bit, and an inversion on track B is a 0 bit. The data is sent LSB first, and big endian. In this case, these signals correspond to a header that says "Fixed point representation, with two digits after the decimal, degrees, flag 4 is set, and program card 1. There is one nibble that seems to have undefined meaning in this trace; and that's number 5 (zero indexed). Teenix's document suggests that it should be either 1 for a 1-card program, or 2 for a 2-card program. In this case it's set to zero. All of my cards have this set to zero, and I've very curious why...

Checking out checking the checksum

Now that I have the low-level signaling of the card data working well enough, it's time to look into the checksum. Ideally, I'd be able to use the simple algorithm from Teenix's document and verify all my my captures, but it didn't work out that way.

My ST-12b Teenix ST-12b Teenix OpCode Teenix dissassembly
2220104 2220004 STATUS FIX2, DEG, No Flags, Prog side 2
b009f85 b009f85
03839b8 03839b8
514e000 514e000
53e08f8 53e08f8
skipped skipped skipped skipped
7acb24b 7acb14b checksum

There are several things to note in the table above. First, I've listed the contents I've scanned from my own copy of the ST-12b card. This card is a little special in that it's a a very short program card. So, it's a lot easier to look at it bit-by-bit, and nibble-by-nibble. First of all, you should be able to see that my copy and Teenix's copy are almost exactly the same. The only difference is in nibble 4 of the status word. My copy has flag 4 set, and Teenix's doesn't; this is also reflected in bit 4 of the checksum.

I had intended to also disassemble the program data on the card into opcodes and the program listing, but that part of the process is still a mystery to me. I'll probably make another post when I've got that figured out. For now, I'll leave the table columns in place.

This is a very encouraging result, because it does validate that the majority of my data processing pipeline works perfectly. Also, in this case, I have a failure to compute the correct checksum in my rust program. Therefore, this is a perfect case to use to start digging into my failed assumptions about how the checksum is computed.

Teenix's Classic Notes document has this to say about the checksum:

The checksum is the sum of the STATUS and program nibbles

And gives the following example:

Word Meaning
2220013 STATUS
1111111 Reg $2F high
1111111 Reg $2F low
0000000 remaining
0000000 registers
... ...
4442235 checksum

This implies that each nibble position in the checksum is the simple sum (mod-16) of every nibble above it in that column (including status). Unfortunately, because none of the nibbles in this example overflow we have to just assume that it's mod-16 (discarding all but the least-significant nibble of the sum).

Again, using the Teenix ST-12b example, we can perform that operation by hand (all operations in hex)

n0 n1 n2 n3 n4 n5 n6 Notes
2 2 2 0 0 0 4 $1F high
b 0 0 9 f 8 5 $1F low
0 3 8 3 9 b 8 $1E high
5 1 4 e 0 0 0 $1E low
5 3 e 0 8 f 8 $1D high
0 0 0 0 0 0 0 $1D low
. . . . . . . skipped
0 0 0 0 0 0 0 $10 low
17 9 1c 1a 20 22 19 computed checksum
7 9 c a 0 2 9 mod 16
7 a c b 1 4 b card checksum
0 1 0 1 1 2 2 abs error

The table above demonstrates the problem when computing the checksum with this method. Frustratingly, the errors are small in magnitude, but exist on most of the nibbles. Furthermore, the errors don't seem to be related to whether or not it overflows. Nibble zero is correct, even though it overflows, nibble 1 is wrong and doesn't, and nibble 2 is correct and doesn't overflow. Also, the card checksums are always higher-valued than the computed checksum! It's almost like there are some values that aren't being included in the summation.

Next steps

Anyway, that's where I'm at now. I'm going to try and get some feedback from Teenix and the community. I'm at the point where I'm considering soldering semi-permanent wires onto the CRC chip so I can try capturing the same card multiple times and seeing if they're different and by how much.


Comments powered by Disqus