; NWRF Nixie Clock program for 2 B5870 tubes and PIC16F722 ; ; Copyright (C) 2005,2015 David Forbes ; ; 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 of the License, or ; (at your option) 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. ; ;------------------------------------------------------------- ; Revision history ; ; 01/05/10 DF Making NWRF version from NWRD version ; 01/06/10 DF Added ANSELA and ANSELB to make buttons work ; Inverted sense of X and Y accelerometer data ; 02/15/10 DF Made tilt setting routine read tilt twice ; 05/18/15 DF Adding feature to detect if reset, not init RAM ; 3/14/16 DF Pi Day! Changed THrs, UHrs to Hrs 0..23 with LdHrs work for 12hr mode ; Reduced set timeout by half ; ;------------------------------------------------------------- ; Theory of Operation ; ; This clock has two display tubes and uses them to show ; first hours, then minutes when the tilt sensor says to. ; ; The processor runs at the stately speed of 8192 instructions per second. ; This has had a big influence on the code structure, as there are no ; spare cycles to waste. Every NOP is visible in the display. ; ; The tilt sensor is a 3-axis accelerometer ADXL335. We use Y and Z, not X. ; A/D converter is two channels with A3 used as power out to tilt sensor. ; Vref is taken from A3 since the ADXL335 is Vcc-referenced. ; ; The tilt sensing algorithm works by looking for both X and Y g factors ; of approximately 0.65g in the desired direction of upwards and toward ; the user. ; To prevent false triggering, an additional constraint is Xtilt + ytilt. ; ; The ADC is not very sensitive as used; the tilt values have the following ; range (approximate). ; X is h'70' at horizontal, h'80' at vertical. Good tilt < h'75'. ; Y is h'80' at horizontal, h'90' at vertical. Good tilt > h'8B'. ; ; OffLoop waits for valid angles from tilt sensor or Set button push. ; Pressing the Adv button while off enters diagnostic tilt display mode. ; ; When the display turns on it displays hours for a second, then ; displays minutes for a second, then either shuts off or displays seconds. ; ; The display code is constructed to minimize off-time of tubes ; so that display will be as bright as possible. This means that ; all display formatting is done outside the tube-scanning loop. ; ; There is a diagnostic tilt angle display activated by pressing Adv when off. ; First X tilt is displayed, then Y tilt, then back to off. ; The tilt angle display does a cheesy hex display by lighting two cathodes ; such as '9' and '1' for hex 'A'. Their sum is the hex value. ; I could modify this to use BCD, but life is too short. ; Perhaps someone else wants to do that. ; ; Timesetting mode is entered from off loop by detecting Set button held. ; first we set 12/24 then hours then tens minutes then unit minutes then ; the tilt angle. The Adv button advances the digit(s) being set. ; ; If the watch is tilted for 1 whole second after hrs/mins display, ; then it enters seconds display mode. It turns off either ; if it's no longer tilted right or if the seconds timeout expires. ; Tilt check occurs right after the change of the second. ; ; The display period for each second is 2/3 second to save power. ; ; Timer is set to div/16384 to give one tick per second. ; No interrupts are used since they cause display glitches. ; Instead, time is incremented by DoTime when timer0 overflows. ; ; Hardware trick: Resistor from TiltPwr to HV sense provides ; blanking control by driving HV below 120V if TiltPwr on. ; Tilt is not needed at this time, so it's a free control bit. ; ;--------------------------------------------------------------- ; ; define chip type, hardware addrs & bits ; list p=16lf722 include ; Time constants for display DigTim equ h'0A' ; # loops per 6 milliseconds (Less due to DoSet) OffTim equ h'C0' ; # loops per 1/8 second for offloop TiltTim equ h'0E' ; # of mux cycles to display tilt HrsTim equ h'1A' ; # of mux cycles to display hours PausTim equ h'30' ; # of mux cycles to pause btwn hrs,mins MinTim equ h'20' ; # of mux cycles to display minutes SecTim equ h'14' ; # of mux cycles to display seconds BlnkTim equ h'0C' ; blink half-period in mux cycles HangTim equ h'06' ; # of Offtims to wait after displaying SetTOut equ h'1e' ; time setting timeout is 30 seconds TDTOut equ h'0F' ; tilt display timeout in seconds XTMargin equ h'fe' ; display X tilt margin from captured X YTMargin equ h'02' ; display Y tilt margin from captured Y Mag1 equ 0xda Mag2 equ 0x55 ; two magic numbers tell if RAM is still valid ; Port A bit definitions ; ; PA5 I Adv 0=Adv button pushed ; PA4 I Set 0=Set button pushed ; PA3 A,O TPwr Analog vref in and 1->TiltPwr, HV blank ; PA2 O DispEn 1->DispPwr ; PA1 A TiltX Analog X tilt signal ; PA0 A TiltY Analog Y tilt signal AdvButt equ 5 ; Adv button bit pressed = 0 SetButt equ 4 ; Set button bit pressed = 0 TiltY equ 0 ; Y tilt sensor analog signal TiltX equ 1 ; X tilt sensor analog signal ; ; Write the following literals to PORTA to set power bits ; PwrOff equ h'00' ; no power to nuthin' PwrDisp equ h'04' ; display HV on and not blanked PwrTilt equ h'08' ; Tilt sensor power on, no HV PwrBlnk equ h'0C' ; display HV on but blanked ; ; Port B bit definitions ; ; PB7 O R1 ; Right digit cathode 1 ; PB6 O R0 ; PB5 O R2 ; PB4 O R9 ; PB3 O R3 ; PB2 O R8 ; PB1 O R4 ; PB0 O R5 ; Port C bit definitions ; ; PC7 O L2 ; Left digit cathode 2 ; PC6 O L3 ; PC5 O L4 ; PC4 O L1 ; PC3 O R6 ; PC2 O R7 ; PC1 O L0 ; PC0 O L5 ; ; Bits in SetDig tell which digit's being set ; NotSet equ 0 ; not in set mode H24Set equ 1 ; setting hours HrsSet equ 2 ; setting hours TMinSet equ 3 ; setting tens of minutes UMinSet equ 4 ; setting units minutes SecSet equ 5 ; set seconds to 00 when Adv pushed T45Set equ 6 ; setting tilt angle for 45 degrees DoneSet equ 7 ; were setting tilt, shut off display now ; ; Bits in TDDig tell which tilt axis is displayed ; NotTD equ 0 ; not in tilt disp mode XTDisp equ 1 ; display X tilt YTDisp equ 2 ; display Y tilt DoneTD equ 3 ; were displaying tilt, shut off display now ; ; bit definitions for display status word Display ; LBlink equ 0 ; left blink flag 1=blink RBlink equ 1 ; right blink flag 1=blink LBlank equ 2 ; left blank flag 1=blank RBlank equ 3 ; right blank flag 1=blank Hrs24 equ 7 ; 12/24 hour mode: 1=24hr ; ; Define RAM storage variables ; cblock h'020' Hrs ; binary hours range 0-23 always TMins ; tens of minutes range 0-5 UMins ; units of minutes range 0-9 TSecs ; tens of seconds range 0-5 USecs ; units of seconds range 0-9 TMinD ; displayed tens of minutes UMinD ; displayed units of minutes LDigit ; Left digit BCD value to display RDigit ; Right digit BCD value to display SetDig ; code of digit being set SetHist ; time that Set button has been held on TDHist ; time that Adv button has been held on for tdisplp TDDig ; mode of tilt display AdvHist ; time that Adv button has been held on TltHist ; time that tilt has been correct Display ; display status bits per above DispTim ; time left to display this bit XTilt ; tilt of X axis from tilt sensor YTilt ; tilt of Y axis from tilt sensor DTilt ; Y tilt - X tilt from tilt sensor XTLim45 ; X tilt limit for 45 degree display YTLim45 ; Y tilt limit for 45 degree display DTLim45 ; Y-X tilt limit for 45 degree display TimLeft ; delay routine working storage LBData ; Left digit B port cathode bits LCData ; Left digit C port cathode bits RBData ; Right digit B port cathode bits RCData ; Right digit C port cathode bits SetTim ; timeout countdown for time setting mode (secs) TDTim ; timeout countdown for tilt display mode (secs) FirstHr ; first hour 0 or 1 LastHrT ; last tens hour 1 or 2 LastHrU ; last units hour 3 or 4 DispHrT ; display tens hour 1 or 2 DispHrU ; display units hour 3 or 4 SecCnt ; number of seconds left to display Magic1 ; magic number 0xda if RAM intact Magic2 ; other magic number 0x57 if RAM intact endc ; ; Upon reset, init ports and timer ; org h'000' nop ; for debugger goto Start ; ; Load display digits with various time digits ; These are called by all display updating functions ; LdSecs movfw TSecs ; copy current secs into disp digits movwf LDigit movfw USecs movwf RDigit call LdDigs return LdMins movfw TMins ; copy current mins into disp digits movwf LDigit movfw UMins movwf RDigit call LdDigs return LdMinD movfw TMinD movwf LDigit movfw UMinD ; copy saved minutes into L&R digits movwf RDigit call LdDigs return ; ; 12 hour display is processed here. ; ; If 24 hour mode, no mod needed. ; If the hours are zero, make it be 12. ; if the hours are > 12, subtract 12. ; LdHrs movfw Hrs movwf RDigit ; working storage for hours clrf LDigit ; decimal adjust into LDigit btfsc Display,Hrs24 goto THrLoop ; no work for 24 hour mode movfw LDigit ; see if it's all zero iorwf RDigit,w skpz goto HrNotZ movlw 0x0c ; zero hours is 12 o'clock movwf RDigit goto THrLoop HrNotZ movfw RDigit addlw 0xf4 ; See how it compares to 12 skpnz goto THrLoop ; 12 noon, don't adjust skpnc movwf RDigit ; >12, use hours - 12 for 12 hour mode THrLoop movfw RDigit addlw 0xf6 ; subtract ten skpc goto NoDec ; too far, done adjusting incf LDigit movwf RDigit ; one decade done goto THrLoop NoDec call LdDigs ; send to display return ; ; Load port B and C digit storage with cathode codes ; Reads LDigit and RDigit ; puts results in LBData..RCData ; ; This is done here to increase brightness by reducing workload ; of DispLp, which used to do all this stuff every iteration. ; LdDigs movlw h'07' ; prepare for left digit display andwf LDigit,f ; fix range of number call BLeft movwf LBData ; save B and C cathode codes call CLeft ; using bizarre PIC table lookup subroutines movwf LCData movlw h'0F' ; prepare for right digit display andwf RDigit,f ; fix range of number call BRight movwf RBData ; Save B and C cathode codes call CRight movwf RCData return ; ; The following tables are located here to ensure no wrap on PCL ; ; Lookup routines to set ports B and C to Nixie cathode ; per LDigit and RDigit ; ; Port B left tube ; BLeft movfw LDigit addwf PCL,f ; RRRRRRRR ; 10293845 retlw B'00000000' ; left 0 retlw B'00000000' ; left 1 retlw B'00000000' ; left 2 retlw B'00000000' ; left 3 retlw B'00000000' ; left 4 retlw B'00000000' ; left 5 retlw B'00000000' ; left 6 displays 1,5 retlw B'00000000' ; left 7 displays 2,5 ; ; Port C left tube ; CLeft movfw LDigit addwf PCL,f ; LLLLRRLL ; 23416705 retlw B'00000010' ; left 0 retlw B'00010000' ; left 1 retlw B'10000000' ; left 2 retlw B'01000000' ; left 3 retlw B'00100000' ; left 4 retlw B'00000001' ; left 5 retlw B'00010001' ; left 6 displays 1,5 retlw B'10000001' ; left 7 displays 2,5 ; ; Port B right tube ; BRight movfw RDigit addwf PCL,f ; RRRRRRRR ; 10293845 retlw B'01000000' ; right 0 retlw B'10000000' ; right 1 retlw B'00100000' ; right 2 retlw B'00001000' ; right 3 retlw B'00000010' ; right 4 retlw B'00000001' ; right 5 retlw B'00000000' ; right 6 retlw B'00000000' ; right 7 retlw B'00000100' ; right 8 retlw B'00010000' ; right 9 retlw B'11000000' ; right 10 displays 1,0 retlw B'00110000' ; right 11 displays 2,9 retlw B'10100000' ; right 12 displays 1,2 retlw B'10001000' ; right 13 displays 1,3 retlw B'10000010' ; right 14 displays 1,4 retlw B'10000001' ; right 15 displays 1,5 ; ; Port C right tube ; CRight movfw RDigit addwf PCL,f ; LLLLRRLL ; 23416705 retlw B'00000000' ; right 0 retlw B'00000000' ; right 1 retlw B'00000000' ; right 2 retlw B'00000000' ; right 3 retlw B'00000000' ; right 4 retlw B'00000000' ; right 5 retlw B'00001000' ; right 6 retlw B'00000100' ; right 7 retlw B'00000000' ; right 8 retlw B'00000000' ; right 9 retlw B'00000000' ; right 10 displays 1,0 retlw B'00000000' ; right 11 displays 2,9 retlw B'00000000' ; right 12 displays 1,2 retlw B'00000000' ; right 13 displays 1,3 retlw B'00000000' ; right 14 displays 1,4 retlw B'00000000' ; right 15 displays 1,5 ; ; Execution starts here after reset ; Start nop bcf STATUS,RP0 ; point to low bank bcf STATUS,RP1 clrf PORTA ; turn off power clrf PORTB ; Clear nixie ports clrf PORTC movlw B'00000000' ; chan0, A/D disabled movwf ADCON0 ; hi RAM bsf STATUS,RP0 ; point to high bank movlw B'10000100' ; TMR0 prescaler, 1:32 movwf OPTION_REG movlw B'00110011' ; PORTA bits 2,3 output movwf TRISA clrf TRISB ; PORTB all outputs clrf TRISC ; PORTC all outputs movlw B'01110010' ; internal RC oscillator, AN3 Vref movwf ADCON1 ; hi hi RAM bsf STATUS,RP1 ; point to extra high bank movlw B'00001011' ; only AN0, AN1, AN3 are analog - tilt sensor movwf ANSELA movlw B'00000000' ; port B digital (outputs; shouldn't matter) movwf ANSELB ; low RAM bcf STATUS,RP0 ; point to low bank bcf STATUS,RP1 ; really point to low bank ; ; Check magic numbers to see if reset rather than POR ; movfw Magic1 addlw 0-Mag1 ; first magic number skpz goto doInit movfw Magic2 ; second magic number addlw 0-Mag2 skpnz goto OffLoop ; don't do RAM init if magic OK doInit movlw Mag1 movwf Magic1 movlw Mag2 movwf Magic2 ; initialize magic numbers clrf Hrs clrf TMinD clrf UMinD clrf TMins clrf UMins ; set mins,secs to :00:00 clrf TSecs clrf USecs clrf LDigit clrf RDigit clrf LBData clrf LCData clrf RBData clrf RCData clrf AdvHist clrf SetHist clrf TltHist clrf Display clrf DispTim clrf XTilt clrf YTilt movlw h'8C' ; close to +0.65g movwf XTLim45 movlw h'74' ; close to -0.65g movwf YTLim45 movlw h'18' ; Y - X movwf DTLim45 call SetHr12 ; set to 12 hour mode clrf SetTim clrf SetDig bsf SetDig,NotSet ; flag no setting going on clrf TDTim clrf TDDig bsf TDDig,NotTD ; turn off tilt display mode ; ; OffLoop follows tilt sequence, incs time, uses little power. ; OffLoop call WaitOff ; wait for 1/8 of second call DoTime ; update time variables call DoSet ; check for set button btfss SetDig,NotSet goto SetLoop ; Got to timesetting loop if setting call DoTD ; check for Adv button btfss TDDig,NotTD goto TDLoop ; goto tilt disp loop if Adv was pressed call GetTilt ; read sensor incf TltHist,f ; assume tilt is good call ChkT45 ; See if tilt is 45 yet skpc ; good tilt clrf TltHist ; bad tilt, start over movlw 2 ; test TltHist is 2 for good tilt subwf TltHist,w skpz goto OffLoop ; not good, do it all again ; ; Tilt sequence correct - turn on watch to show time ; incf TltHist,f ; force another tilt to display again call LdHrs ; get hours into display bytes movfw TMins movwf TMinD ; save current minutes movfw UMins ; so it won't show old hrs but new mins movwf UMinD bcf Display,LBlank ; disp both digits for now bcf Display,RBlank NoHBlnk movlw HrsTim movwf DispTim ; set time to display hours movlw PwrBlnk ; turn on HV but blank display movwf PORTA call DispLpN ; display the hours movlw PausTim movwf DispTim HrMinLp call WtDigit ; pause between hrs, mins decfsz DispTim,f goto HrMinLp call LdMinD ; set display to saved minutes movlw MinTim movwf DispTim ; set time to disp minutes call DispLpN ; display the minutes movlw PausTim movwf DispTim MinScLp call WtDigit ; pause between mins, secs decfsz DispTim,f goto MinScLp movlw SecTim movwf SecCnt ; set timeout for seconds display ; ; loop for seconds display till duty cycle reaches zero ; SecLp btfss INTCON,T0IF ; test timer overflow goto SecLp ; loop till set (top of second) call DoTime ; update time variables call GetTilt ; read sensor call ChkT45 ; See if tilt is still good skpc ; good tilt, do another second goto PwrDown ; bad tilt, exit seconds display call LdSecs ; update seconds value to display movfw SecCnt ; duty cycle is remaining loop index movwf DispTim ; set time to display them call DispLpN ; display the seconds decfsz SecCnt,f ; count down seconds display timeout goto SecLp ; still active, do another second ; ; Finish up time display ; PwrDown clrf PORTB ; shut off nixies clrf PORTC clrf PORTA ; turn off power movlw HangTim movwf DispTim ; set up holdoff timer HangLp call WaitOff call DoTime decfsz DispTim,f ; kill time between displayings goto HangLp goto OffLoop ; ready to display again ; ; TDispLp - diagnostic display of tilt values ; ; TDispLp displays X or Y tilt in hexadecimal on the nixies ; continuously for test purposes. ; It reads the tilt 2 times a second and updates the display at this ; rate. Power is shut down for tilt reading, causes display to wink. ; ; TDispLp is entered from OffLp when Adv button is pushed, ; causing TiltDig to be advanced to XTDisp. Adv steps through modes: ; off -> XTilt -> YTilt -> Done ; TDLoop call DoTime ; keep timekeeping up to date call GetTilt ; read tilt sensor movlw PwrBlnk ; turn on HV but blank display movwf PORTA movfw XTilt btfsc TDDig,YTDisp ; use Y instead of X when in Y mode movfw YTilt movwf LDigit ; MSBs into left digit swapf LDigit,f ; move MSBs to LSBs of left digit movwf RDigit ; LSBs to right digit call LdDigs ; write digits to tubes bcf Display,LBlank ; disp both digits bcf Display,RBlank movlw TiltTim ; set time to display it movwf DispTim call DispLpN ; display tilt but don't check buttons movlw PwrOff ; turn off HV movwf PORTA call DoTD ; process Adv button for tilt modes btfss TDDig,NotTD ; exit this loop if out of tdisp mode goto TDLoop ; pushed, so do it again goto OffLoop ; else go back to being a clock ; ; DoTD detects if the Adv button is pushed, and debounces. ; If it's time for action, it sequences through the tilt display modes. ; DoTD btfss PORTA,AdvButt ; read the button - 0 is pushed goto IsTD clrf TDHist ; button not pressed - clear history tstf TDTim ; timed out? skpnz goto DoneT ; yes, turn off tilt display mode goto NoTD IsTD incf TDHist,f ; it is pushed - bump history counter movlw 4 subwf TDHist,w skpnz ; make hist stick at 4 to prevent autorepeat decf TDHist,f GotTD movlw 1 subwf TDHist,w ; debounce time is 1 loop skpz goto NoTD movlw TDTOut ; load tilt display timeout counter movwf TDTim clrc rlf TDDig,f ; bump to next setting mode btfss TDDig,DoneTD ; are we done tilt displaying? goto NoTD DoneT clrf TDDig ; turn off tilt display mode bsf TDDig,NotTD clrf TDTim ; clear timeout counter NoTD return ; ; -------------------------------------------------------- ; ; Time setting code ; ; Setting procedure: ; ; Hold Set button till display lights, then release. ; Display is unblinking '12' or '24', indicating hours mode. ; Press Adv button to toggle between 12 hour and 24 hour modes. ; Press Set button and release. ; Both digits of Hours will flash. ; Press Adv button once per increment to set hours. ; Press Set button and release. ; Tens of Minutes will flash, units will be solid. ; Press Adv button once per increment to set tens minutes. ; Press Set button and release. ; Units of Minutes will flash, tens will be solid. ; Press Adv button once per increment to set units minutes. ; Press Set button and release. ; Display is incrementing seconds. ; Press Adv button once to reset seconds to 00. ; Press Set button and release. ; Display is unblinking '45'. ; Hold watch at best viewing angle, typically 45 degrees. ; Press Adv button to save tilt angle. Display will flash once. ; Press Set button and release. ; Display will blank. Setting is done. ; ; Set & Adv buttons are sampled once per mux iteration. ; Each button must be pressed then released for each action. ; SetLoop is entered from OffLp when Set button is pushed, ; causing SetDig to be advanced to HrsSet. ; ; The setting state machine keeps its state in SetDig. ; SetHist is a count of how long the Set button has been held down. ; When the buton has been hjeld down long enough to debounce, then ; the state machine is advanced to the next state and the necessary ; variables are updated to make the display show the right stuff for ; that mode. Similarly, the Adv button's time pressed is AdvHist. ; When Adv is pressed, the appropriate vairable is incremented and ; the display variables are updated. ; ; Display time with blinking digits, read buttons and act on them. ; SetLoop movlw BlnkTim ; set display duration movwf DispTim bcf Display,LBlank ; Enable display for on phase of blink bcf Display,RBlank call DispLp ; Do display, read buttons movlw BlnkTim movwf DispTim ; set duration btfsc Display,LBlink ; set L blanking flag per blink request bsf Display,LBlank btfsc Display,RBlink ; set R blanking flag per blink request bsf Display,RBlank call DispLp ; do display, read buttons btfss SetDig,NotSet goto SetLoop ; loop if still setting clrf PORTB ; shut off nixies clrf PORTC clrf PORTA ; turn off power goto OffLoop ; ; DoSet detects if the Set button is pushed, and debounces. ; If it's time for action, it selects the digit being set in the order: ; ; This routine sets up the display mode to match the set function. ; DoSet btfss PORTA,SetButt ; read the button - 0 is pushed goto IsSet clrf SetHist ; button not pressed - clear history tstf SetTim ; timed out? skpnz goto DoneS ; yes, turn off timesetting mode goto NoSet IsSet incf SetHist,f ; it is pushed - bump history counter movlw 4 subwf SetHist,w skpnz ; make hist stick at 4 to prevent autorepeat decf SetHist,f GotSet movlw 2 subwf SetHist,w ; debounce time is 2 loops ~=40 millisec skpz goto NoSet movlw SetTOut ; load time setting timeout counter movwf SetTim clrc rlf SetDig,f ; bump to next setting mode btfsc SetDig,H24Set ; is it 12/24 hour mode? goto H24S btfsc SetDig,HrsSet ; is it hours? goto HrsS btfsc SetDig,TMinSet ; is it tens of minutes? goto TMinS btfsc SetDig,UMinSet ; is it units of minutes? goto UMinS btfsc SetDig,SecSet ; is it seconds? goto SecS btfsc SetDig,T45Set ; is it tilt? goto Tilt45S btfsc SetDig,DoneSet ; are we done setting? goto DoneS ; ; Enter H24 setting mode ; ; This routine turns on the display when timesetting mode is entered. ; H24S movlw PwrBlnk ; turn on HV but blank display movwf PORTA nop nop movfw DispHrT ; get mode display value movwf LDigit ; into display digits movfw DispHrU movwf RDigit call LdDigs ; load it into disp storage bcf Display,LBlink ; no blinking bcf Display,RBlink goto NoSet ; ; Enter Hours setting mode ; HrsS call LdHrs ; copy current hours into disp digits bsf Display,LBlink ; blink both digits bsf Display,RBlink goto NoSet ; ; Enter TMins setting mode ; TMinS call LdMins ; copy current mins into disp digits bsf Display,LBlink ; blink only left digit bcf Display,RBlink goto NoSet ; ; Enter UMins setting mode ; UMinS call LdMins ; load minutes into disp storage bcf Display,LBlink ; blink only right digit bsf Display,RBlink goto NoSet ; ; Enter Secs setting mode ; SecS call LdSecs ; load seconds into disp storage bcf Display,LBlink ; No blink, but display advances bcf Display,RBlink ; via code at TimeRet. goto NoSet ; ; Enter Tilt45 setting mode ; Tilt45S movlw h'4' ; get a '45' for display movwf LDigit movlw h'5' movwf RDigit call LdDigs ; load it into disp storage bcf Display,LBlink ; no blinking bcf Display,RBlink goto NoSet ; ; Finish up when exiting set mode ; DoneS clrf SetDig ; leave setting mode bsf SetDig,NotSet clrf SetTim ; clear timeout counter clrf PORTB clrf PORTC clrf PORTA ; Shut off display NoSet return ; ; DoAdv processes the Adv button. If it's pushed, we debounce ; via AdvHist. When it's time to advance the clock setting, ; the appropriate digit(s) are incremented without carrying into other ; digits. ; DoAdv btfss PORTA,AdvButt ; read the button - 0 is pushed goto IsAdv clrf AdvHist ; button not pressed - clear history goto AdvDone IsAdv incf AdvHist,f ; it is pushed - bump history counter movlw 4 subwf AdvHist,w skpnz ; make hist stick at 4 to prevent autorepeat decf AdvHist,f GotAdv movlw 2 subwf AdvHist,w ; debounce time is 2 loops ~=40 millisec skpz goto AdvDone btfsc SetDig,H24Set ; is it 12/24 hour mode? goto H24A btfsc SetDig,HrsSet ; is it hours? goto HrsA btfsc SetDig,TMinSet ; is it Tenmins? goto TMinA btfsc SetDig,UMinSet ; is it UnitsMins? goto UMinA btfsc SetDig,SecSet ; is it seconds? goto SecA btfsc SetDig,T45Set ; is it Tilt at 45? goto Tilt45A AdvDone return ; Nothing to do so return ; ; toggle 12/24 hour mode ; H24A btfss Display,Hrs24 ; In 24 hour mode? goto H24No call SetHr12 ; yes, go from 24 to 12 hour mode goto H24Got H24No call SetHr24 ; else go from 12 to 24 hour mode H24Got movfw DispHrT ; get mode display value movwf LDigit ; into display digits movfw DispHrU movwf RDigit call LdDigs ; load it into disp storage goto AdvDone ; ; Set timekeeping to 12 hour mode ; SetHr12 movlw 1 movwf DispHrT ; display '12' movlw 2 movwf DispHrU bcf Display,Hrs24 return ; ; Set timekeeping to 24 hour mode ; SetHr24 movlw 2 ; display '24' movwf DispHrT movlw 4 movwf DispHrU bsf Display,Hrs24 return ; ; Advance hours ; HrsA incf Hrs,f ; bump the hours movlw 0xe8 addwf Hrs,w ; at last hour? skpnz movwf Hrs ; yes, reset hours to 0 o'clock call LdHrs ; copy new hours into disp digits goto AdvDone ; ; Advance tens of minutes ; TMinA incf TMins,f ; increment tens of minutes movlw 6 subwf TMins,w ; did it hit 6? skpnz clrf TMins ; yes, wrap from 5 to 0 call LdMins ; copy new minutes into disp digits goto AdvDone ; ; Advance units minutes ; UMinA incf UMins,f ; increment units of minutes movlw h'A' subwf UMins,w ; did it hit 10? skpnz clrf UMins ; yes, wrap from 10 to 0 call LdMins ; copy new minutes into disp digits goto AdvDone ; ; Reset seconds ; SecA clrf TSecs ; Set seconds to 0 clrf USecs clrf TMR0 ; set sub-seconds to 0 call LdSecs ; copy new seconds into disp digits goto AdvDone ; ; Capture and save 45 deg tilt angle ; Tilt45A movlw PwrOff movwf PORTA ; cut off power now for stability call GetTilt ; find out current tilt angle call WaitOff ; pause to flash display call GetTilt ; find out current tilt angle movlw XTMargin ; Get X tilt + XTMargin addwf XTilt,w movwf XTLim45 ; save as X limit movlw YTMargin ; Get Y tilt + YTMargin addwf YTilt,w movwf YTLim45 ; save as Y limit movfw YTilt ; Get X tilt - Y tilt subwf XTilt,w movwf DTLim45 ; save as D limit decf DTLim45,f ; make it X - Y - 1 goto AdvDone ; ;-------------------------------------------------------- ; Display loop ; ; DispLp displays the cathodes stored in [L,R][B,C]Data. ; It uses the blanking bits LBlank and RBlank per digit. ; It runs for DispTim then returns. ; Also calls Set and Adv button routines for timesetting mode. ; ; This routine does multiplexing by lighting first left digit then right. ; ; Entered and returns with display dark via TiltEn bit of Port A ; Also clears Ports B and C to ensure blankness ; DispLp call DoTime ; update time variables movfw LBData movwf PORTB ; Set ports B and C to left cathode codes movfw LCData movwf PORTC movlw PwrDisp btfss Display,LBlank ; blank this digit? movwf PORTA ; not blanked so let HV go high call WtDigit ; Wait for it to display call DoSet ; process the Set button while lit movlw PwrBlnk movwf PORTA ; blank display while changing cathodes movfw RBData movwf PORTB ; Set ports B and C to right cathode codes movfw RCData movwf PORTC movlw PwrDisp btfss Display,RBlank ; blank this digit? movwf PORTA ; not blanked so let HV go high call WtDigit ; Wait for it to display call DoAdv ; process the Adv button while other digit lit movlw PwrBlnk movwf PORTA ; blank via HV control from TiltPwr decfsz DispTim,f goto DispLp ; do it till count expires return ; ; This version doesn't call DoSet or DoAdv. It's used for normal display. ; DispLpN call DoTime ; update time variables movfw LBData movwf PORTB ; Set ports B and C to left cathode codes movfw LCData movwf PORTC movlw PwrDisp btfss Display,LBlank ; blank this digit? movwf PORTA ; not blanked so let HV go high call WtDigit ; Wait for it to display movlw PwrBlnk movwf PORTA ; blank display while changing cathodes movfw RBData movwf PORTB ; Set ports B and C to right cathode codes movfw RCData movwf PORTC movlw PwrDisp btfss Display,RBlank ; blank this digit? movwf PORTA ; not blanked so let HV go high call WtDigit ; Wait for it to display movlw PwrBlnk movwf PORTA ; blank via HV control from TiltPwr decfsz DispTim,f goto DispLpN ; do it till count expires return ; ; WtDigit delays for one digit time - about six milliseconds ; ; Destroys W ; WtDigit movlw DigTim ; get constant delay number movwf TimLeft ; copy to local storage WtLoop1 decfsz TimLeft,f goto WtLoop1 ; loop that many times return ; ; WaitOff delays for about 1/8 second while power is off ; ; Destroys W ; WaitOff movlw OffTim ; get constant delay number movwf TimLeft ; copy to local storage WtOff1 decfsz TimLeft,f goto WtOff1 ; loop that many times return ; ; DoTime checks timer, updates time vars accordingly ; ; The timer is set up to overflow once per second. This routine is called ; several times a second to poll the timer status to detect an overflow. ; If the timer overflows, the overflow bit is reset and the time variables ; are incremented according to standard clock behavior. ; ; The reason that interrupts are not used is that the display flickers ; when the interrupt occurrs. Been there, done that. ; DoTime btfss INTCON,T0IF ; test timer overflow goto TimeNop ; no overflow, do nothing (equalize time) bcf INTCON,T0IF ; reset overflow flag tstf SetTim ; Setting mode? skpz decf SetTim,f ; Yes, count down setting time left tstf TDTim ; Tilt display mode? skpz decf TDTim,f ; Yes, count down display time left incf USecs,f ; bump units seconds movlw h'A' ; ten second overflow? subwf USecs,w bnz TimeRet ; no, return clrf USecs incf TSecs,f ; bump tens seconds movlw h'6' ; Minute overflow? subwf TSecs,w bnz TimeRet ; no, return clrf TSecs incf UMins,f ; bump units minutes movlw h'A' ; ten minute overflow? subwf UMins,w bnz TimeRet ; no, return clrf UMins incf TMins,f ; bump tens minutes movlw h'6' ; hourly overflow? subwf TMins,w bnz TimeRet ; no, return clrf TMins incf Hrs,f ; bump the hours movlw 0xe8 addwf Hrs,w ; at last hour? skpnz movwf Hrs ; yes, reset hours to 0 o'clock TimeNop nop nop ; equalize timing of routine to nop ; prevent display glitch once per second nop nop nop nop nop nop nop TimeRet btfsc SetDig,SecSet ; If we're setting seconds, update display goto LdSecs return ; ; Read the tilt sensor. ; Results are placed in XTilt and YTilt. ; ; Disp, Tilt power is turned off by this routine. ; ; Sensor needs 2 milliseconds from TiltPwr on to first A/D conversion. ; Code is interleaved to minimize power-on time of tilt sensor, ; which uses 300 uA. This saves battery drain. ; ; The remnants are from an attempt to use higher resolution on the ADC. ; It needed all 10 bits, so I didn't bother. Seems to work as is. ; GetTilt movlw PwrTilt ; turn on tilt sensor power movwf PORTA bsf ADCON0,ADON ; turn on the A/D converter module bcf ADCON0,CHS0 ; select A/D channel 0 = X nop nop ; wait one millisecond nop nop nop nop nop nop bsf ADCON0,GO ; Start A/D conversion at 2 milliseconds nop ; Wait for X conversion to complete bsf ADCON0,CHS0 ; select A/D channel 1 = Y movfw ADRES ; get X tilt from A/D movwf XTilt ; save X tilt bsf ADCON0,GO ; Start second A/D conversion nop ; Wait for Y conversion to complete nop ; Wait for Y conversion to complete movfw ADRES ; get Y tilt movwf YTilt ; save Y tilt movlw PwrOff ; turn off tilt sensor power movwf PORTA bcf ADCON0,ADON ; turn off the A/D converter module return ; ; ChkT45 See if tilted to 45 degrees ; ; Returns NC if not tilted right, ; returns C if tilted right. ; ; We want: ; (XTilt - YTilt) > DTLim45 (h'18') ; XTilt > XTLim45 (h'8C' ~+0.65g) ; YTilt < YTLim45 (h'74' ~-0.65g) ; ChkT45 movfw YTilt subwf XTilt,w movwf DTilt ; calc XTilt - YTilt -> DTilt movfw DTLim45 subwf DTilt,w ; D tilt minus D lim, pos is good tilt skpc ; NC is bad goto CT45Don ; bad Y-X tilt movfw XTLim45 subwf XTilt,w ; X tilt minus X lim, pos is good tilt skpc ; NC is bad goto CT45Don ; bad X tilt movfw YTilt subwf YTLim45,w ; Y lim minus Y tilt, pos is good tilt CT45Don return ; NC is bad, C is good end ; end of file