By bill duncan. Created: 2000.05.21
After many years of carrying an hp-41 around just because I liked it, I've finally given it a job where it gets almost daily use. As someone who bills clients for time, I've found over the years that much of the time falls "through-the-cracks" because I wasn't careful about documenting it. I call the program a Time Manager, although strictly speaking it just helps me with my time accounting.
The program may also find use anywhere which requires tracking more than one (possibly overlapping) time span.
The program will track up to ten active timers with times up to 24 hours. Ten accumulated time registers store totals for those ten timers, and will accumulate up to the precision of the calculator. (eg. While a timer is active, it can only track a span of up to 24 hours. When a timer is "closed" however, it is accumulated in another register which can hold as much time as you need.)
Each timer has a key associated with it, labels "A" through "J". In user mode, these keys will toggle a timer on or off. When toggled off, a timer will briefly display the current elapsed time for that timer and then add it to the accumulated time for that timer.
The lower case or shifted user keys "a" through "e" provide some status display and control functions. The "a" key displays "Accumulated times", while the "d" key displays any active timer times. The "b" key just displays a list of active timers. The "c" key "Closes" and accumulates all active timers (with the same timestamp). And finally the "e" key will "Erase" all active timers and accumulations.
To turn the calculator off while the "TIME MANAGER" display is showing, just press the "R/S" key. This will turn the system off and set it up for auto execution when turned on again.
Flag 00 informs user whether there are any active timers.
If flag 01 is set, the system will only allow one active timer at a time. (Useful to avoid double billing <g>). Although it will take several seconds to execute, the system will use the same timestamp to close all active timers and to turn on the desired timer.
Flag 02 is reserved for future use. (Probably for swapping registers in and out of "secondary storage" or extended memory.)
Setting flag 03 will pause the display on most information displays. This is automatically cleared if flag 21 is set (which will either stop the program or print the results anyway, which makes pausing redundant).
Active timers are kept in H.MS format while accumulations are kept in HR format. All displays are controlled by flag 04 however, which controls which format the times are displayed in. If clear, HH:MM:SS format is used, and if set the display shows decimal hours.
Implements basic timer functions for up to ten timers. A useful program for those of us who bill for time, or otherwise need to keep track of up to ten time intervals. Note that this program has given me a good (exc)use or reason to carry my favourite HP-41CX around most of the time!
Each active timer is good for up to 24 hours, and accumulations are made for each timer when toggled off. The accumulations can hold any number of hours up to the limits of precision.
I tried to keep time critical timer toggle routines up near front to minimize label lookup times. Should be only a small fraction of a second. The close all active timers routine label was also moved up front in version 1.5 to minimize search time.
I used short form labels (LBL 00 - LBL 14) where possible, although avoided using LBL 01 to LBL 10 for future use as other access routines from keyboard. (Using XEQ as prefix key and top two rows of keys might be useful in future, perhaps for displaying individual timers.)
In fact, several places reuse LBL 13 when used with forward references to skip over short blocks of code. This saves several bytes and speeds things up slightly. All the numeric labels will be compiled when branched to the first time which will speed up subsequent branches.
Note that the statistical registers (SIGREG) are relocated to register 22. This is to prevent the accidental press of the SIG+ or SIG- key without the USER mode on from screwing up the timer accumulations. All other keypresses on the top two rows of keys would be benign. But this one bit me once when I had switched out of USER mode temporarily (and forgot to switch it back on).
Also note that I have avoided the use of "synthetic programming", even though it would've come in handy in several places. I wanted the program to be generally accessible to most people, and thought that synthetic programming would limit (and perhaps frustrate) some of the intended audience.
Note that if you have flag 01 set (for single timer limit) and try to toggle a timer off with its toggle key, it will close the timer (accumulating the time) and then re-open it with the same timestamp, effectively restarting it.
When I originally encountered this, I wasn't expecting it and considered it to be a bug I needed to fix. However, it has turned out to be a useful feature.
This feature may be used to effectively lengthen the span of a timer past the 24 hour limit without losing any precision, as the time will be accumulated and then the active timer restarted at zero with the same timestamp. To use the feature, just set flag 01 and toggle the timer to be extended sometime before the 24 hour limit. (Do it as many times as you wish, as the time is accumulated without loss.)
This is not really possible without the flag 01 functionality, as toggling the timer off (and accumulating the time) then toggling it on again as a separate operation will lose the time required between keypresses (which can amount to several seconds).
To close a timer while flag 01 is set, just use the LBL "c" routine. Since there will only be one active timer, it will not impact any others and will close the timer and accumulate the time normally. Toggling another time on will also close the existing active timer.
Some of the future features I'd like to add as separate modules if there is any interest are:
- Register swap into and out of extended memory (this is simple, and I already have a working version, although not included here). This simply swaps out the registers used temporarily, in case you need to use the calculator for something else.
- Client billing - using a client/activity model associated with each key. My thoughts are not complete on this, but I envisage something like a 2 digit client number and a 2 digit general activity (eg. phone support) or project number which can be associated with each key for the day. The accumulations can then be added to records on a tape drive or mag cards to be tallied and itemized for a monthly billing.
- Hooks for program driven time monitoring, perhaps for data logging applications?
HP users have been exchanging programs for many years, dating back to the first programmables in the 1970's. It's only become fashionable of late to term this type of software "Open Source", as the source code is "in the open" and can be freely modified.
Unfortunately, much of the early work of HP calculator users in the form of the HP Users Library has been lost. This is a shame, but it doesn't have to end there. I believe that there is enough interest available to rebuild another library. Using something like the GPL might ensure that a new library doesn't end up in landfill somewhere.
The GPL is a legal means of sharing programs "in the open" which can protect programmers and their work. The intent of the GPL license is that the work should remain free (and open), and that improvements may be incorporated back to the original for all to use.
With this in mind, the following is the Copyright Notice for the program and documentation:
TMGR, Time Manager for the HP-41C series Calculators
Copyright (c) 2000, William J. DuncanThis program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 or any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
This license is available from: http://www.gnu.org/copyleft/gpl.html
You can contact the author at: bduncan@beachnet.org
Please send any improvements or suggestions along.
If anyone has access to bar code printing, I'm sure Dave wouldn't mind including a link to either a postscript or a PNG graphic form of the bar code. At the time of publishing, I didn't have the means to produce the bar code. Thanks.
External Labels (User routines) TMGR and 99 - loop to start of program A-J - toggle timer 1-10 a - display accumulations for times which have been closed b - list timers which are active c - close and accumulate all active timers d - display all active timers and times on e - erase all (could be secondary functions or swap in future) 01-10 - unassigned yet (future use, prob display individual times) Flags Used 00 - at least one active timer running 01 - only one active timer allowed (close others first) 02 - swapped to secondary storage extended memory (not implemented) 03 - pause if set, cleared automatically if Flag 21 set 04 - alternative hour display (rather than hh:mm:ss) 10 - temp flag 11 - auto on 21 - set this manually if no printer and you want display stopped for each value, eg. for writing down accumulations Internal Labels 11 - All timer toggles funnel through this routine 13 - used for short forward branches in several places 19 - toggle timer named in X register, with Timestamp in Y 18 - body of LBL "c", close all active timers 20 - body of LBL "a", display accumulations 30 - body of LBL "b", display list of active timer numbers 40 - body of LBL "d", display active timers and times 80 - set up for display of timer number 81 - display timer with calculated time difference 82 - display timer with time given 83 - display in HR format Registers 00 - number of active timers 01-10 - timers in H.MS format, 0 if timer is not active 11-20 - accumulated times in HR format 21 - temp storage for LBL 11 (actually LBL 13 after) needed because stack is too short to store a timer number across the close-all-active (LBL "c") subroutine call 22 - SigREG is relocated here!
Note that the convention used for my program listing is somewhat different than normal. I have used the hash symbol (octothorpe or "#") as a comment character. These should not be entered, but are used as commentary to the program.
Some lines have more than one operation, where I thought that it would clarify the program listing. Where I have done this, I've separated the operations with commas. I've also used indentation to clarify the program structure.
Many of the characters available on the HP-41C series are not part of the standard ASCII character set. In these cases, I have replaced the characters with ASCII and enough information (I hope) in which the correct HP-41C codes can be deduced.
eg. "SIGREG nn" moves the Sigma registers to the register nn. (Sigma being the summation symbol.)
The form "X!=0?" is the test of X not equal to zero. The "!=" being standard among C programmers as a "not equal" symbol (since the equal sign with a stoke through it is unavailable.)
There may be others. When you key this in, you should have a program which is 221 lines (including the "END") and 435 bytes (I think). The listing follows. Enjoy!
KEYS # ------------------------------------------------------------------------------ # The version number is always a good idea in programs, and normally doesn't # display unless loading with the autoexecute flag 11 turned on. # "V1.5" # identification, executed once on autostart AVIEW # if loaded from tape, cards or file LBL "TMGR" # main program start LBL 99 # everything loops back here # turn on active timer annunciator if any timers active CF 00 # assume no timers active RCL 00 # number of active timers X!=0? # if non-zero SF 00 # then turn on annunciator FS? 21 # if printer (or view stop) flag set CF 03 # then we don't need to pause on displays SF 27, SF 29, FIX 4 # make sure these conditions are set SIGREG 22 # make sure these registers do not interfere!! "TIME MANAGER" # display prompt -or- alternate "ON THE CLOCK" PROMPT # just press R/S to turn off SF 11, OFF # set for auto execute and turn off GTO 99 # ------------------------------------------------------------------------------ # Jump here for the time critical timer toggle keys # The forward search should take minimal time, since we're stopped # just above. LBL "c" XEQ 18, GTO 99 LBL "A", 1, GTO 11 LBL "B", 2, GTO 11 LBL "C", 3, GTO 11 LBL "D", 4, GTO 11 LBL "E", 5, GTO 11 LBL "F", 6, GTO 11 LBL "G", 7, GTO 11 LBL "H", 8, GTO 11 LBL "I", 9, GTO 11 LBL "J", 10 # GTO 11 not needed here. fall through... # ------------------------------------------------------------------------------ # All timer toggle keys A-J funnel through here... LBL 11 FS? 01 # if f01 set GTO 13 # then we close off all other active timers first TIME, X<>Y # place time in Y register almost immediate and swap to Y XEQ 19 # we execute this rather than jumping to it... GTO 99 # ...because LBL 19 is also executed elsewhere LBL 13 # come here on f01 STO 21 # need to store timer number temporarily, no room on stack XEQ 18 # this is the LBL "c" close all active timers routine RCL 21 # with timestamp still in X, get timer number back XEQ 19 # Y=timestamp, X=timerno -- now toggle timer on GTO 99 # back to main # ------------------------------------------------------------------------------ # LBL 19 -- Toggle timer on/off with display # # This routine toggles the timer on or off. Enter and Exit with # Y = Timestamp # X = Timerno (timer number) # # Must exit with Timer number in X register and timestamp in Y! (For LBL "c") # Z and T registers are not preserved on exit! # LBL 19 XEQ 80 # set up timer number part of display RCL IND X # get timer current timer value X=0? # if it is clear, then that timer not on GTO 13 # so skip this and go to LBL 12 CF 00 # assume all timers are off DSE 00 # decrement number of timers SF 00 # turn annunciator back on if any timers are still active # this next instruction (RCL Z) is why we need R21 for temporary storage. # We need to keep the extra copy of the timestamp. # RCL Z # Roll Z->X (current time), Y=timerval, Z=timerno XEQ 81 # now finish up display with calculated time HR # convert H.MS -> HR format for storing accumulated time X<>Y # swap time with timer number 0, STO IND Y # clear out running timer value RDN # discard zero, timer number back in X 10, + # add ten to timer number to get accum reg range X<>Y # swap with calculated time again ST+ IND Y # accumulate into total for that timer RDN # discard time, timer no (+10) back in X 10, - # set up for exit with timer number in X, Timestamp in Y RTN # (return to LBL 11 routine or LBL 14 loop) # Here if timer was not running. Note that we never reach here from the # Close (LBL "c") routine as it pre-checks whether the timer was active. # This is only reached from the keyboard timer toggle keys when a timer # is being activated. # LBL 13 # here on starting timer running ISG 00 # one more active timer X<> X # NO-OP (placeholder) SF 00 # we know there's at least one active, so turn it on RDN # discard zero X<>Y # get current time into X, timer no in Y STO IND Y # store time in current timer running register " START" # append " START" string to timer number in display AVIEW # display FS? 03 # pause if required PSE RTN # back to main (return to LBL 11 routine) # ------------------------------------------------------------------------------ # LBL "a" -- Accumulated Time Display # # Display accumulated times for all timers # LBL "a" 11.02 # set up loop value in accum timers range 11-20 LBL 20 # loop RCL IND X # X=time, Y=register X=0? # if clear GTO 13 # then skip body of loop X<>Y # swap, X=register, Y=time 10, - # convert register number to timer number... XEQ 80 # ...for use with display routine LBL 80 10, + # convert back to register number X<>Y # bring accum time back to X, timerno in Y HMS # convert to H.MS for display XEQ 82 # display routine LBL 13 # set up for loop RDN # discard either zero or time, and leave # the register index in X ISG X # next register GTO 20 GTO 99 # back to main # ------------------------------------------------------------------------------ # LBL "b" -- display list of active timers # LBL "b" CF 10 # temporary flag set if any active timers 1.01 # index counter FIX 0 # set for integer display CF 29 # with no decimal point CLA # clear alpha LBL 30 # loop RCL IND X # get current timer value X=0? # if clear GTO 13 # then skip loop body SF 10 # flag that we have at least one ARCL Y # recall timer number " " # append a space LBL 13 RDN # discard timer value or zero ISG X # loop if more to do GTO 30 # loop exit FS? 10 # if we had at least one active timer AVIEW # then display the list FS?C 10 # and pause (ignores flag 03 for now) PSE # which is OK because it's only one display GTO 99 # back to main # ------------------------------------------------------------------------------ # LBL "c" -- Close All Active Timers and Accumulate # # - makes use of same LBL-19 routine for toggle # - The keyboard LBL "c" moved to top to be faster # - turned into a subroutine to be called when F01 set, so all active can be # closed first # - timestamp from top of routine is left in X on exit, so that the exact # same time may be used to toggle on the next timer LBL 18 TIME # get current time to use for all immediately CF 10 # temp flag 1.01 # set up index LBL 14 RCL IND X # get timer value X!=0? # if timer is active SF 10 # then set flag RDN # discard value, index back to X FS?C 10 # if timer was active XEQ 19 # then toggle off and accumulate ISG X # next timer GTO 14 # loop back RDN # leave timestamp in X when called to close on f01 first RTN # ------------------------------------------------------------------------------ # LBL "d" -- Display all active timers # LBL "d" TIME # (moved from body of loop in v1.4) 1.01 # set index LBL 40 RCL IND X # get timer value X=0? # if not active GTO 13 # skip loop body X<>Y # timer number (index) into X XEQ 80 # set up to display timer number X<>Y # timer value in X RCL Z # current time XEQ 81 # display difference LBL 13 RDN # discard difference ISG X # next timer GTO 40 # loop back GTO 99 # back to main on finish # ------------------------------------------------------------------------------ # LBL "e" -- Clear All Registers! (both current and accumulated) # LBL "e" .02, ENTER # set index up for loop in Y CLX # value to store LBL 00 STO IND Y # clear active timer ISG Y # next register GTO 00 # loop GTO 99 # back to main on finish # ------------------------------------------------------------------------------ # AUXILIARY ROUTINES, LBL 80, LBL 81, LBL 82 # ------------------------------------------------------------------------------ # # LBL 80 -- start a timer display with timerno (timer number) # # LBL 80 FIX 0 # integer display CF 29 # with no decimal point "T" # clears alpha and inserts "T" 10 # X>Y? # Test if less than 10 "0" # pad with leading zero if so RDN # discard 10 ARCL X SF 29 " " # three spaces appended RTN # ------------------------------------------------------------------------------ # Finish Time Display, two entry points, LBL 81, LBL 82 # # LBL 81 -- used when difference needed, enter with Y=timerval, X=current # exit with Z->Y and difference in X # LBL 82 -- used with just time to display in X, exit with X and Y # preserved # # Both routines preserve contents of X, Y on exit # Although these routines don't use it, the index register is normally # preserved in Y on exit back to calling routines. # LBL 81 # entry point if difference is needed X<>Y # swap times, Y=current, X=timerval HMS- # time difference # fix for midnight rollover X>0? # if greater GTO 82 # then skip # else 24, HMS+ # add 24 hours if result was less than or equal zero # ------------- # Enter here with time to display in X LBL 82 # entry point to display time with no subtraction FS? 04 # if hour display flag set GTO 83 # then goto that routine instead FIX 4 # display seconds too RND # round to 4 decimal places ATIME # append to alpha in time format GTO 13 # skip to end LBL 83 # alternative hour display, not called directly, F04 set HR # ...entry from LBL 82 routine FIX 2 RND # round to 2 decimal places ARCL X "HR" # append "HR" string LBL 13 # finish up and exit RDN # discard rounded time, full precision original back in X LASTX # restore full precision time to exit with (in X) AVIEW # display FS? 03 # if pause flag PSE # then pause END # also return with Y intact
Go back to the HP-41 software library
Go back to the general software library
Go
back to the main exhibit hall