' ' Model 11d ' $Id: sn8-12.bas,v 1.5 2002/11/13 01:13:49 tomj Exp tomj $ ' Nixie Clock using Model 01 rev. B CPU/IO card and Nixie daughterboard. ' Funny toaster cabinets, s/n 8 - 12. ' ' 4-digit clock with alarm and snooze. ' ' INPUTS: all input switches are multiplexed onto one input ' pin using SR C. ' TIME display (rotary switch) ' DATE display (rotary switch) ' ALARM TIME display (rotary switch) ' SET ' SEC/YEAR ' SNOOZE ' ALARM enable ' ' The RTC chip stores year as 00 - 99, we display as ' 1952 - 2051; since 2000 is a leap year it works ' just fine. 1952 is the year Nixies were introduced ' by Burroughs Corp. ' ' SET MODES: ' If SET is pressed on power up, display mode is selected: ' 12 hour, blinking colon ' 24 hour, blinking colon ' 12 hour, non-blinking colon ' 24 hour, non-blinking colon ' ' When SET is pressed again, clock enters main loop, ' displaying time until SET is pressed. ' ' ALARM switch: when on the main display loop compares ' time against alarm time, and when equal sets the 'alarmed' ' flag which causes the bell to be rung once per second. ' When off, 'alarmed' is cleared and the alarm time is ' retrieved from EEPROM (see SNOOZE). ' ' SNOOZE momentary: assuming 'alarmed' is true, ' 10 minutes is added to the alarm time and 'alarmed' ' is cleared, therefore stopping the bell. This modifies ' the alarm time setting in RAM. ' ' (An undesirable side-effect is that the ALARM display ' shows the snoozed value, not necessarily the EEPROM set ' value, while alarm is enabled and in snooze period.) ' ' ' When SET is pressed, CPU goes into the set loop. ' The RTC runs, but the display is frozen. Digits ' blink in SET mode. ' ' First press: ' Displays time or date, as indicated by switches. ' Turning CHANGE changes whatever is displayed. ' Pressing SEC/YEAR when time displayed simply ' displays 00:00, since seconds is always ' zeroed on set. NOTE holding SNOOZE on causes ' all values to increment by 30, instead of 1. ' ' Second press: ' Returns to normal operation. ' ' The in-memory values are changed; the RTC chip ' is written to on the second press of SET. If no ' value changes, the clock is not set; this way, if ' SET is pressed accidentally but the knob not turned, ' nothing changes. ' ' NOTE THAT THERE ARE NO MULTIPLICATIONS NOR DIVISIONS ' IN THIS CODE! The library routines are large, slow, ' and not necessary. ' ' REVISION HISTORY ' ' 17 Dec 2002 ' Put back code to check for delta to avoid writing to ' nixies if values not changed. Constant updating is the source ' of the fuzzy lighting of random digits (often "3") caused ' by cathodes pulled to ground (for microseconds). The amount ' of discharge depends on nixie ageing and even the driver ' chip manufacturer -- presumably ionization time shortens when ' tubes warm up. ' ' 8 June 2002 ' Moved a lot of code to assembly, re-wrote nixie display, ' replaced readrot(), added alarm code, added switch-scan ' code (instead of switch::portbit), vastly speeded ' up things. Fixed up overall structure. Almost amounts ' to a rewrite. ' ' 28 Mar 2002 ' Copied from sn4-7.bas. See for rev history. ' ' Copyright Tom Jennings 2002. ' ' e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e ' ' B0 is saved to EEPROM and restored on power-up. Tmode and cblink ' are remembered modes, but the rest just go along for the ride. ' Many flags are position dependent. ' ' b ' flags: l a ' a l c ' c n a d t b ' o k r o m l ' l n m s o i ' o p i e e d n ' n m x d t e k ' ' 0 0 0 0 0 0 1 0 ' 1 1 TIMEBITS ' 0 0 hh mm 12 hr time, blinking colon ' 0 1 hh mm 12 hr, no blink ' 1 0 hh mm 24 hr time, blink ' 1 1 hh mm 24 hr, no blink ' 1 1 HWBITS symbol flags= b0 symbol cblink= bit0 ' 1 == disable colon blink symbol tmode= bit1 ' 1 == 24 hour mode symbol doset= bit2 ' 1 == write new time/date to RTC symbol alarmed= bit3 ' 1 == alarm tripped symbol blanknix= bit4 ' 1 == blank nixies symbol TIMEBITS= %00000011 ' isolate TIME mode bits symbol TIMEBITM= %11111100 ' isolate all but TIME mode bits symbol F_ALARMED= 3 ' bit number symbol F_BLANK= 4 ' bit number ' ' colon, pm must be MS, 2nd MS bits; they're ORed into SRA output directly. symbol pm= bit6 ' 1 == PM else AM symbol colon= bit7 ' 1 == colons lit symbol HWBITS= %11000000 ' mask these two hardware bits symbol NHWBITS= %00111111 ' zero hardware bits ' ' Function switches are read with a single input (SW_IN), each switch ' far side tied to a SR C output bit. Selecting an output bit pulls ' down the switch which if closed pulls down SW_IN. ' These are bit numbers for btfss. ' symbol SW_TIME= 0 ' display TIME symbol SW_DATE= 1 ' display DATE symbol SW_ATIME= 2 ' display ALARM TIME symbol ROTSW= 7 ' sw 0-ROTSW is a rotary switch symbol SW_SNOOZE= 3 ' SNOOZE switch symbol SW_ALARM= 4 ' ALARM enable symbol SW_SECYR= 5 ' SEC/YEAR display symbol SW_SET= 6 ' SET switch symbol switches= b1 ' all switches go in B1. symbol timesw= bit8 ' display TIME symbol datesw= bit9 ' display DATE symbol atimesw= bit10 ' display ALARM TIME symbol snoozesw= bit11 ' SNOOZE switch symbol alarmsw= bit12 ' ALARM enable symbol secyrsw= bit13 ' SEC/YEAR display symbol setsw= bit14 ' SET switch symbol swval= b9 ' rotary switch index value symbol TIMEONLY= 0 ' swval value for time display symbol i= b2 symbol j= b3 symbol n= b4 symbol k1= b5 symbol k2= b6 symbol l= b7 symbol timer= b8 ' SET mode flash timer symbol BLINKTIME= 250 ' loop value ' Alarm behavior. symbol ALARM_WIN= 30 ' alarm triggers on seconds 0 - N symbol SNOOZE_INC= 10 ' SNOOZE advances this many minutes symbol RINGSEC= 3 ' ring bell every 4 seconds symbol rotR= b12 ' rotary encoder persistent storage symbol ROTSLOW= 1 ' normal rotI increment, symbol ROTFAST= 30 ' fast rotI increment, ' ' Values last shifted out; no output if values don't ' change. This avoids the fuzzy blur during blinking. symbol lastA= b13 ' rotary encoder increment (1, 10) symbol lastB= b14 ' rotary encoder direction (0, 255) ' ' Time & date data as used by the RTC routines. See getclock() and setclock() ' code for data handling details (eg. packing/unpacking of BCD for the compiler ' RTC routines). These always contain the last-read RTC values. ' symbol GSARRAY= B15 ' address of first array element. symbol year= b15 ' (data referenced by these) symbol month= b16 symbol day= b17 symbol daywk= b18 symbol hour= b19 ' see note below symbol minute= b20 symbol second= b21 ' These two are the same order as hour and minute, above, ' so we can use the same efficient increment routine, incT and incA. ' symbol ahour= b10 ' alarm HH STORED IN EEPROM symbol aminute= b11 ' alarm MM STORED IN EEPROM ' ' Pin assignments (eg. 3 = pin3). Very annoying that the compiler doesn't ' syntactically allow 'pin 3' or 'pin SD'. Inputs are pulled up, and are ' logically ON when pulled to ground. ' symbol SW_IN= 7 ' one side all input switches tie here symbol ROTA= 6 ' Rotary encoder bit A, pin 6 symbol ROTB= 5 ' Rotary encoder bit B, pin 5 symbol BELL= 4 ' bell solenoid symbol SRC= 3 ' input switch scan selector symbol SRB= 2 ' SR B clock (MM, DD) symbol SRA= 1 ' SR A clock (HH, MO) symbol SD= 0 ' Serial Data pin number symbol SDpin= pin0 ' Serial Data to shift reg ' ' Invalid codes to blank the display. ' symbol INV_MS= 15 ' invalid code; blanks display symbol INV_LS= 255 ' invalid code; blanks display ' ' Code starts here. - - - - - - - - - - - - - - - - - - - - - - - - - ' main: dirs= %00011111 ' 1=output 0=input pins= 0 call POST ' init & test hardware, read 0, flags ' global settings from EEPROM. call readsetsw ' if SET pressed, if N = 0 then run ' ' If SET was pressed during POST, then go into mode-set. We ' stay in this mode until SET is pressed again. Note that this ' ignores all switches but SET and CHANGE. ' mode: hour= 23 : minute= 59 ' sample data for display, swval= TIMEONLY ' force TIME display, gosub setupTDA ' setup time display, ' gosub blink ' do blinking, colon= blanknix + 1 | cblink ' (usually calc'd on second) call outNixies ' display time/date/alarm, call readrot ' read the encoder, i= flags + n & TIMEBITS ' next/prev time mode combo, flags= flags & TIMEBITM | i ' set new mode combo, call readsetsw ' check switches, if N = 0 then mode ' if SET is pressed, write 0, flags ' write mode to EEPROM, exit. ' ' Load alarm time from EEPROM, then run the display code. ' run: read 1, ahour ' set alarm time now read 2, aminute ' from EEPROM, alarmed= 0 ' disable alarm. run1: call getclock ' read RTC chip, call readsetsw ' check switches, if N = 1 then set ' go change if SET pressed, gosub setupTDA ' setup for display blanknix= 0 ' not blinking now call outNixies ' time/date/alarm, call ckalarm ' run alarm logic, if alarmsw = 0 then run ' reset alarm time, if alarmed = 0 then run1 ' if alarm tripped, call ring ' ring bell, goto run1 ' don't reload atime ' ' SET switch was pressed; increment time or date or alarm ' until SET pressed again, when we return to boring old run ' mode. ' set: read 1, ahour ' restore actual settings read 2, aminute ' SNOOZE changes ' --------------------------------------------------------------------- ' time is money time is money time is money time is money time is money ' ' This is the most time-sensitive code, since reading the rotary ' encoder is sensitive to missed reads. ' min max uS set1: call readrot ' 18 35 read the encoder, if n = 0 then set2 ' gosub set3 ' 40 400 change values, doset= 1 ' time changed, second= 0 ' reflects RTC chip reality set2: 'gosub blink ' 75 125 blink display, gosub setupTDA ' 500 500 setup display, call outNixies ' 180 210 do display call readsetsw ' 120 120 check switches, if N = 0 then set1 ' loop until SET pressed. if doset = 0 then run ' 'run' if no changes, else... ' time is money time is money time is money time is money time is money ' --------------------------------------------------------------------- call setclock ' update RTC, write 1, ahour ' remember alarm settings, write 2, aminute goto run ' run w/new settings. ' ' Perform set functions. doset is set if anything changes. ' N and I are set by readrot() in calling code. ' set3: j= n & 8 + swval + swval + secyrsw ' (readrot() N returns 0 or 255) ' J is 0=time+, 1=seconds+, 2=date+, 3=year+, 4=alarm+ 5=alarm+ ' 8=time-, 9=seconds-, 10=date-, 11=year-, 12=alarm- 13=alarm- ' i= rotI ' set increment from readrot() gosub luday ' set N= days in month, branch j, (incT, ret, incD, incY, incA, incA, ret, ret, decT, ret, decD, decY, decA, decA) setret: return incD: day= day + i : if day <= n then setret ' if > days in the month, day= 1 : month= month + 1 ' first day, next month, if month < 13 then setret ' wrap dec to jan month= 1 : goto setret decD: day= day - i ' decrement day, if day = 0 then sd1 ' (eg. if day=1 - 1) if day < 32 then setret ' (eg. if day=1 - 10) sd1: month= month - 1 ' previous month, if month = 0 then sd1a ' 1 <= N <= 12 if month < 13 then sd2 sd1a: month= 12 : ' wrap jan to dec, sd2: day= n ' last day of month, goto setret ' ' Return the number of days in the current month in N. ' luday: lookup month, (0,31,28,31,30,31,30,31,31,30,31,30,31), n ' find max day, k1= year & 3 + month : if k1 <> 2 then ret ' if (year % 4) && (month=2) n= 29 ' 29 days in Feb. ret: return ' ' Blink the display. ' 'blink: timer= timer - 1 ' tick tock... ' if timer > 0 then bk ' if we reach zero, ' timer= BLINKTIME ' blanknix= blanknix + 1 ' toggle blink bit 'bk: return ' Set up for time/date/alarm display: sets the two Nixie ' display values (as decimal) in K1 (left Nixie, A) and K2 ' (right Nixie, B); colon=1 for time else 0. ' Modifies I, J, K, flags. ' setupTDA: flags= flags & NHWBITS ' clear PM, COLON, j= swval + swval + secyrsw ' (0, 1, 2) --> (0, 2, 4) + 1 branch j, (d0, d1, d2, d3, d4, d4) d0: k1= hour : k2= minute : goto d5 ' HH MM d1: k1= 0 : k2= second : goto d6 ' 00 SS d2: k1= month : k2= day : goto d7 ' DY MO d3: k1= 19 : k2= year + 52 ' 19 YY, else if k2 < 100 then d7 k2= k2 - 100 : k1= 20 : goto d7 ' 20 YY d4: k1= ahour : k2= aminute ' ALARM HH MM gosub d5 ' fix AM/PM, colon= 1 : goto d7 ' force colon ON (no blink) ' ' Do some time-display stuff. ' d5: if tmode = 0 then d6 ' if 12-hour mode, k1= k1 + 1 ' hours run 1-12 not 0-23 if k1 < 13 then d6 ' if PM pm= 1 ' turn on NE-2, k1= k1 - 12 ' adjust d6: colon= second + 1 | cblink ' assume TIME, set colon d7: ' return timer= timer - 1 ' tick tock... if timer > 0 then d8 ' if we reach zero, timer= BLINKTIME blanknix= blanknix + 1 ' toggle blink bit d8: return END 'BASIC asm ; ; Execute alarm logic. The result is flag 'alarmed', which ; if true means the bell should now be rung. ; ; Input to this routine is current time, alarm time, and ; ALARM and SNOOZE switches. ; ; This consists of two sections; the test for entering/leaving ; alarm state, and the snooze code. ; ; The first section determines when to enter or leave ; the alarm state; ON when alarm switch is on and time ; equals alarm time, OFF when the alarm switch is off. ; A slight kludge is used to prevent re-triggering when ; the alarm switch is turned off and on when time == alarm time; ; alarm trips only when seconds < 3. ; ; The second section handles snooze logic, and requires that ; we be in the alarmed state. It simply clears the alarm ; state and advances alarm time by a predetermined amount, ; so that the alarm will reoccur later, eg. snooze. ; Modifies I, flags, alarm time. ; Set alarmed state if ALARM switch on and we reached alarm time. _ckalarm btfss _switches, _SW_ALARM ; if (ALARM switch ON goto _ca1 movf _minute, w ; && (minute == alarm minute) xorwf _aminute, w btfss Z goto _ca2 ; /* is not alarm time */ movf _hour, w xorwf _ahour, w ; && (hour == alarm hour) btfss Z goto _ca2 movlw _ALARM_WIN subwf _second, w ; && (second <= 3)) { btfss C bsf _flags, _F_ALARMED ; we are now alarmed; goto _ca2 _ca1 bcf _flags, _F_ALARMED ; } else { alarmsw off } ; Defer alarm state if SNOOZE switch is pressed. _ca2 btfss _flags, _F_ALARMED ; if ( alarmed return btfss _switches, _SW_SNOOZE ; && snooze pressed) { return bcf _flags, _F_ALARMED ; stop (defer) ringing, movf _minute, w ; set alarm time movwf _aminute ; to now + SNOOZETIME movf _hour, w movwf _ahour movlw _SNOOZE_INC movwf _I goto _incA ; } ; ; Ring the alarm bell. This is a little kludgey: this is ; probably called multiple times per second, and each call ; makes clapper strike bell. To prevent endless ringing, ; we ring only when the current seconds modulo RINGSEC is ; 0. RINGSEC must of course be a power of two. ; Modifies J. ; _ring movlw _RINGSEC andwf _second, w ; if second % RINGSEC == 0 btfss Z return movlw 3 ; N clangs per call, movwf _J _rg1 bsf portB, _BELL ; set clapper to bell, call _P256msec ; for 1/4 second, bcf portB, _BELL ; release, call _P256msec decfsz _J goto _rg1 return ; ; Convert canonical time & date to packed BCD, write to the RTC; ; convert back to canonical. (Leaves the data consistently usable ; to other routines at little processing cost). ; _setclock call _setgs ; set up, _sc1 call _tBCD ; [i]= toBCD([i]) incf FSR ; ++i, decfsz _L goto _sc1 ; loop... call _CLOCKSET ; set the clock, goto _gc0 ; return data to canonical. ; ; Set up for getclock()/setclock(). ; _setgs movlw _GSARRAY ; first address, movwf FSR ; FSR= movlw 7 movwf _L ; L= loop byte count, bcf RP0 return ; ; Read the clock chip (packed BCD) and convert to canonical, in-place. ; Also has an entry point for converting back to canonical, jump to from ; setclock(). ; Modifies K, L. ; _getclock call _CLOCKGET ; read BCD time, ; Entry point from setclock(). _gc0 call _setgs ; set up first, _gc1 call _fBCD ; [i]= fBCD([i]); incf FSR ; ++i; decfsz _L goto _gc1 ; loop... goto done ; ; Convert packed BCD to canonical. Address of operand in W. Modifies J. ; Entry point fBCD is for loops that explicitly set FSR. ; ; l= k / 16 * 10 : k= k & $0f : k= k + l ; ; K / 16 * 10 is done with three right-shifts and an intermediate ; save; eg. N * 10 is (N * 8) + (N * 2), and we already have ; N * 16; so one shift-right makes N * 8, two more make N * 2. ; _fromBCD movwf FSR ; point to var, _fBCD movf INDF, w ; get BCD value, movwf _J ; save in J, andlw 0f0h ; upper digit only, movwf INDF ; in reg, rrf INDF ; make div 8 movf INDF, w ; save in W, rrf INDF rrf INDF ; div 2 addwf INDF ; the sum in reg, movf _J, w ; get lower digit, andlw 0fh ; mask off lower digit, addwf INDF ; add together. return ; ; Convert decimal value in K to packed BCD in K. Address of ; operand in W. Modifies J. ; Entry point tBCD is for loops that explicitly set FSR. ; _toBCD movwf FSR ; point to var, _tBCD clrf _J ; clear quotient, movlw 10 _toB1 incf _J ; incr quotient, subwf INDF ; k= k + (-10) btfsc status, 0 ; loop until underflow, goto _toB1 _toB2 addwf INDF ; undo last subtract, decf _J ; uncount last iteration, swapf _J, w ; put (K / 10) in upper nybble, iorwf INDF, f ; add in remainder. return ; ; These are time (minute:hour) and single-value increment/decrement ; routines. They all share a common _incX/_decX routine. J is ; used as the inc/dec bound, and I the inc/dec amount. ; I is set by the calling routine (but modified to 1 by the ; time routines). i; ; Modifies I, J. ; _incA movlw 60 movwf _J movlw _aminute goto _incT1 _incT movlw 60 movwf _J ; set bound, movlw _minute ; point to minute, _incT1 call _incX ; increment it, btfss C ; if < bound, return ; done. movlw 24 ; minute wrapped to 0, movwf _J ; so increment hour, movlw 1 movwf _I ; increment is 1, decf FSR ; point to hour goto _incX1 ; ; Increment year. ; _incY movlw 100 movwf _J ; set bound, movlw _year goto _incX ; ; Increment register (W) by I, wrap to 0 if we => bound J. ; _incX movwf FSR ; point to minute _incX1 movf _I, w addwf INDF, f ; minute + I --> minute movf _J, w ; bound subwf INDF, w ; minute - K --> W btfss C ; Cy= minute > K return movlw _J ; minute > 60, subwf INDF, f ; fix wraparound, bsf status, C ; set carry flag, return ; ; Same as above, but decrement. ; _decA movlw 60 movwf _J movlw _aminute goto _decT1 _decT movlw 60 movwf _J ; set minutes bound, movlw _minute _decT1 call _decX ; decrement it, btfss C ; if no underflow, return ; done. movlw 24 ; else decrement hour, movwf _J ; set bound, movlw 1 movwf _I ; set increment, decf FSR ; point to hour, goto _decX1 ; ; Subtract I from register (W), add J if it underflows. ; _decX movwf FSR _decX1 movf _I, w subwf INDF, f ; minute - I --> minute, btfsc C ; Cy= (INDF >= 0), return movlw _J addwf INDF, f ; minute += 60, bcf status, C return _decY movlw 100 movwf _J movlw _year goto _decX ; ; Output decimal value K1 to the MS two Nixies (hour/month), converting to ; packed BCD first (outABCD). The colon and PM bit are ORed in before ; output. Modifies J, K. ; ; NOTE FOR THIS REVISION ONLY -- Bits B6 and B7 are inverted, ; because we have DS2003's installed in their sockets for ; port C to scan the switches; B6 and B7 would normally have ; shorting jumpers. (LS nixies) ; _outNixies ; write to left pair of Nixies movlw _K1 ; point to K, call _toBCD ; convert K to BCD, movlw _INV_MS btfsc _flags, _F_BLANK ; if to blank the display, iorwf _K1 ; set invalid code, _outA movf _flags, w ; get colon, AM/PM bits, andlw _HWBITS ; strip clean, iorwf _K1, f ; OR in BCD bits, movf _K1, w ; value --> W subwf _lastA, w ; if zero (same) btfsc status, 2 ; no change, goto _outBBCD ; don't write nixies, movf _K1, w ; else it's changed, movwf _lastA ; update "lastA" movlw 8 movwf _J ; set bit loop counter, _oa1 bcf portB, _SD ; set data bit= 0 rlf _K1 ; move MSB to carry, btfsc status, 0 ; if carry set (MSB == 1) bsf portB, _SD ; assert a 1, bsf portB, _SRA ; clock data bit out, bcf portB, _SRA decfsz _J ; repeat. goto _oa1 _outBBCD ; write to right pair of Nixies movlw _K2 ; point to K, call _toBCD ; convert K to BCD, _outB movlw _INV_LS btfsc _flags, _F_BLANK ; if to blank the display, movwf _K2 ; set invalid code, ; sn8-12 invert B6, B7 as described above. -------------------- movf _K2, w xorlw 192 ; invert MSBs, ; sn8-12 ------------------------------------------------------ movf _K2, w ; value --> W subwf _lastB, w ; if zero (same) btfsc status, 2 ; no change, goto done ; don't write nixies, movf _K2, w ; else it's changed, movwf _lastB ; update "lastB" movlw 8 movwf _J ; set bit counter, _ob1 bcf portB, _SD ; set data bit= 0 rlf _K2 ; move MSB to carry, btfsc status, 0 ; if carry set (MSB == 1) bsf portB, _SD ; assert a 1, bsf portB, _SRB ; clock data bit out, bcf portB, _SRB decfsz _J ; repeat. goto _ob1 goto done ; ; Various pause routines. _P10msec movlw 10 ; 10 mS goto pause@0W _P1msec movlw 1 ; 1 mS goto pause@0W _P256msec movlw 1 movwf R0+1 ; for 256 mS movlw 0 ; (01 00 = 256) goto pause@XW ; ; Read the switches, and if SET is pressed, wait for it to go off ; and return N=1, otherwise just return N=0. ; _readsetsw clrf _N ; assume not set, _sw1 call _readsw btfss _switches, _SW_SET ; if SET is off, return ; exit, movlw 1 movwf _N ; is set, flag it, goto _sw1 ; wait for it to release. ; ; Read up to 8 switches tied to SR C outputs (switch contacts) ; with the pole tied to pin7, and store the result in 'switches'. ; Switches should be debounced in hardware, there is a 1mS delay to ; allow the debounce RC to charge/discharge. This polls the switches ; once per call, assumes this routine is called repeatedly in loops. ; The results of the first call are garbage, since this routine ; relies on the previous call to have initialized the SR. Modifies J. ; _readsw movlw 1 ; init loop ctr/switch value J movwf _J clrf _switches ; assume none closed, ; bsf 3, 5 ; set RP0 in STATUS (bank0) bsf portB, _SD ; clock a 1 into LSB 1st time, _raw1 bsf portB, _SRC ; clock bit over, bcf portB, _SRC bcf portB, _SD ; shift in 0's ; This makes the set loop too slow, and we miss rotary encoder ; state changes! Now if it were interrupt driven... ; call _P1msec ; brief delay for C on sw line, btfsc portB, _SW_IN ; if switch is ON (pulls to gnd) goto _raw2 movf _J, w ; get test bit, iorwf _switches, f ; set "switch" ON, _raw2 bcf status, 0 ; clear carry before shift in rlf _J ; move 'em over, btfss status, 0 ; until bit moves to carry, goto _raw1 ; repeat; ; ; Now turn the rotary switch bits into an index, eg. position number. ; For a 3-pos switch, values run 1, 2, 4; convert this to 0, 1, 2 ; by finding the first bit set. For failsafe, we shift in 1's from the left ; to ensure termination. ; movf _switches, w ; get switches read, andlw _ROTSW ; mask off our bits, movwf _J ; save for test, clrf _swval ; set initial value, _raw3 btfsc _J, 0 ; if switch on, goto done ; clear WDT, return val. bsf _J, 7 ; (failsafe) rrf _J ; shift to next bit, incf _swval ; next value, goto _raw3 ; keep looking. ; ; Begin POST. This basically runs the Nixies through a nice ; sequence: ; ; 00:00, 11:11, 22:22, ; 23:33, 23:44, 23:55, ; 23:56, 23:57, 23:58, 23:59. ; _post clrf _minute clrf _hour movlw 11 movwf _N ; N= minute increment, movwf _I ; I= hour increment, movlw 2 ; W= interations, call _postdr ; display 00:00, 11:11, (now 22:22) movlw 1 ; do once, movwf _I ; I= 1 (minute += 11; hour += 1) call _postdr ; display 22:22, (now 23:33) clrf _I ; I= 0 (minute += 11; hour += 0) movlw 2 ; do twice, call _postdr ; display 23:33 .. 23:44 (now 23:55) movlw 1 movwf _N ; N= 1 (minute +=1; hour += 0) movlw 5 ; five times, call _postdr ; display 23:55 .. 23:59, (now 23:60) incf _hour clrf _minute ; display 24:00 movlw 1 ; 1 iteration, fall through. call _postdr call _P256msec ; extra pause at 24:00, call _readsw ; clear switch SR goto _readrot ; init rotary encoder ; ; Display HH:MM, pause, increment hour and minute by N and I, respectively. ; Enter with W=iteration count. ; _postdr movwf _L ; set loop count, _pd1 movf _hour, w movwf _K1 movf _minute, w movwf _K2 call _outNixies ; display HH:MM, movlw 01h movwf R0+1 ; pause for 500 mS movlw 0f4h ; (01 f4 = 500) call pause@XW movf _I, w ; increment, addwf _hour movf _N, w addwf _minute, f decfsz _L ; loop. goto _pd1 return ; ; Read the rotary encoder, returns: ; ; N I ; NO MOTION 0 x ; CCW -1 1 or 10 (see note) ; CW 1 1 or 10 (see note) ; ; NOTE: If there was motion, the value returned in I ; is 10 if the SNOOZE button is pressed, else 1. This ; cobbles up a fast mode for setting the time and ; date. ; ; This uses the algorithm found in TechTools' PIC app notes: ; ; Upon every polling call of this routine, save the current ; (R) and previous (pR) value of the rotary encoder AB ; bits. ; ; XOR the LS bit of pR with the MS bit of R. The remaining ; bit will be 1 for the CW sequence and 0 for the CCW sequence. ; ; ; CW CCW ; ; AB AB ; 01 10 ; 00 00 ; 10 01 ; 11 11 (resting state) ; ; or ... ; ; a b c d e f g h i j k l ... ; CW 01 00 10 11 01 00 10 11 01 00 10 11 ... ; CCW 10 00 01 11 10 00 01 11 10 00 01 11 ... ; ; Looking at any pair of reads (ab, bc, fg, gh, etc), ; adjacent bits will always be different for CW, ; always the same for CCW. ; ; PIC registers power up as 0's, so the first call ; inits the data and returns garbage. ; ; NOTE: The encoder I use doesn't have a detent ; per-phase; in one detent the encoder goes through ; all four phases. Therefore it never stops for extended ; (manually controlled) periods at any state except the one ; marked "resting state", above. This is the purpose of ; the check at _rxxxx for absolute state == 00000011; ; only when this state is seen is a value returned. ; If your encoder (most!) has detents per-phase, simply ; remove the indicated code. ; _readrot clrf _N ; assume no motion, ; ; Read and normalize the two rotary encoder bits, leave in J ("new read"). ; swapf portB, w ; read port, shift R 4 places, movwf _J ; work here, rrf _J ; normalize (note Cy may be set) movlw 3 ; mask for our bits, andwf _J ; mask off bits. ; ; Exit now if the encoder hasn't moved since the last call. ; movf _J, w ; new read in W, xorwf _rotR, w ; check against R, btfsc Z goto done ; same, no change, exit. ; -------------- four-phase-per-detent encoders only. --------------- movlw 3 andwf _J, w ; test for phase 0000 0000, btfss Z goto _rrs ; not until 4th phase. ; -------------- four-phase-per-detent encoders only. --------------- ; ; Determine rotation direction. XOR the MS bit of the current read ; with the LS bit of the previous read. A result of 1 is CW, 0 ; is CCW. ; rlf _rotR ; move LSB of previous left, movf _J, w ; current read to W, xorwf _rotR ; test direction (bit1), movlw 1 ; (value for CW) btfsc _rotR, 1 ; test bit1, movlw -1 ; (value for CCW) movwf _N ; return direction, ; bsf portB, _BELL ; "keyclick" ; bcf portB, _BELL _rrs movf _J, w ; save new read movwf _rotR ; as previous for next call, ; ; If SNOOZE is pressed, then make the increment 10 instead of 1. ; movlw _ROTSLOW ; assume increment= 1, btfsc _switches, _SW_SNOOZE ; if snooze pressed, movlw _ROTFAST ; then it's 10, movwf _I ; set increment. goto done endasm