From: ervin@pinbot.enet.dec.com (Joseph James Ervin) Newsgroups: comp.sys.hp48 Subject: TICKS revealed ! Date: 19 Feb 92 17:38:14 GMT Reply-To: ervin@pinbot.enet.dec.com (Joseph James Ervin) Organization: Digital Equipment Corporation Ever wonder how the HP48 keeps track of time? Well, here is the disassembly of the built in TICKS command, which returns the current clock TICKS value to the stack as a binary integer. "But what does this integer represent?", you may ask. The TICKS command returns the number of clock ticks since the birth of Christ, where a "tick" is 1/8192 of a second. Now you know why the TICKS command returns such a large number. All time representations inside the HP48 are in this 13-nibble binary integer format. So you see, the "ticks" format is an absolute time, i.e. you can decode the hour, minute, second, day, month, and year all from the ticks "But how does the HP48 actually keep time?", you ask? Like this: The HP48 keeps in memory, at locate 70052, a CRC protected ticks value which represents the "next event", i.e. the time at which the next timer interrupt will occur. The "next event" may be a user alarm, or the time at which the 10 minute auto-power-off time should expire, or a couple other possible things which I won't get into now. The key point is that the "next event" time stored at 70052 represents a future time at which the timer (TIMER2) will generate an interrupt due to underflow. TIMER2 counts down at a rate of 8192 ticks per second. The TIMER2 timer keeps track of the number of clock ticks UNTIL THE NEXT EVENT. In this way, the HP48 can determine the current time simply by subracting the current contents of TIMER2 from the "next event" value stored at 70052. Simple. This is essentially what the TICKS command does. The "next event" value and the contents of TIMER2 are updated whenever an interrupt occurs. Below is the disassembly of the TICKS command, for you enjoyment. >>>Joe ******************************************************************* ******************************************************************** 1982D: ; *** TICKS (XLIB 2 18) *** 1982D: 02D9D ! Program 19832: 18A1E ; Save current command, stack size, clear @706FD.S 19837: 0EB81 ; Internal TICKS 1983C: 0312B ! End Marker ******************************************************************** ******************************************************************** 0EB81: ; *** Internal TICKS *** 0EB81: 02D9D ! Program 0EB86: 0EDB9 ; create Binary Integer (13 nibbles) 0EB8B: 02DCC ! Code 0EB90: 00013 ! 19 nibbles (next RPL at 0EBA3) 0EB95: 8F70310 GOSBVL 01307 ; MC: let C(13) = TICKS 0EB9C: 108 R0=C 0EB9F: 6013 GOTO 0EEB0 ; MC: store R0(13) into existing stk1 ; Binary Integer(13) and continue RPL . . . . . . ! End of Code 0EBA3: 0312B ! End Marker ******************************************************************** ******************************************************************** 01307: ; *** MC: let C(13) = TICKS *** 01307: 8FB9760 GOSBVL 0679B ; save D0,D1,B,D , clr carry 0130E: 77EF GOSUB 012F9 ; Reads the timer register if possible ; and does a bunch of RSI's if not. 01312: 500 RTNNC ; My guess is that carry=0 indicates ; that the timer has been successfully ; read, and c(13) contains the ticks. 01315: 24 P= 4 ; If carry was set, then the ; "next event" was screwed up, so do a ; warm start. 01317: 8C3AEE GOLONG 001BC ; Do a warm start. ******************************************************************** 012F9: 84F ST=0 15 ; Disable all interrupts. 012FC: 7CFE GOSUB 011FC ; Check system state and read the ; timer register if possible. Carry ; set if couldn't read timer. Timer ; value in C. 01300: 8D5E010 GOVLNG 010E5 010E5: 85F ST=1 15 ; Reenable interrupts. 010E8: 8080 INTON ; Enable keyboard interrupts. 010EC: 441 GOC 01101 ; If timer read was unsuccessful, then ; skip down to ^x1101. 010EF: 86E70 ?ST=0 14 ; If no unsatisfied interrupt requests ; pending then skip down. Else we may ; have missed a keyboard poll, so ; reset the keyboard interrupt state ; machine. ; GOYES 010F9 010F4: 80810 RSI ; Reset the keyboard interrupt state ; machine to catch any key presses that ; may have occured while interrupts ; were disabled. 010F9: 86F41 ?ST=0 15 ; Alternate entry point. We just set ; ST<15> up above. GOYES 01110 ; 010FE: 511 GONC 01110 ; Skip down to 1110. 01101: 86EC0 ?ST=0 14 ; If ST<14>=0, then skip to end. GOYES 01110 01106: 80810 RSI ; Reset keyboard int. state machine. 0110B: 87F20 ?ST=1 15 ; ST<15> should be set, so this must GOYES 01110 ; be to set the carry bit. 01110: 84E ST=0 14 01113: 0F RTI ; Return, enable interrupts. ; We _need_ to use RTI here because ; we disabled all interrupts before ; by clearing ST<15>. If there were ; any unsatisfied interrupts in the ; interim, then the 48 will not allow ; any more interrupts until it sees ; RTI. ******************************************************************** ******************************************************************** 011FC: 04 SETHEX 011FE: 1FF2100 D1=HEX 0012F 01205: 15F0 C=DAT1 1 01209: AC3 D=0 S 0120C: 808B050 ?CBIT=1 0 ; Is bit 0 of ^x12F set? GOYES 01216 ; If yes, then skip next instruction. 01213: B47 D=D+1 S ; Else increment D.S. 01216: 1DB1 D1=HEX 1B 0121A: 143 A=DAT1 A ; Read location ^x11B for the upper ; nibble. Save it in A. 0121D: 1D40 D1=HEX 04 ; D1 = 104 01221: D2 C=0 A 01223: 15D3 DAT1=C 4 ; Write four 0's to ^x104. This ; intializes the CRC generator. 01227: 131 D1=A ; Fetch that upper nibble from 11B. 0122A: 1E2500 D1=HEX 0052 ; D1=^x70052. 01230: 15FC C=DAT1 13 ; Get 13 nibbles from location ^x70052. ; The upper 5 nibbles contains the ; upper 5 nibbles of the system time. ; Recall that time is stored as 13 ; nibbles but the hardware timer is ; only 8 nibbles wide. The act of ; reading this data causes the CRC ; generator to calculate CRC over the ; stored time. 01234: 1F40100 D1=HEX 00104 ; 16 bit hardware CRC. 0123B: D2 C=0 A ; Clear the bottom 5 nibbles of C. 0123D: 15F3 C=DAT1 4 ; Now read the CRC generator and ; compare it to the stored CRC from ; location ^x7005F. Note that stored ; CRC is located just after the 13 ; nibbles of stored system time. 01241: 131 D1=A 01244: 1EF500 D1=HEX 005F 0124A: D0 A=0 A 0124C: 15B3 A=DAT1 4 ; ; 01250: 8A250 ?C=A A ; Is the 4 nibble value from the CRC ; register = to the stored CRC? GOYES 01258 ; If so then skip next instruction. 01255: B47 D=D+1 S ; Increment error counter. 01258: 1E2500 D1=HEX 0052 0125E: AF0 A=0 W 01261: 15BC A=DAT1 13 ; Read the stored system time again. 01265: AF8 B=A W ; Copy it to register B. 01268: 1F83100 D1=HEX 00138 ; Point at hardware timer. ; Note that the act of initializing the CRC generator with 0000 is ; necessary to accumulate a clean CRC. Since the CRC generator seems to ; do this by watching data coming into the CPU, it is imperitive that no ; interrupts be allowed during this process. This is one reason why ; interrupts are disabled at the top of the TICKS code. 0126F: AF2 C=0 W ; 01272: 20 P= 0 01274: 94F31 ?D#0 S ; Is error counter still = 0? GOYES 0128A ; If not, then skip down to ^x128A. 01279: 15B0 A=DAT1 1 ; Else read _bottom nibble_ of TIMER. 0127D: 15F0 C=DAT1 1 ; Read _bottom nibble_ of timer into C. 01281: 9029F ?C=A P ; Loop here until bottom nibbles are GOYES 0127D ; different,.... 01286: 15F7 C=DAT1 8 ; then read the whole timer register. 0128A: AF4 A=B W ; We came here cause coudn't read ; timer. Save a copy of the 13 nibble ; value from ^x70052 back in A. (We ; come here also when we _do_ read ; the timer.) ; What happens next is that the 8 nibble value from the hardware timer ; is sign extended to 16 nibbles. 0128D: 27 P= 7 ; Point at nibble 7. 0128F: A85 B=C P ; Register B is used as scratch here. ; Change nibble 7 to that read from ; timer, or to zero if we coudn't ; read the timer.... 01292: A05 B=B+B P ; ...and double it. 01295: 580 GONC 0129E ; If B.7 didn't overflow, go to ^x129E. ; If B.7 did overflow, then we need to ; set the upper 8 nibbles of the ; register holding the timer value ; to all 1's, thus sign extending it. 01298: BFA C=-C W ; Negate the value from the hardware ; timer. 0129B: B9A C=-C WP ; Now re-negate the bottom 8 nibbles. ; This effectively makes the 8 nibbles ; read from the hardware timer ; unchanged, but makes the upper half ; of register C all 1's. 0129E: AFF CDEX W ; Save the timer value in D. D.S gets ; copied into C.S. Now C.S = 0 if ; timer was read, and is nonzero if ; we couldn't read the timer. 012A1: 80DF P=C 15 ; P = this "timer_read semaphore". 012A5: AF6 C=A W ; Get the copy of location ^x70052. 012A8: B7B C=C-D W ; Subtract the timer value from the ; "next event" value from location ; ^x70052. The result is the actual ; system time in clock ticks since the ; birth of Christ. ; All that remains now is to do a bounds check on the resulting system time ; to make sure it makes sense. 012AB: 89060 ?P= 0 ; Did we ever read the timer at all? GOYES 012B4 ; If yes, then go down to ^x12B4 012B0: 20 P= 0 012B2: 02 RTNSC ; Otherwise, return with the carry ; bit set to indicate failure. ; We have come here because the timer ; was successfully read. 012B4: AF5 B=C W ; Save the time difference in B. 012B7: AF2 C=0 W ; Clear C. 012BA: 25 P= 5 ; Point to the fifth nibble. 012BC: 37CC36A3D1 LCHEX 1D3A63CC ; Here we are doing a bounds check on ; the calculated time to make sure that ; it falls within the date limits ; allowed by the calculator. This is ; the low limit. ; ; 012C6: 20 P= 0 ; Restore P to zero. 012C8: AFD CBEX W ; Pull the time difference back into ; register C. 012CB: 9F100 ?B>C W ; Return to calling routine if this ; low limit value is greater than RTNYES ; the calculated time. The ; carry bit should be set if ; returning. 012D0: AFD CBEX W ; Else repeat this limit test with ; the upper date limit. 012D3: AF2 C=0 W 012D6: 25 P= 5 012D8: 373F592BE1 LCHEX 1EB295F3 ; 012E2: 20 P= 0 012E4: AFD CBEX W 012E7: 9FD00 ?C>=B W ; Return to calling routine if the ; calculated time value ; is greater than ; the upper limit. The ; carry bit is set to indicate ; failure. ; RTNYES ; 012EC: 03 RTNCC ; Return with carry clear to indicate ; success. The calculated time value ; is in register C.