About the Datalink
What is it?
Download Protocol
Display Segments
Memory Map
150 vs 150s
EEProms

Wristapp Programming
Reference
Creating Wristapps
Wristapp Format
The State Table
Wristapp Routines
Wristapps

Wristapp Programming
Tutorials
1 - Hello World
2 - Getting Input
3 - Better Input
4 - Showing Selection
5 - PassWord
6 - Day Find
7 - Playing with Sound
8 - Using Callbacks
9 - Hex Dump
10 - EEPROM Dumper
[This Page] 11 - Spend Watch
12 - Sound Schemes
13 - Random Numbers
14 - Hourly Chimes
15 - Lottery Picker

Sound Schemes
Sound Hardware
Sound Scheme Format

Home Send Mail

Tracking Money - Spend Watch example

David Andrews [david@polarnet.com] gets the credit for the inspration on this example.  Of course it turned out to be a bit harder than I expected to write it - mostly due to the fact that I wanted it to be a full blown wristapp with lots of features yet still fit on the watch.  This one also takes advantage of the 'parent' app which allows setting information in the applet without recompiling it.

What was the hardest about this application is making the user interface work and still be intuitive.  Once I got past that, coding was just an excercise left to the reader.

There are a lot of tricks in this code to make it fit.  I created a lot of subroutines and learned some interesting tricks to reduce code size.  It currently sits at 713 bytes and I know how I can get 2 more bytes out of it, but I can't find much more fluff in the code to cut out.  If you can find ways to make it smaller, I would be more than happy to hear about them...

You can download the wristapp and set program here

;Name: spend watch
;Version: spend0
;Description: spend watch - by John A. Toebes, VIII
;This keeps track of how much is in one of 7 categories
;
; Press the NEXT/PREV buttons to advance/backup through the categories
; Press the SET button to add/subtract/set/clear the amounts in the categories
; If you press the set button while the action is blinking, it will be carried out, otherwise
; you can cancel the operation.
;
;TIP:  Download your watch faster:  Download a WristApp once, then do not send it again.  It stays in the watch!
;HelpFile: watchapp.hlp
;HelpTopic: 106
;Parent: SpendSet
;****************************************************************************************
;* Copyright (C) 1997 John A. Toebes, VIII                                              *
;* All Rights Reserved                                                                  *
;* This program may not be distributed in any form without the permission of the author *
;*         jtoebes@geocities.com                                                        *
;****************************************************************************************
;
; History:
;    31 July 96 - Corrected problem with totals not being recalculated when you reenter
;                 the wristapp.
;
            INCLUDE "WRISTAPP.I"
;
; (1) Program specific constants
;
; We use a few extra bytes here in low memory.  Since we can't possibly
; be running while the COMM app is running, we have no chance of
; conflicting with it's use of this memory.
;
BLINK_BUF       EQU     $5C     ; 3 Byte Buffer for the blink routine
;               EQU     $5D
;               EQU     $5E
CAT_SAVE        EQU     $5F     ; Temporary counter variable
COUNTER         EQU     $60     ; Temporary variable to hold the 
FLAGBYTE        EQU     $61
;   Bit 0 indicates that the display does not need to be cleared
;   The other bits are not used

CURRENT_MODE    EQU     $62     ; The current mode that we are in
MODE_SELECT     EQU     0       ; Set mode, selecting which category to modify
MODE_HUNDREDS   EQU     1       ; Set mode, changing the hundreds of dollars digits
MODE_DOLLARS    EQU     2       ; Set mode, changing the dollars digits
MODE_CENTS      EQU     3       ; Set mode, changing the cents
MODE_ACTION     EQU     4       ; Set mode, changing the action
MODE_VIEW       EQU     5       ; Normal display mode

CATEGORY        EQU     $63     ; Current category 
;
; These three bytes need to be contiguous.  The represent the current
; value that is being operated on
;
HUNDREDS        EQU     $64
DOLLARS         EQU     $65
CENTS           EQU     $66

ACTION          EQU     $67     ; Selector for the current action
ACT_ADD         EQU     0
ACT_SUB         EQU     1
ACT_SET         EQU     2
ACT_CLEAR       EQU     3
AMT_BASE        EQU     $F0
;
;
; (2) System entry point vectors
;
START   EQU     *
L0110:  jmp     MAIN    ; The main entry point - WRIST_MAIN
L0113:  rts             ; Called when we are suspended for any reason - WRIST_SUSPEND
        nop
        nop
L0116:  jmp     DO_UPD  ; Called to handle any timers or time events - WRIST_DOTIC
L0119:  rts             ; Called when the COMM app starts and we have timers pending - WRIST_INCOMM
        nop
        nop
L011c:  rts             ; Called when the COMM app loads new data - WRIST_NEWDATA
        nop
        nop

L011f:  lda     STATETAB0,X ; The state table get routine - WRIST_GETSTATE
        rts

L0123:  jmp     HANDLE_STATE
        db      STATETAB0-STATETAB0
L0127:  jmp     HANDLE_STATE
        db      STATETAB1-STATETAB0
;
; (3) Program strings
;
; These strings represent the 4 possible actions.  They need to be early on in the data segment so that
; then can be pointed to by using 8-bit offset addressing.  They are exactly 3 bytes long and are
; displayed by using the BLINK_TZONE routine
;
S3_MODE:
S3_ADD          timex   "ADD"
S3_SUB          timex   "SUB"
S3_SET          timex   "SET"
S3_CLR          timex   "CLR"
;
; These are the categories that the end user has configured.  They are set by using the SPENDSET program
; which searches for the first string "TOTAL   ".  These strings must be exactly 8 bytes each in order with
; total being the first one.
;
S8_TOTAL:       timex   "TOTAL   "
S8_CAT1:        timex   "CAT1    "
S8_CAT2:        timex   "CAT2    "
S8_CAT3:        timex   "CAT3    "
S8_CAT4:        timex   "CAT4    "
S8_CAT5:        timex   "CAT5    "
S8_CAT6:        timex   "CAT6    "
S8_CAT7:        timex   "CAT7    "
;
; These are the running amounts for each category.  Note that you can actually
; initialize them with some default and the code will run properly
;
AMT_TOTAL:      db      0,0,0
AMT_CAT1:       db      0,0,0
AMT_CAT2:       db      0,0,0
AMT_CAT3:       db      0,0,0
AMT_CAT4:       db      0,0,0
AMT_CAT5:       db      0,0,0
AMT_CAT6:       db      0,0,0
AMT_CAT7:       db      0,0,0
;
; These strings prompt for the current mode that we are in.  They are displayed on the top line of
; the display.
;
S6_SELECT       timex6  "SELECT"
S6_AMOUNT       timex6  "AMOUNT"
S6_ACTION       timex6  "ACTION"
S6_SPEND:       timex6  "SPEND"         ; save a byte by leaching off the space on the start of the next string
S6_WATCH:       timex6  " WATCH"
;
; This table selects which string is to be displayed.  It is directly indexed by the current mode
;
MSG_TAB         db      S6_SELECT-START ; 0 - MODE_SELECT  
                db      S6_AMOUNT-START ; 1 - MODE_HUNDREDS
                db      S6_AMOUNT-START ; 2 - MODE_DOLLARS 
                db      S6_AMOUNT-START ; 3 - MODE_CENTS   
                db      S6_ACTION-START ; 4 - MODE_ACTION  
                db      S6_SPEND-START  ; 5 - MODE_VIEW
;
; This is one of the magic tricks for providing the source for the blink routine.
; These are base pointers (offset from HUNDREDS) that we use to copy three bytes into
; BLINK_BUF.  The interesting one here is the MODE_CENTS entry which points to DATDIGIT1
; This works because the last number that we format happens to be the cents amount,
; and the blink routine expects the two characters instead of the actual value.
;
DATASRC         db      HUNDREDS-HUNDREDS       ; 1 - MODE_HUNDREDS
                db      DOLLARS-HUNDREDS        ; 2 - MODE_DOLLARS 
                db      DATDIGIT1-HUNDREDS      ; 3 - MODE_CENTS   
                db      S3_ADD-HUNDREDS         ; 4 - MODE_ACTION  0 - ACT_ADD
                db      S3_SUB-HUNDREDS         ; 4 - MODE_ACTION  1 - ACT_SUB
                db      S3_SET-HUNDREDS         ; 4 - MODE_ACTION  2 - ACT_SET
                db      S3_CLR-HUNDREDS         ; 4 - MODE_ACTION  3 - ACT_CLR
;
; This is the parameter to select which blink routine we want to use
;
BLINK_PARM      db      BLINK_MID12     ; 1 - MODE_HUNDREDS
                db      BLINK_MID34     ; 2 - MODE_DOLLARS
                db      BLINK_SECONDS   ; 3 - MODE_CENTS
                db      BLINK_TZONE     ; 4 - MODE_ACTION
;
; (4) State Tables
;
; This set of state tables is a little special since we actually use the
; same state processing routine for both states.  This saves us a lot of
; memory but still allows us to let the state table make it easy to exit
; the app with the MODE button
;
STATETAB0:
        db      0
        db      EVT_ENTER,TIM2_12TIC,0          ; Initial state
        db      EVT_RESUME,TIM_ONCE,0           ; Resume from a nested app
        db      EVT_TIMER2,TIM_ONCE,0           ; This is the timer
        db      EVT_MODE,TIM_ONCE,$FF           ; Mode button
        db      EVT_SET,TIM_ONCE,1              ; Set button
        db      EVT_DNANY4,TIM_ONCE,0           ; NEXT, PREV, SET, MODE button pressed
        db      EVT_END
        
STATETAB1:
        db      1
        db      EVT_RESUME,TIM_ONCE,1           ; Resume from a nested app
        db      EVT_DNANY4,TIM_ONCE,1           ; NEXT, PREV, SET, MODE button pressed
        db      EVT_UPANY4,TIM_ONCE,1           ; NEXT, PREV, SET, MODE button released
        db      EVT_USER2,TIM_ONCE,0            ; Return to state 0
        db      EVT_END                         ; End of table
;
; (5) Put up the initial banner screen
;
HANDLE_ENTER
        clra
        sta     CATEGORY                        ; We start out displaying the totals
        jsr     FETCH_CATEGORY
        jsr     CLEARALL                        ; Clear the display
        lda     #S6_SPEND-START                 ; Put 'SPEND ' on the top line
        jsr     PUT6TOP
        lda     #S6_WATCH-START                 ; Put ' WATCH' on the second line
        jsr     PUT6MID
        clr     FLAGBYTE                        ; Force us to clear the display
        lda     #MODE_VIEW                      ; Start out in the VIEW mode
        sta     CURRENT_MODE
        lda     #SYS8_MODE                      ; Put MODE on the bottom line
        jmp     PUTMSGBOT
;
; (6) This is the main screen update routine.
;---------------------------------------------------------------
; Routine:
;   SHOWCURRENT
; Parameters:
;   HUNDREDS,DOLLARS,CENTS - Current value to be displayed
;   0,FLAGBYTE - Screen state (CLR=Must clear it first)
;   CATEGORY - the current category to be displayed
; Returns:
;   DATDIGIT1,DATDIGIT2 - 2 digit characters for the cents value
; Purpose:
;   This routine shows the current selected category and value for the category
;---------------------------------------------------------------
SHOWCURRENT
        brset   0,FLAGBYTE,NOCLEAR              ; If we don't need to clear the display, skip it
        jsr     CLEARALL                        ; Clear the display
        bset    0,FLAGBYTE                      ; And remember that we did it
NOCLEAR
        lda     #ROW_MP45                       ; Turn on the decimal point
        sta     DISP_ROW
        bset    COL_MP45,DISP_COL
        ldx     HUNDREDS                        ; Get the Hundreds
        jsr     FMTBLANK0                       ;   Format it
        jsr     PUTMID12                        ;   and display it
        ;
        ; We want to output the dollars, but if there were no hundreds, we want to let the
        ; first digit be a blank.  To do this, we simply let it be a blank and set it to a zero
        ; if there was actually anything in the hundreds field
        ;
        ldx     DOLLARS                         ; Get the Dollars
        jsr     FMTX                            ;   Format it
        tst     HUNDREDS                        ; Do we need to have a leading zero?
        beq     NOBLANKIT                       ; No, so it is fine
        ldx     DOLLARS                         ; Yes, Get the Dollars again
        jsr     FMTXLEAD0                       ;   And format it with a leading zero
NOBLANKIT
        jsr     PUTMID34                        ;   Display the Dollars
        ldx     CENTS                           ; Get the Cents
        jsr     FMTXLEAD0                       ;   Format it (and leave it around for later)
        jsr     PUTMID56                        ;   and display it.
        lda     CATEGORY                        ; Get which category we want
        lsla                                    ;  *2
        lsla                                    ;  *4
        lsla                                    ;  *8
        add     #S8_TOTAL-START                 ; *8+the start of the string
        jmp     BANNER8                         ;  and display the right string
;
; (7) State Table 0 and 1 Handler
; This is called to process the state events.
; We see SET, RESUME, DNANY4, and UPANY4 events
;
HANDLE_STATE:
        bset    1,APP_FLAGS	                ; Indicate that we can be suspended
        lda     BTNSTATE                        ; Get the event
        cmp     #EVT_ENTER                      ; Is this the initial state?
        beq     HANDLE_ENTER
        cmp     #EVT_DNANY4                     ; How about a button pressed?
        beq     HANDLE_DNANY
        bclr    1,BTNFLAGS                      ; Turn off the repeat counter
        cmp     #EVT_SET                        ; Did they press the set button
        bne     SKIP2                           ; No
        clr     CURRENT_MODE                    ; Yes, Go to MODE_SELECT
SKIP2   bra     GOREFRESH
;
; (8) They pressed a button, so handle it
;
HANDLE_DNANY
        lda     BTN_PRESSED                     ; Let's see what the button they pressed was
        beq     DO_NEXT                         ; MODE=1, and NEXT=0, so if it is less, it must be the next button
        cmp     #EVT_SET                        ; MODE=1 SET=2 PREV=3, test all at once
        blo     DO_MODE                         ; <2 = 1 so we have a EVT_MODE
        bhi     DO_PREV                         ; >2 = 3 so we have a EVT_PREV
        ;
        ; They pressed the set button, so we want to carry out the operation IF they have
        ; one currently selected.
        ;
DO_SETOUT
        lda     CURRENT_MODE                    ; See what mode we were in
        cmp     #MODE_ACTION                    ; Is it the ACTION mode?
        bne     NO_ACTION                       ; No, so just cancel the operation
        jsr     DO_OPERATION                    ; Do what they requested
        jsr     DO_TOTAL                        ; And total up everything
        jsr     PLAYCONF                        ; Plus tell them that we did it
NO_ACTION
        bclr    0,FLAGBYTE                      ; We need to clear the display
        lda     #MODE_VIEW                      ; And switch back to VIEW mode
        sta     CURRENT_MODE
        lda     #EVT_USER2                      ; And go back to state 0
        jmp     POSTEVENT
;
; (9) This handles the update routine to change a digit...
;
DO_NEXT
        bset    0,SYSFLAGS                      ; Mark our update direction as up
        BRSKIP2                                 ; and skip over the next instruction
DO_PREV
        bclr    0,SYSFLAGS                      ; Mark our update direction as down
DO_UPD
        lda     CURRENT_MODE                    ; Which mode are we in?
        beq     CHANGE_CATEGORY                 ; 0=MODE_SELECT, so change the category
        cmp     #MODE_VIEW                      ; 5=MODE_VIEW, so we also change the category
        bne     TRYOTHERS
CHANGE_CATEGORY
; (10) updating the category
        ldx     #CATEGORY                       ; Point to the category variable
        lda     #7                              ; get our range of values
        bsr     ADJUST_PX_ANDA                  ; And let the routine do the adjust for us
        jsr     FETCH_CATEGORY                  ; Update the current amount from the new category
GOREFRESH
        bra     REFRESH
;
; (11) ADJUST_PX_ANDA - a routine to adjust a value based on the direction
;---------------------------------------------------------------
; Routine:
;   ADJUST_PX_ANDA
; Parameters:
;   A - Binary range to limit value within ((2**x)-1)
;   0,SYSFLAGS - Direction to adjust, SET=UP
;   X - Pointer to value to be adjusted
; Returns:
;   Value pointed to by X is adjusted
; Purpose:
;   This routine adjusts a value up or down based on the current direction, wrapping 
;   it to the binary range indicated by the value in A.  Note that this value must
;   be a power of 2-1 (e.g. 1, 3, 7, 15, 31, 63, or 127)
;---------------------------------------------------------------
ADJUST_PX_ANDA
        inc     ,X
        brset   0,SYSFLAGS,NODEC
        dec     ,X
        dec     ,X
NODEC   and     ,X
        sta     ,X
        rts
;
; (12) Try updating one of the other modes
; We have already handled MODE_SELECT and MODE_VIEW.  This code handles
; MODE_HUNDREDS, MODE_DOLLARS, MODE_CENTS, and MODE_ACTION
;
TRYOTHERS
        cmp     #MODE_CENTS                     ; 3=MODE_CENTS
        bls     TRYMORE                         ; If it is <=, then we leave only MODE_ACTION
; (13) updating the Action
        lda     CATEGORY                        ; Which category is it?
        beq     REFRESH                         ; If we are displaying the total, you can't change the action
        ldx     #ACTION                         ; Point to the current action
        lda     #3                              ; and the range of actions
        bsr     ADJUST_PX_ANDA                  ; and let our simple routine handle it for us
        bra     REFRESH
TRYMORE
        beq     DOCENTS                         ; If it is MODE_CENTS, go handle it
; (14) Update MODE_HUNDREDS=1 and MODE_DOLLARS=2
        clrx                                    ; Set the lower limit =0
        stx     UPDATE_MIN
        ldx     #99                             ; And the upper limit= 99
        stx     UPDATE_MAX
        add     #HUNDREDS-1                     ; Point to the right byte to update
        tax                                     ; And put it in X as the parameter
        lda     CURRENT_MODE                    ; MODE=1       MODE=2
        deca                                    ;    0           1
        lsla                                    ;    0           2
        add     #UPD_MID12                      ;  5=UPD_MID12 7=UPD_MID34
        jsr     START_UPDATEP                   ; And prepare the update routine   
        bset    4,BTNFLAGS                      ; Mark that the update is now pending
        rts
;
; (15) This is where we switch which digit we are changing...
;
DO_MODE
        lda     CURRENT_MODE                    ; Get the mode
        ldx     #MODE_ACTION                    ; Limit it to the first 5 modes
        jsr     INCA_WRAPX                      ; And let the system increment it for us
        sta     CURRENT_MODE                    ; Save it back
        ; When we switch to the ACTION mode and we have the Totals category showing,
        ; we need to limit them to the single action of CLEAR
        ;
        cmp     #MODE_ACTION                    ; Did we go to action mode?
        bne     REFRESH                         ; No, nothing to do
        clr     ACTION                          ; Reset the action to be add
        tst     CATEGORY                        ; Are we displaying the totals
        bne     REFRESH                         ; No, nothing more to do
        lda     #ACT_CLEAR                      ; Yes, switch them to CLEAR
        sta     ACTION
;
; (16) Refresh the screen and start blinking the current digit...
;
REFRESH
        ; 0 - SELECT   <Category>
        ; 1 - AMOUNT    (Blink hundreds)
        ; 2 - AMOUNT    (Blink dollars)
        ; 3 - AMOUNT    (Blink cents)
        ; 4 - ACTION    
        jsr     SHOWCURRENT                     ; Format the screen
        ldx     CURRENT_MODE                    ; Get the mode
        lda     MSG_TAB,X                       ; So that we can get the message for it
        jsr     PUT6TOP                         ; And put that on the top of the display
        ;
        ; Now we need to make the right thing blink
        ;
        ldx     CURRENT_MODE                    ; Are we in Select mode?
        beq     NOBLINK2                        ; Yes, don't blink anything
        cpx     #MODE_ACTION                    ; How about ACTION MODE?
        bhi     NOBLINK2                        ; >ACTION is VIEW mode, so if so, don't blink either
        ; 1 -> BLINK_MID12    PARM=&HUNDREDS
        ; 2 -> BLINK_MID34    PARM=&DOLLARS
        ; 3 -> BLINK_SECONDS  PARM=&2Characters
        ; 4 -> BLINK_TZONE    PARM=&3Characters
        brset   1,BTNFLAGS,NOBLINK2             ; Also, we don't want to be blinking if we are in an update routine
        bne     SETUP_BLINK                     ; If we were not in action mode, we have the right data source
        ; Put a > on the display
        ldx     #C_RIGHTARR                     ; Put a > sign right in front of the action
        lda     #POSL3_5
        jsr     PUTLINE3
        lda     CURRENT_MODE                    ; Get the mode
        add     ACTION                          ; And add in the action
        tax                                     ; To compute our data source pointer
SETUP_BLINK
;
; (17) Set up the parameters for and call the blink routine
;
        ldx     DATASRC-1,X                     ; Get the offsetted pointer to the right data
        lda     HUNDREDS,X                      ; And copy the 3 bytes to our blink buffer
        sta     BLINK_BUF
        lda     HUNDREDS+1,X
        sta     BLINK_BUF+1
        lda     HUNDREDS+2,X
        sta     BLINK_BUF+2
        ldx     CURRENT_MODE                    ; Get our mode again
        lda     BLINK_PARM-1,X                  ; and use it to pick up which parameter we are passing
        ldx     #BLINK_BUF                      ; Point to the common blink buffer
        jsr     START_BLINKP                    ; And do it
        bset    2,BTNFLAGS                      ; Mark a blink routine as pending
NOBLINK2
        rts
;
; (18) Update MODE_CENTS
; This is a special case since we don't have a system routine that allows updating
; the right most digits on the middle line.  Fortunately we can fake it by turning
; on the tic timer and waiting until 8 tics have passed before going into a repeat
; loop.  The code has been carefully constructed so that the tic timer can just go
; straight to the DO_UPD code to work.
DOCENTS
        ldx     #COUNTER                        ; Point to the counter (saves code size)
        brset   1,BTNFLAGS,NOSTART              ; Are we already in an update loop?
        lda     #8                              ; No, we need to wait 8 tics
        sta     ,X      ; X->COUNTER            ; Save the value
        BSET    1,BTNFLAGS                      ; and start the timer
        bra     DOIT                            ; But still do it once right now
;
DEC_DELAY                                       
        dec     ,X      ; X->COUNTER            ; We haven't hit the limit, decrement it and try again
        rts
NOSTART
        tst     ,X      ; X->COUNTER            ; We are in the loop, have we hit the limit?
        bne     DEC_DELAY                       ; no, go off and delay once more
DOIT
        lda     #99                             ; Our upper limit is 99
        ldx     #CENTS                          ; Point to the cents variable (saves code size)
        brset   0,SYSFLAGS,UPCENTS              ; Are we in an up mode?
        dec     ,X      ; X->CENTS              ; Down, decrement the value
        bpl     REFRESH                         ; If we didn't wrap, just go display it
        sta     ,X      ; X->CENTS              ; We wrapped, save the upper limit
        bra     REFRESH                         ; and go display it
UPCENTS
        inc     ,X      ; X->CENTS              ; Up, increment the value
        cmp     ,X      ; X->CENTS              ; Did we hit the limit?
        bpl     REFRESH                         ; No, go display it
        clr     ,X      ; X->CENTS              ; Yes, wrap to the bottom
        bra     REFRESH                         ; and display it
;
; (19) DO_OPERATION - Perform the requested operation
;---------------------------------------------------------------
; Routine:
;   DO_OPERATION
; Parameters:
;    HUNDREDS,DOLLARS,CENTS - Amount to be added/subtracted/set
;    CATEGORY - Item to be updated
;    ACTION - 0 = ACT_ADD
;             1 = ACT_SUB
;             2 = ACT_SET
;             3 = ACT_CLEAR
; Purpose:
;   Adjusts the corresponding category by the given amount
;---------------------------------------------------------------
DO_OPERATION
        lda     CATEGORY                        ; Get our category
        bsr     COMPUTE_BASE                    ; And point to the data for it
        lda     ACTION                          ; Which action is it?
        beq     DO_ADD                          ; 0=ADD, go do it
        cmp     #ACT_SET                        ; 3 way compare here... (code trick)
        beq     DO_SET                          ; 2=SET, go do it
        blo     DO_SUB                          ; <2=1 (SUB), go do it
DO_CLR                                          ; >2 = 3 (CLEAR)
        clr     HUNDREDS                        ; Clear out the current values
        clr     DOLLARS
        clr     CENTS
        tst     CATEGORY                        ; Were we clearing the total?
        bne     DO_SET                          ; No, just handle it
        ;
        ; They want to clear everything
        ;
        ldx     #(3*8)-1                        ; Total number of categories
CLEAR_TOTALS
; Mini Routine here X=number of bytes to clear
        clra
CLR_MORE
        sta     AMT_TOTAL,X                     ; Clear out the next byte
        decx                                    ; Decrement the number to do
        bpl     CLR_MORE                        ; And go for more
        rts
;
; (20) Handle Subtracting a value
;
DO_SUB
        neg     HUNDREDS                        ; Just negate the value to be added
        neg     DOLLARS
        neg     CENTS                           ; And fall into the add code
;
; (21) Handle Adding a value
;
DO_ADD
        lda     CENTS                           ; Add the cents
        add     AMT_BASE+2,X
        sta     CENTS
        lda     DOLLARS                         ; Add the dollars
        add     AMT_BASE+1,X
        sta     DOLLARS
        lda     HUNDREDS                        ; Add the hundreds
        add     AMT_BASE,X
        sta     HUNDREDS
        ldx     #CENTS                          ; Point to the cents as it will be the first one we fix up
        tst     ACTION                          ; See what type of operation we just did
        beq     FIXUP_ADD                       ; Was it an ADD? If so, do do it
        bsr     TRYDEC                          ; Decrement, fix up the Cents
        bsr     TRYDEC                          ; Then fix up the dollars
        lda     HUNDREDS                        ; Did the hundreds underflow as a result?
        bmi     DO_CLR                          ; Yes, so just set everything to zero
        bra     DO_SET                          ; No, so copy over the values to the current entry
TRYDEC
        lda     ,X                              ; Get the current byte to check
        bpl     RETDEC                          ; If it didn't underflow, then skip to the next byte
        add     #100                            ; Add back the 100 that it underflowed
        sta     ,X                              ; And save that away
        decx                                    ; Back up to the next most significant byte
        dec     ,X                              ; and borrow the one
        rts
RETDEC  decx                                    ; No need to do anything, so skip to the next byte
        rts
TRYADD
        lda     ,X                              ; Get the current byte to check
        sub     #100                            ; See if it was less than 100
        bmi     RETDEC                          ; If so, then it was already normalized so skip out
        sta     ,X                              ; It was an overflow, so save the fixed value
        decx                                    ; Skip to the next byte
        inc     ,X                              ; And add in the overflow
        rts

FIXUP_ADD
        bsr     TRYADD                          ; Fix up the cents
        bsr     TRYADD                          ; and then fix up the dollars
;
; (22) Handle setting a value
;
DO_SET
        bsr     COMPUTE_CATEGORY_BASE           ; Point to the data for our category
        lda     HUNDREDS                        ; Copy over the values to the current category
        sta     AMT_BASE,X
        lda     DOLLARS
        sta     AMT_BASE+1,X
        lda     CENTS
        sta     AMT_BASE+2,X
        rts
;
; (23) COMPUTE_BASE - Computes an offset pointer to get to the total amounts
; This is a trick to save us a few bytes in the instructions.
;---------------------------------------------------------------
; Routine:
;   COMPUTE_BASE
; Parameters:
;   A - Offset into total
; Returns:
;   X - Pointer relative to AMT_BASE to use
; Purpose:
;   Computes an offset pointer to get to the total amounts
;---------------------------------------------------------------
COMPUTE_CATEGORY_BASE
        lda     CATEGORY                        ; Get our category
COMPUTE_BASE
        ldx     #3
        mul
        add     #AMT_TOTAL-AMT_BASE
        tax
        rts
;
; (24) This is the main initialization routine which is called when we first get the app into memory
;
MAIN:
        lda     #$c0                            ; We want button beeps and to indicate that we have been loaded
        sta     WRISTAPP_FLAGS
        ; Fall into DO_TOTAL
;
; (25) DO_TOTAL - Recomputes the current total
;---------------------------------------------------------------
; Routine:
;   DO_TOTAL
; Parameters:
;   NONE
; Purpose:
;   Recomputes the current total
;---------------------------------------------------------------
DO_TOTAL
        lda     CATEGORY                        ; Remember our category
        sta     CAT_SAVE
        clr     ACTION                          ; Say that we want to add 0=ACT_ADD
        clr     CATEGORY                        ; To the total category
        ldx     #2                              ; But we need to clear it first
        bsr     CLEAR_TOTALS
        lda     #7                              ; And iterate over the 7 categories
        sta     COUNTER
TOT_LOOP
        lda     COUNTER                         ; Get our current category
        bsr     FETCH_CATEGORY                  ; And fetch the data
        jsr     DO_OPERATION                    ; Then add it to the total
        dec     COUNTER                         ; Go to the next category
        bne     TOT_LOOP                        ; Until we are done
        lda     CAT_SAVE                        ; Restore the category
        sta     CATEGORY
        ; fall into FETCH_CATEGORY
; (26) FETCH_CATEGORY - Retrieves the value of the total amount for the selected category
;---------------------------------------------------------------
; Routine:
;   FETCH_CATEGORY
; Parameters:
;   A - Category to be fetched
; Returns:
;   HUNDREDS,DOLLARS,CENTS - Current value of selected category
; Purpose:
;   Retrieves the value of the total amount for the selected category
;---------------------------------------------------------------
FETCH_CATEGORY
        bsr     COMPUTE_BASE                    ; Get the pointer to the base
        lda     AMT_BASE,X                      ; And retrieve the data
        sta     HUNDREDS
        lda     AMT_BASE+1,X
        sta     DOLLARS
        lda     AMT_BASE+2,X
        sta     CENTS
        rts
;--------------------END OF CODE---------------------------------------------------

This is a pretty significant program and the sections are ordered to make the branches all work out. Here's a quick lookaround at the sections.

  1. Program specific constants - It is worth noting that in this case, I actually intruded on the space which one might consider reserved for the system applications. However, the only one that uses any of this memory is the Comm app and there is no chance that we need to be running while it is. We are forced in several instances to use this lower memory because the system roms need a pointer passed in X. Since our code loads into 0110 and beyond, we have to use lower memory if we want to actuall point to something.
  2. System entry point vectors - Nothing really special here. However, we do have a timer routine that we enable when we are inputting cents. What is nice in this case is that the code is constructed so that it jumps right into the processing loop to act as if a timer event had occured with the normal state processing.
  3. Program strings - We have quite a few strings that we have created. We also take advantage of table of pointers to save us code space.
  4. State Tables - This is a pretty unusual program in that even though we have two state tables, they both point to the same state table processing routine. This allows me to let the system handle knowing when we are in set mode to allow for the mode button to advance us through states in the set mode and to take us out of the wristapp when we are not in set mode.
  5. Initial Banner Screen - No real surprises here.
  6. This is the main screen update routine.
  7. State Table 0 and 1 Handler
  8. They pressed a button, so handle it
  9. This handles the update routine to change a digit...
  10. updating the category
  11. ADJUST_PX_ANDA - a routine to adjust a value based on the direction
  12. Try updating one of the other modes
  13. updating the Action
  14. Update MODE_HUNDREDS=1 and MODE_DOLLARS=2
  15. This is where we switch which digit we are changing...
  16. Refresh the screen and start blinking the current digit...
  17. Set up the parameters for and call the blink routine
  18. Update MODE_CENTS
  19. DO_OPERATION - Perform the requested operation
  20. Handle Subtracting a value
  21. Handle Adding a value
  22. Handle setting a value
  23. COMPUTE_BASE - Computes an offset pointer to get to the total amounts
  24. This is the main initialization routine which is called when we first get the app into memory
  25. DO_TOTAL - Recomputes the current total
  26. FETCH_CATEGORY - Retrieves the value of the total amount for the selected category
  27. Handle the underflows when adding dollars and cents
1