The Museum of HP Calculators


Time Manager for HP-41C Series

By bill duncan. Created: 2000.05.21

User Notes

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.)

User Keys

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.

User Flags

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.

Some Design Notes

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.

A Bug (or a Feature?)

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.

Ideas for Future Features

Some of the future features I'd like to add as separate modules if there is any interest are:

  1. 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.
  2. 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.
  3. Hooks for program driven time monitoring, perhaps for data logging applications?

Fine Print

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. Duncan

This 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.

Request

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.

Program Description

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!

Program Listing

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