;Name: Numpad/Chrono ;Version: NUMPAD ;Description: A multi-function WristApp, which includes three 12 digit Number Pads, a Chronometer, a Countdown Timer, and an Alarm Enhancer ;Version 1.4 ; ;(c) 1998 Michael Polymenakos (mpoly@panix.com). Compiled using tools and knowledge published by John A. Toebes, VIII ; ;TIP: Read the NUMPAD.TXT file ; ;HelpFile: watchapp.hlp ;HelpTopic: 101 INCLUDE "WRISTAPP.I" ; ; NOTE: This code can not be compiled 'as-is'. You will need to remove one or more features ; to make the program fit in the 742 bytes of memory available. ; ; ;?C marks day-chime specific code ; ;?B marks correct-beeps specific code ; ;?W Weekly alarm enhancer code ; ;?A Line of code that needs to be removed for 'always beep on return' feature. ; ; Version Remove Lines Marked ; A ;?C ?A ; B ;?C ; C ;?B ; D ;?C ;?W ; ; ; Program Constants ; DASH equ $1E ; Dash in Timex6 charset ; ; Memory Addresses ; steal 5 bytes from the stack. CURSOR equ $C3 ; Points to the next byte to be updated by numpad COUNT_NOW equ $C4 ; Minutes left to countdown COUNT_LAST equ $C5 ; Last value of MINUTE encountered in timer routine COUNT_INIT equ $C6 ; User's selected value for countdown DOW equ $C7 ; Current Day-Of-Week ; We have bytes $60 thru $67 to use for memory (8 bytes) TICKER equ $26 ; System tick counter, incremented by 1 every 1/100 sec TEMP equ $60 ; Used sparingly HOURS equ $61 ; --+ MINUTES equ $62 ; | The chronometer time value. This matches what is SECONDS equ $63 ; | displayed on screen HUNDREDS equ $64 ; --+ LASTTICK equ $65 ; Last value of TICKER encountered, see PT_TICK routine ; Our timer status flags STATUS equ $66 ; Chronometer Status Flags, bits as follows: ST_BPRESSED equ 7 ; The user has pressed at least one button in Timer ST_SETCOUNT equ 6 ; We are in Set-Countdown mode ST_REDRAWING equ 5 ; We are in the redrawing routine ST_100S equ 4 ; 100s of seconds need to be redrawn (10's are always redrawn) ST_ALARM equ 3 ; Use alarm instead of beep on countdown ST_SPLIT equ 2 ; we are in split mode ST_REDRAW equ 1 ; The chronometer is the current program ST_RUNNING equ 0 ; time is running SV_BPRESSED equ 128 SV_SETCOUNT equ 64 SV_REDRAWING equ 32 SV_100S equ 16 SV_ALARM equ 8 SV_SPLIT equ 4 SV_REDRAW equ 2 SV_RUNNING equ 1 CURRENT_PROGRAM equ $67 ; 0 - 3, Subprogram currently active ; ; System entry point vectors ; START equ * L0110 jmp PM_MAIN ; The main entry point L0113 jmp PM_SLEEP ; Called when we are suspended for any reason L0116 jmp PT_TICK ; Called to handle any timers or time events L0119 rts ; Called when the COMM app starts and we have timers pending nop nop ;011C jmp PM_MAIN ; Called when the COMM app loads new data (Reset application) L011C bset 1,BTNFLAGS ; Request a TICK event every 1/10 sec. rts L011F lda STATETAB0,X ; The state table get routine rts L0123 jmp PM_HANDLE_STATE0 db STATETAB0-STATETAB0 ; ; State Table ; STATETAB0 db 0 db EVT_ENTER,TIM_ONCE,0 ; Initial state db EVT_RESUME,TIM_ONCE,0 ; Resume from a nested app db EVT_NEXT,TIM_ONCE,0 db EVT_PREV,TIM_ONCE,0 ; Various buttons pressed db EVT_DNSET,TIM_ONCE,0 db EVT_UPSET,TIM_ONCE,0 db EVT_MODE,TIM_ONCE,0 db EVT_GLOW,TIM_ONCE,0 db EVT_USER2,TIM_ONCE,$FF ; User Event : Exit app db EVT_END ; Various display Strings BANNERS S8_CHRONO timex "-CHRONO-" S8_NUMPAD timex "NUMPAD 1" ; Cursor Table - for turning "cursor" segments on CURSORS db ROW_T1I,ROW_T2I,ROW_T3I,ROW_T4I,ROW_T5I,ROW_T6I ; DIGITS is the main storage for the NUMPAD wristapps DIGITS equ DIGITS1-16 ; So that offsets are right DIGITS1 db DASH,DASH,DASH,DASH,DASH,DASH db DASH,DASH,DASH,DASH,DASH,DASH ; Note: there must be exactly 4 bytes filler between here and digits2 X_IN_Y db 24,60,60,100 ; 24 hours in a day, 60 minutes in an hour, etc... DIGITS2 db DASH,DASH,DASH,DASH,DASH,DASH db DASH,DASH,DASH,DASH,DASH,DASH ; Note: there must be exactly 4 bytes filler between here and digits3 db 0 ROLL_TABLE equ ROLL_TABLEX-$1D ROLL_TABLEX db 9,DASH,0 ; See PN_CHANGE routine DIGITS3 db DASH,DASH,DASH,DASH,DASH,DASH db DASH,DASH,DASH,DASH,DASH,DASH ; ; General/Setup routines ; PM_MAIN ; This routine gets called after app/data is uploaded lda #SV_SPLIT+SV_ALARM ; Defaults for chronometer sta STATUS ; Set default status lda #$C0 ; Store WristApp flags sta WRISTAPP_FLAGS ; Button Beeps = System, Loaded = Yes ;?B{ bclr 7,MODE_FLAGS brclr 4,MODE_FLAGS,PM_MAIN1 bset 7,MODE_FLAGS ;?B} PM_MAIN1 clr COUNT_INIT ; Clear Countdown jsr PT_RESET_TIMER ; Reset Chronometer time bset 1,BTNFLAGS ; Request a TICK event every 1/10 sec. PM_SLEEP ; Called when we get suspended bclr ST_BPRESSED,STATUS ; Clear the button-pressed indicator bset ST_SPLIT,STATUS ; Set split mode (Disables display of timer) clr CURRENT_PROGRAM ; Reset current program to 0 rts ; ; Application State table handler ; PM_HANDLE_STATE0 ; This routine gets called whenever an event is triggered jsr SNDSTOP ; Shudup! lda BTNSTATE ; Get the event cmp #EVT_MODE ; Is it the mode button? bne PM_HANDLE_APP ; No, handle program PM_HANDLE_NEXT_APP lda #EVT_ENTER inc CURRENT_PROGRAM ; Yes, increment program number brclr 2,CURRENT_PROGRAM,PM_HANDLE_APP ; Are we now in program number 4? ; for some inexplicable reason, when this application returns to TIME mode, it ; does not produce the distinctive sound that it is supposed to. this is a workaround ; for that. This is pretty good code to comment out if you need an extra 10 bytes ;?A{ brclr 4,MODE_FLAGS,PM_NO_RETURN_BEEP ; Should we make a 'return to time' beep? ;?A} lda #SND_RETURN sta SYSSOUND ; Play Sound jsr SNDSTART ; PM_NO_RETURN_BEEP bsr PM_SLEEP ; Get ready to say bye lda #EVT_USER2 ; Go back to original state jmp POSTEVENT PM_HANDLE_APP ldx CURRENT_PROGRAM ; Is this program 0? bne PN_HANDLE_NUMPAD ; Yes, go to timer jmp PT_HANDLE_TIMER ;########################### ;# NUMPAD APPLICATION CODE # ;########################### ; Application handler gets called on any event PN_HANDLE_NUMPAD ; Called if we are the current app, and there is an event pending bset ST_SPLIT,STATUS ; Set split mode (Disables display of timer) ; bset 1,APP_FLAGS ; Indicate that we can be suspended ; Disabled; we cannot be suspended. brset ST_BPRESSED,STATUS,PM_HANDLE_NEXT_APP ; If the user did something in timer, we want out... cmp #EVT_ENTER ; Is this our initial entry? beq PN_CURSORFIRST ; Yes, just move cursor to top/left position ldx CURSOR ; Next 2 routines need CURSOR in X cmp #EVT_DNSET ; SET Button - Move cursor right beq PN_CURSORRIGHT ; cmp #EVT_GLOW ; SET Button - Move cursor left beq PN_CURSORLEFT ; ldx DIGITS,X ; Next 3 routines need DIGIT,CURSOR in X cmp #EVT_PREV ; PREV decrements the value beq PN_CHANGE_DOWN ; ;cmp #EVT_NEXT ; NEXT increments the value tsta ; since #EVT_NEXT = 0 beq PN_CHANGE_UP ; bra PN_CHANGE_OK ; Catch-all ; Routines to handle digit changes ; Increment or decrement the digit ; X must have the current digit value in it. PN_CHANGE_UP incx ; Twice, because it will be dec'd later incx PN_CHANGE_DOWN decx bmi PN_CHANGE_DASH ; If it is $FF or 10, Change it to DASH cpx #10 bne PN_CHANGE_ROLL PN_CHANGE_DASH ldx #DASH PN_CHANGE_ROLL cpx #10 ; Handle transition from 9 <-> DASH <-> 0 blo PN_CHANGE_OK ; Does it need to be rolled? ldx ROLL_TABLE,X PN_CHANGE_OK ; Done txa ldx CURSOR sta DIGITS,X bra PN_REDRAW ; Remember, redraw needs Cursor in X ; Routines to handle Cursor Movement PN_CURSORFIRST ; Set cursor to 0 (left/top most) digit ldx #$FF ; (Set it to -1 and let following routine fix it) PN_CURSORRIGHT ; Move cursor right incx incx PN_CURSORLEFT ; Move cursor left decx PN_REDRAW ; Set Cursor, then redraw. X must contain current cursor PN_CURSOR_SET ; Handle rolling off the edge in both directions txa and #$0F ; A = A mod 16 cmp #$0C ; if a = 13 then a = 0 beq PN_CURSORFIRST ; cmp #$0F ; if a = -1 then a = 12 bne PN_CURSOR_SET1 ; PN_CURSORLAST ; lda #$0B ; PN_CURSOR_SET1 ; A contains CURSOR mod 16 ldx CURRENT_PROGRAM stx S8_NUMPAD+7 ; Patch display to read 'NUMPAD n' lslx lslx lslx lslx stx TEMP ; TEMP = (Program * 16) add TEMP sta CURSOR ; CURSOR = CURSOR + (Program * 16) txa add #DIGITS - START ; TEMP = DIGITS - START + (Program * 16) sta TEMP brset 3,$93,PN_CURSOR_KEEPGLOW ; Are we in night mode? jsr INDIGLO_OFF ; no, turn off indiglo, in case GLOW was pressed PN_CURSOR_KEEPGLOW jsr CLEARALL ; Clear the display lda TEMP jsr PUT6TOP ; Print Line 1 lda TEMP add #6 jsr PUT6MID ; Print Line 2 lda #S8_NUMPAD-START jsr BANNER8 ; Print banner ; ; Cursor logic - displays a "," on the digit currently being edited ; Some effort made to keep this code compatible with the 150 and the 150S ; lda CURSOR and #$0F ; Cursor = Cursor mod 16 tax sub #$06 bpl PN_REDRAWMID ; If cursor >=6 then we are on line 2 PN_REDRAWTOP ; See definition of DISP_ROW in WRISTAPP.I ldx CURSORS,X stx DISP_ROW bset COL_T1I,DISP_COL rts PN_REDRAWMID tax ldx CURSORS,X stx DISP_ROW bset COL_M1I,DISP_COL rts ;########################### ;# TIMER APPLICATION CODE # ;########################### PT_HANDLE_TIMER bset ST_REDRAW,STATUS PT_HANDLE_TIMER0 cmp #EVT_ENTER ; Is this our initial entry? beq PT_ENTER cmp #EVT_RESUME ; Are we resuming from Suspense? beq PT_RESUME bset ST_BPRESSED,STATUS ; Mark that the user has pressed a button here ;cmp #EVT_NEXT ; Next button tsta beq PT_START_STOP cmp #EVT_PREV ; PREV button beq PT_SPLIT cmp #EVT_GLOW ; Let it glow, let it glow, let it glow beq PT_RETURN cmp #EVT_UPSET ; Did they release the SET key? beq PT_COUNTDOWN_DONE ; yes - get out of countdown set mode jmp PT_COUNTDOWN_SET ; Must be the SET button (DOWN) PT_ENTER ; Routine called when we first enter the timer PT_RESUME ; and if we are resuming jsr CLEARALL ; jsr PUT_HOURX ; just to display the colon ; lda #ROW_MC23 ; format display as __:__.__ ; sta DISP_ROW ; bset COL_MC23,DISP_COL ; lda #ROW_MP45 ; replaced by call to PUT_MINUTEX, below ; sta DISP_ROW ; bset COL_MP45,DISP_COL bclr ST_REDRAWING,STATUS PT_COUNTDOWN_DONE ; Stop incrementing the counter, if we are bclr ST_SETCOUNT,STATUS PT_SPLIT_OFF ; Set split off bset ST_100S,STATUS ; Show the real 100th digit lda #S8_CHRONO-START ; Display "CHRONO" again jsr BANNER8 bra PT_REDRAW_AND_CLEAR_SPLIT PT_START_STOP ; Called when NEXT is pressed brclr ST_RUNNING,STATUS,PT_START ; If we are stopped, then Start bset ST_100S,STATUS ; Prepare to display the 100's jsr PT_TICK ; To redisplay the 1/100s byte with the present value bclr ST_RUNNING,STATUS ; Stop rts PT_START ; Restarts the timer lda TICKER sta LASTTICK bset ST_RUNNING,STATUS ; Run! PT_RETURN rts PT_SPLIT_TOGGLE ; We are here because the timer is running ;brset ST_SPLIT,STATUS,PT_SPLIT_OFF ; Already in SPLIT? Clear it, done lda #SYS8_SPLIT ; enter SPLIT mode jsr PUTMSGBOT bset ST_100S,STATUS jsr PT_TICK ; To fore update of 1/100s byte with the present value bset ST_SPLIT,STATUS rts PT_SPLIT ; Called when PREVIOUS is pressed brset ST_SPLIT,STATUS,PT_SPLIT_OFF ; If we are stopped and split, just clear split brset ST_RUNNING,STATUS,PT_SPLIT_TOGGLE ; If we are running, just go to split mode lda COUNT_LAST ; If count_last = FF inca bne PT_RESET_TIMER ; Zero the stopwatch lda COUNT_INIT ; Is counter turned off? bne PT_SPLIT_CLEAR_COUNT_INIT ; Turn off counter lda STATUS ; Nothing else to do, so toggle alarm/beep mode eor #SV_ALARM ; Flip Alarm bit sta STATUS ;?B{ and #SV_ALARM bne PT_SPLIT_CLEAR_COUNT_INIT lda MODE_FLAGS ; toggle keybeeps eor #$80 ; bit 7 sta MODE_FLAGS ;?B} PT_SPLIT_CLEAR_COUNT_INIT clr COUNT_INIT PT_RESET_TIMER ; Clear Hours, Minutes, Seconds ldx #3 PT_RESET_LOOP clr HOURS,X decx bpl PT_RESET_LOOP stx COUNT_LAST ; Conveniently, X contains $FF, which will force ; an update the moment we start the chronometer lda COUNT_INIT PT_RESET_COUNT_NOW sta COUNT_NOW ; reset countdown PT_REDRAW_AND_CLEAR_SPLIT bclr ST_SPLIT,STATUS bset ST_REDRAW,STATUS PT_REDRAW ; Redraws the timer display brset ST_SPLIT,STATUS,PT_RETURN ; Don't redraw if we are in split mode brset ST_REDRAWING,STATUS,PT_RETURN ; Don't redraw if we are already redrawing bset ST_REDRAWING,STATUS ; Having some strange problems with segments getting ; trashed when buttons pressed. It appears that the ; problem is that tick events happen while this routine is ; running. This flag checks for the problem ldx HUNDREDS jsr FMTXLEAD0 ; Hide the 100s digit. Because we usually get a tick every 10/100s of a second, ; this digit tends to appear 'stuck' on the display, even though it is ; internally computed correctly. brset ST_100S,STATUS,PT_REDRAW_NOHIDE ; Show the real 100th digit clr DATDIGIT2 PT_REDRAW_NOHIDE bclr ST_100S,STATUS jsr PUTTOP56 brclr ST_REDRAW,STATUS,PT_REDRAW_SKIP ; Skip the rest if we don't need them. ldx SECONDS jsr FMTXLEAD0 jsr PUTMID56 ldx MINUTES jsr PUT_MINUTEX ;jsr FMTXLEAD0 ;jsr PUTMID34 ldx HOURS ;jsr PUT_HOURX jsr FMTXLEAD0 jsr PUTMID12 ldx COUNT_NOW jsr FMTX ldx COUNT_INIT bne PT_REDRAW_100S jsr FMTBLANK0 PT_REDRAW_100S jsr PUTTOP12 PT_REDRAW_INDICATORS lda #ROW_ALARM ; Now show or hide the little "o)))" icon sta DISP_ROW bclr COL_ALARM,DISP_COL ; hide it brclr ST_ALARM,STATUS,PT_REDRAW_DONE0 bset COL_ALARM,DISP_COL ; show it PT_REDRAW_DONE0 ;?B{ lda #ROW_NOTE ; Now show or hide the little "Note" icon sta DISP_ROW bclr COL_NOTE,DISP_COL ; hide it brclr 7,MODE_FLAGS,PT_REDRAW_DONE1 bset COL_NOTE,DISP_COL ; show it ;?B} PT_REDRAW_DONE1 PT_REDRAW_SKIP bclr ST_REDRAWING,STATUS bclr ST_REDRAW,STATUS PT_RETURN2 rts ; DONE PT_COUNTDOWN_SET ; User pressed the SET button lda COUNT_LAST ; If count_last = FF inca ; (i.e. stopwatch display is not 00) bne PT_RESET_TIMER ; just reset the stopwatch lda #6 ; Ok, increment the countdown period. If the user keeps this button sta TEMP ; pressed for over 8/10s sec, we will start incrementing it rapidly lda #SYS8_SET ; Display 'SET' jsr PUTMSGBOT bset ST_SETCOUNT,STATUS bra PT_COUNTDOWN_INC PT_TICK ; Called approx. every 1/10th second bsr PA_FUDGE_ALARMS PT_COUNTDOWN_TIMED_INC ; The user is pressing on the SET key, handle it brclr ST_SETCOUNT,STATUS,PT_COUNTDOWN_TIMED_INC_DONE ; No s/he is not brset 7,TEMP,PT_COUNTDOWN_INC; Have we been in this mode for > 8/10ths second? (Temp = -1) dec TEMP PT_COUNTDOWN_TIMED_INC_DONE brclr ST_RUNNING,STATUS,PT_RETURN2 ; Do nothing if we are paused lda TICKER ; This is a constantly rolling counter, it increments by one every 1/100sec tax sub LASTTICK ; figure out how many 1/100s elapsed since we were last here stx LASTTICK ; and remember it for next time add HUNDREDS ; Add that to our 'hundreds' byte sta HUNDREDS ldx #3 ; for byte=hundreds to hours PT_TICK_LOOP ; Whenever one of the bytes overflows, lda HOURS,X ; this loop increments the next higher sub X_IN_Y,X ; byte. blo PT_TICK_DONE bset ST_REDRAW,STATUS ; We'll have to redraw sta HOURS,X ; Store subtracted value in current byte inc HOURS-1,X ; and Increment the next higher byte decx ; note: TEMP gets trashed bpl PT_TICK_LOOP ; loop PT_TICK_DONE ldx MINUTES ; Yes - Have the minutes changed since we were here last? cpx COUNT_LAST beq PT_TICK_DONE0 ; No - bye stx COUNT_LAST ; Remember the current number of minutes lda COUNT_INIT ; Are we using the countdown feature? beq PT_TICK_DONE0 ; No - bye dec COUNT_NOW bpl PT_TICK_DONE0 ; Out of time? deca ; Reset the time, A still contains COUNT_INIT sta COUNT_NOW lda #SND_CONF brclr ST_ALARM,STATUS,PT_TICK_BEEP ; Should we beep or should we start the alarm? lda #SND_ALARM PT_TICK_BEEP sta SYSSOUND ; Play Sound jsr SNDSTART ; PT_TICK_DONE0 ; Done, now redraw the screen jmp PT_REDRAW PT_COUNTDOWN_INC lda COUNT_INIT ; Increment the value of COUNT_INIT by A+1 ldx #99 jsr INCA_WRAPX PT_COUNTDOWN_INC0 sta COUNT_INIT jmp PT_RESET_COUNT_NOW ; Update the COUNT_NOW byte (for display) and redraw ;###################### ;# WEEKLY ALARM CODE # ;###################### PA_FUDGE_ALARMS ; Note: TEMP and DOW are the same variable! lda CURRENT_APP and #$02 bne PA_RETURN ; Do not run this code if we are in ALARM or COMM mode ;?B{ bset 4,MODE_FLAGS brset 7,MODE_FLAGS,PA_ADJUST bclr 4,MODE_FLAGS ;?B} ;?C{ ; Hourly Chime Control Code ; Note : This only works for Time Zone 1 lda TZ1_HOUR jsr CHECK_TZ ; See which one we are really using bcc PA_SET_CHIME ; If we were right, just skip on to do the work lda TZ2_HOUR ; Wrong guess, just load up the second time zone PA_SET_CHIME cmp #20 ; 8 O'Clock PM bhi PA_CHIME_OFF cmp #08 ; 8 o'Clock AM blo PA_CHIME_OFF bset 2,MODE_FLAGS bra PA_SET_CHIME_DONE PA_CHIME_OFF bclr 2,MODE_FLAGS PA_SET_CHIME_DONE ;?C} PA_ADJUST ;?W{ lda TZ1_DOW ; Assume that we are using the first timezone jsr CHECK_TZ ; See which one we are really using bcc PA_SET_DOW ; If we were right, just skip on to do the work lda TZ2_DOW ; Wrong guess, just load up the second time zone PA_SET_DOW inca ; Adjust to our numbering scheme sta DOW lda #4 ; For 5 alarms PA_LOOP tax ; Silly register tricks lslx lslx lslx ldx $0476,X ; get last digit on display beq PA_WEEKDAY ; if 0, do weekday routine cpx #8 ; if 8, do weekend routine beq PA_WEEKEND bhi PA_NEXT ; if > 8 do nothing cpx DOW ; compare with day of week beq PA_ALARM_ON PA_ALARM_OFF ; Set the alarm off tax lda ALARM_STATUS,X ; Set the alarm off and #$FE bra PA_SET PA_WEEKEND ; Is today a weekend? ldx DOW cpx #5 bhi PA_ALARM_ON bra PA_ALARM_OFF PA_WEEKDAY ; Is today a weekday? ldx DOW cpx #5 bhi PA_ALARM_OFF PA_ALARM_ON ; Set the alarm on tax lda ALARM_STATUS,X ora #$01 PA_SET sta ALARM_STATUS,X ; done txa PA_NEXT deca bpl PA_LOOP ; next timer ;?W} PA_RETURN rts