When I first built a microcode emulator for the HP-29C, it came up with a flashing decimal point. I didn’t remember this being the case with the real thing; but that was a long time ago and I might have forgotten.
I had a vague feeling there was something about low power and a flashing dot. Perhaps it normally flashes but doesn’t if power is low. All of the earlier (“classic”) calculators had hardware driven low power indicators, and I didn’t have any hardware in the emulator for that, so the normal behaviour must be a flashing decimal point.
My daughter said, “it makes more sense for the dot to flash during low power as the consumption would be slightly less”. That’s where all of my thinking started unraveling.
The manual for the calculator does exist online. On page 42 it says, “When you are operating from battery power in RUN mode, the decimal point blinks on and off to warn you that you have a minimum of one minute of operating time left.”
My logic was blown out of the water. She was right.
Why was the calculator blinking the decimal point? Why did it think the power was low? It must know or think that – because it was the thing doing the blinking. It was under software control; not hardware.
After a bit of looking, it turns out that the s 5 flag gets set (1) if there is sufficient power for normal operation and it is clear (0) if low power is detected. The calculator checks the s 5 flag and changes its behaviour accordingly.
The following retraces how I got to that point, and explores how the calculator flashes the dot. (I added the ability to “Set Internal” “voltage” in HP29w Ver 0.02)
C:\Test\hp29w>hp29w -d HP29w Ver 0.02 Copyright 2018 Greg Sydney-Smith CPU speed is 200 kHz >>> si voltage 1.9 >>> g (You see 0.00 with a flashing decimal point) (Switch to OFF to return to the debugger ...) >>> ts200 00345: p - 1 -> p ; P=13 00346: 0 -> s 15 ; 00347: if 1 = s 15 then goto 00175 ; 00351: if p # 4 then goto 00345 ; 00345: ... ; P=12,11,10,..., 4 00351: if p # 4 then goto 00345 ; 00353: p <- 0 ; P= 0 00354: a + 1 -> a[p] ; A=0000FFFFFFFF05 00355: if n/c goto 0345 ; 00345: ... ; P=13,12,11,..., 4 00351: if p # 4 then goto 00345 ; ; 00345-00351 is a delay loop: for (p=13; p>4; p--) ; There are 10 loops, over 6 words. It takes 60 T-states. 00353: p <- 0 ; P= 0 00354: a + 1 -> a[p] ; A=0000FFFFFFFF06 00355: if n/c goto 0345 ; ... 00354: a + 1 -> a[p] ; A=0000FFFFFFFF07 00355: if n/c goto 0345 ; ... 00354: a + 1 -> a[p] ; A=0000FFFFFFFF08 00355: if n/c goto 0345 ; ... ... 00354: a + 1 -> a[p] ; A=0000FFFFFFFF0F 00355: if n/c goto 0345 ; 00354: a + 1 -> a[p] ; A=0000FFFFFFFF00 00355: if n/c goto 0345 ; ; 00353-00355 is a loop around the delay loop: ; for (A[0]=0; A[0]<0x10; A[0]++) ; It runs 16 times. The extra overhead is 3 words. ; Total time is 16*(3+60)= 1008 T-states. ; it ends by putting what was in A[0] back, and returning 00356: p <- 0 ; 00357: a exchange b[p] ; 00360: return ;
A subroutine suggests "part of a bigger picture" so, after a little looking around, here's the bigger picture:
>>> l152 00152: 0 -> s 1 00153: 0 -> s 3 00154: if 0 = s 3 then goto 00163 00156: if 0 = s 11 then goto 00165 00160: 0 -> s 11 00161: ... 00163: if 0 = s 11 then goto 00232 00165: 0 -> s 5 00166: if 1 = s 5 then goto 00173 00170: jsb 0331 00171: jsb 0331 00172: nop 00173: if 0 = s 15 then goto 00152
This should look familiar because we've seen some of it before. Address 00152 is the start of the HP-29C wait loop.
We're mainly interested in the lines from 00165 which check the s 5 flag. If power is fine, it skips over to 00173 and checks for a keypress or loops back to 00152 to see if anything has happened since last time.
If power isn't fine, it does two "jsb 0331" instructions then continues as for the "power is fine" situation.
We've seen some of the 00331 process above. Here's what it looks like in a listing:
>>> l331 00331: p <- 0 00332: if a[p] # 0 then goto 00361 00334: a - 1 -> a[p] 00335: jsb 0365 00336: c - 1 -> c[p] 00337: b exchange c[w] 00340: p <- 1 00341: load constant 2 00342: c -> addr 00343: data register -> c 15 00344: a exchange b[p] 00345: p - 1 -> p 00346: 0 -> s 15 00347: if 1 = s 15 then goto 00175 00351: if p # 4 then goto 00345 00353: p <- 0 00354: a + 1 -> a[p] 00355: if n/c goto 0345 00356: p <- 0 00357: a exchange b[p] 00360: return ... 00365: p <- 13 00366: p - 1 -> p 00367: if b[p] = 0 then goto 00366 00371: b -> c[w] 00372: return >>>
00345-00355 is a very tight way of doing the two nested loops discussed above.
s 15 is the key pressed flag so it is being checked in the loop to keep the keyboard responsive. We'll probably see 00175 when we explore what happens when a key gets pressed. We're going to note but ignore it for now.
00356-00360 is the exit from the loop that we already saw.
That leaves 00331-00344.
00334 only gets run if A[0] is 0 (because of 00331 and 00332/3). It sets A[0]=0xF
00335 calls 00365.
00365 starts just right of the mantissa sign and goes looking through B until it finds a non-zero digit. In the display formatting used in B:
- a '2' indicates a sign should be shown (0 in A shows as blank, 9 in A shows as '-')
- a '9' indicates a decimal point should be added ('5' shows as '5.')
- a '0' indicates a normal digit ('3' shows as '3')
The subroutine basically sets P to where the decimal point is.
I've set talk to 7, ENTERed 2.34 and rerun the loop. Here's what we get:
00170: jsb 0331 ; 00331: p <- 0 ; 00332: if a[p] # 0 then goto 00361 ; 00334: a - 1 -> a[p] ; A=0234FFFFFFFF0F 00335: jsb 0365 ; 00365: p <- 13 ; P=13 00366: p - 1 -> p ; P=12 00367: if b[p] = 0 then goto 00366 ; 00371: b -> c[w] ; C=29000000000000 00372: return ; 00336: c - 1 -> c[p] ; C=28000000000000 00337: b exchange c[w] ; B=28000000000000 C=29000000000000 00340: p <- 1 ; P= 1 00341: load constant 2 ; C=29000000000020 P= 0 ; ram_addr=32 00342: c -> addr ; ; data[47] -> C=02340000000000 00343: data register -> c 15 ; C=02340000000000 00344: a exchange b[p] ; A=0234FFFFFFFF00 B=2800000000000F 00345: p - 1 -> p ; P=13 00346: 0 -> s 15 ; 00347: if 1 = s 15 then goto 00175 ; >>>
You can see 2.34 in the A register (and F after that to blank out later digits).
You can see the '9' that indicates the decimal point changing to an '8' which doesn't.
You can also see it getting the X value out of ram[47] (bank 2, register 15).
We are also in the delay loop (00345...).
The display is on, so the A/B combination is displayed and we see " 234 ".
The next high level step is 00171 jsb 0331. This time we get:
>>> g,360 Breakpoint 1 at 00360 >>> ts 00360: return ; 00171: jsb 0331 ; 00331: p <- 0 ; 00332: if a[p] # 0 then goto 00361 ; 00361: 0 -> a[p] ; A=0234FFFFFFFF00 00362: jsb 0365 ; 00365: p <- 13 ; P=13 00366: p - 1 -> p ; P=12 00367: if b[p] = 0 then goto 00366 ; 00371: b -> c[w] ; C=28000000000000 00372: return ; 00363: c + 1 -> c[p] ; C=29000000000000 00364: if n/c goto 0337 ; 00337: b exchange c[w] ; B=29000000000000 C=28000000000000 00340: p <- 1 ; P= 1 00341: load constant 2 ; C=28000000000020 P= 0 00342: c -> addr ; ram_addr=32 00343: data register -> c 15 ; data[47] -> C=02340000000000 00344: a exchange b[p] ; 00345: p - 1 -> p ; P=13 00346: 0 -> s 15 ; 00347: if 1 = s 15 then goto 00175 ; 00351: if p # 4 then goto 00345 ; 00345: p - 1 -> p ; P=12 >>>
This time, a[p] does equal 0 so we goto 00361 and that includes add 1 to c[p] at 00363; instead of the subtract 1 we did last time at 00336.
We end up with B=2900... instead of 2800... and the decimal point gets displayed. We see " 2.34 ".
At the highest level, we see:
>>> g,00170 Breakpoint 1 at 00170 >>> dr A=0234FFFFFFFF00 D=02340000000000 M1=02340000000000 P= 0 B=29000000000000 E=00000000000000 M2=00000000000000 C=02340000000000 F=00000000000000 S =...3............ >>> g,00171 ; ram_addr=32 ; data[47] -> C=02340000000000 Breakpoint 1 at 00171 >>> dr A=0234FFFFFFFF0F D=02340000000000 M1=02340000000000 P= 0 B=28000000000000 E=00000000000000 M2=00000000000000 C=02340000000000 F=00000000000000 S =...3............ >>>
Note the B values and the A[0] values.
In Summary
If s 5 = 0 then we run the low power code.
This is really just two lines, at 00170 and 00171. Both of these are "jsb 0331".
The first time through 0331 has A[0]=0. It changes the '9' in B for the decimal point to an '8'. That turns off the decimal point.
The second time through 0331 has A[0]=F. It changes the '8' back to a '9'. The decimal point gets displayed again.
Timing
1008 T-states with a 200 kHz clock and 56 bits per opcode word is about 1/3 of a second. The display blinks on for 1/3s, off for 1/3s, and so on.
200 kHz / 56 = 3571 op words per sec.
1008 / 3571 = 282 mS, a little faster than 1/3s, but not much.
This is part of the HP-29 topic.
Hi – just what I need for my Woodstock simulators – thanks!
0.333 second blink frequency looks great.