' ' Nixie Clock using Model 01 CPU/IO card and Nixie daughterboard. ' Will display YY/MM/DD or HH:MM:SS on the six nixie displays. ' Various global modes (eg. 12/24 hour display; date order) are ' determined by power-up switch settings and remembered in EEPROM. ' ' Tom Jennings ' mru 25 July 98 ' written 17 July 98 ' ' For unrevised Model 01 card (prototype) card, built into Model 11a. ' AM/PM support missing; 24hr only. ' ' Copyright Tom Jennings, 1998. ' ' BUGS: ' ' 29 July 98: still seems to be a problem wrecking RTC settings on ' power up or down. Works much better when RC filter installed on PA4. ' symbol BDELAY= 30 ' loop delay til autorepeat (button()) symbol BREPT= 4 ' loop delay between autorepeat ' 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 ' ' These bit variables control global modes (eg. 24/12 hr mode) and are ' stored in EEPROM and retreived upon power up. New values are set and ' written to EEPROM if specific switch combinations are held on power up. ' symbol flags= b0 ' reserved for bit flags ' All bit flags are stored in B0 to conserve space; not all need to ' be remembered . Also, these two are bit-ordered and are used as an ' integer value in display()! ' ' flags dmode tmode ' 0 0 0 time, 12hr mode, HH MM SS ' 1 0 1 time, 24hr mode, HH MM SS ' 2 1 0 (x) date, MM DD YY (tmode dont-care) ' 3 1 1 (x) date, MM DD YY (tmode dont-care) ' symbol tmode= bit0 ' (0..1) GLOBAL 1 == 24 hour mode symbol dmode= bit1 ' (0..1) LOCAL 1 == date display (else time) symbol BMASK= 1 ' bit mask to strip non-global bits ' ' 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 symbol unused1= b1 ' reserved for bit flags symbol i= b2 symbol n= b3 ' ' The display routine needs to shift bits out to the SRs MS bit first. We ' do so by putting the value to be output in the lower half of a word var, ' shifting left (word= word + word), then using the LS bit of the upper byte. ' This means that the following two variables MUST be on a word boundary and ' sequential. ' symbol k= w2 ' val stored in K ends up in lower byte, k_lower symbol k_lower= b4 ' not used as-is symbol k_cy= b5 ' carry from K after shift symbol j= b6 symbol l= b7 symbol last_SRA= b8 ' cache for display(); symbol last_SRB= b9 ' last-written contents of SRx, symbol last_SRC= b10 ' see display() code. symbol bvar1= b11 ' temp for button command symbol bvar2= b12 ' temp for button command symbol bvar3= b13 ' temp for button command symbol unused2= b14 ' ' Time & date data as used by the RTC routines. See the 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 year= b15 symbol month= b16 symbol day= b17 symbol daywk= b18 symbol hour= b19 symbol minute= b20 symbol second= b21 ' ' 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_SET= SET switch is on PA4 ' (0..1) 0 == RUN/SET in SET position symbol SW_DATE= pin7 ' (0..1) 0 == TIME/DATE in DATE position symbol SW_HY= pin6 ' (0..1) 0 == INCREMENT HOUR(YEAR) switch pressed symbol BIT_HY= 6 ' number of the HOUR(YEAR) pin (aargh) symbol SW_MM= pin5 ' (0..1) 0 == INCREMENT MINS(MONTH) switch pressed symbol BIT_MM= 5 ' number of the MIN(MONTH) pin symbol SW_SD= pin4 ' (0..1) 0 == INCREMENT SEC(DAY) switch pressed symbol BIT_SD= 4 ' number of the SEC(DAY) pin ' ' Data is written to the shift registers by asserting data on the SD pin, and clocking ' the appropriate SR clock pin. 8 bits per shift register, MS bit output first. ' symbol SDpin= pin0 ' pin number, Serial Data to shift reg symbol SRC= 1 ' SR C clock symbol SRB= 2 ' SR B clock symbol SRA= 3 ' SR A clock ' ' Startup code starts here. - - - - - - - - - - - - - - - - - - - - - - - - - ' main: dirs= %00001111 ' MS bits inputs, rest outputs. pins= 0 ' deassert device selects peek 133, n : n= n | 16 : poke 5, n ' make PA4 an input last_SRA= 255 : last_SRB= 255 ' invalidate display() cache last_SRC= 255 for i= 0 to 99 step 11 ' test the Nixies -- run hour= i : minute= i : second= i ' through all the digits, gosub display pause 50 next i ' ' Load global settings from EEPROM; if certain switches are pressed at power up, ' change global settings. ' read 0, b0 ' read global settings from EEPROM flags= flags & BMASK ' strip unwanted bits if SW_HY = 1 then puz ' if INCR HOUR(YEAR) pressed, tmode= tmode + 1 ' toggle 12/24 hour mode, write 0, b0 ' write out new setting hour= 12 * tmode + 12 ' set HH to 12 or 24, minute= 0 : second= 0 gosub display ' display new setting, pause 2000 ' pause to show new setting puz: flags= 1 ' set tmode only ' End of startup, beginning of runtime. - - - - - - - - - - - - - - - - - - - - - - - - - - - - ' ' The main loop here is the basic clock function. TIME/DATE determines which three ' bytes are displayed. RUN/SWITCH must be in the SET switch for a long time before ' we enter SET mode; this not only debounces the switch, it prevents spurious writes ' on power down where the switch pullups go down before CPU power. ' a1: gosub getclock ' read RTC data, unpack and copy, dmode= SW_DATE + 1 ' read TIME/DATE switch once per loop, gosub display ' display current values, pause 200 ' don't hit RTC too often peek 5, n ' check RUN/SET (PA4) switch n= n & 16 : if n = 16 then a1 ' branch if RUN/SET in RUN position. pause 200 ' long delay if SET detected bvar1= 0 : bvar2= 0 : bvar3= 0 ' clear SET function switch vars a2: peek 5, n n= n & 16 : if n = 16 then a1 ' branch if RUN/SET not SET position dmode= SW_DATE + 1 ' read TIME/DATE switch once per loop, gosub display ' display new or current values ' ' Set mode is pretty simple; pressing INCREMENT HOUR(YEAR), INCREMENT MINUTE(MONTH) ' or INCREMENT SECOND(DAY) increments the time/date field; "SECONDS" clears seconds ' to 0. ' button BIT_HY, 0, BDELAY, BREPT, bvar1, 0, m2 ' if INCREMENT HR/YEAR pressed, if dmode = 0 then m1a ' and DATE mode, year= year + 1 // 100 ' increment YEAR (0..99) goto mw m1a: hour= hour + 1 // 24 ' else increment HOUR (0 - 23) goto mw m2: button BIT_MM, 0, BDELAY, BREPT, bvar2, 0, m3 ' if INCREMENT MIN/MON pressed, if dmode = 0 then m2a month= month + 1 : if month < 13 then mw ' increment MONTH (1..12) month= 1 : goto mw m2a: minute= minute + 1 // 60 ' increment MINUTE (0..59) goto mw m3: button BIT_SD, 0, BDELAY, BREPT, bvar3, 0, mz ' if INCREMENT SEC/DAY pressed, if dmode = 0 then m3b ' increment DAY (1..N) lookup month, (0,31,28,31,30,31,30,31,31,30,31,30,31), n ' find N i= year // 4 : if i <> 0 then m3a ' if a leap year (2000 is, lucky us) if month <> 2 then m3a ' and Feb (MM == 2) n= 29 ' then 29 days, new bound value m3a: day= day + 1 : if day <= n then mw ' increment DAY (0..N) day= 1 ' wrap around goto mw m3b: second= 0 ' simply clear SECONDS mw: gosub setclock ' write new time or date mz: pause 50 ' make loop execute time more uniform goto a2 ' repeat; we're just a stupid clock. ' ' Write time or date to the six Nixie displays. To avoid flickering while shifting ' bits into the Nixie drivers (since they're always enabled) we only update display ' pairs that have changed. A three-byte cache remembers the last values written. ' Note that dmode, ymode affect the order of display. ' ' Uses private variables J, K, L. ' display: k= hour ' preload for time display branch flags, (d0, d1, d2, d2) ' see 'flags' comment ' 12hr. time, AM, PM. ' ---------- AM ------------- ------------- PM (+128) ----------------------- d0: lookup k, (12,1,2,3,4,5,6,7,8,9,10,11, 12,1,2,3,4,5,6,7,8,9,10,11), k d1: gosub wrleft ' display time as HH MM SS k= minute : gosub wrmid k= second : goto wrright d2: k= month : gosub wrleft ' display date as MM DD YY k= day : gosub wrmid k= year : goto wrright wrleft: if k = last_SRA then wz ' skip if same as what's displayed last_SRA= k : l= SRA : goto shiftout ' remember new value, go write display wrmid: if k = last_SRB then wz last_SRB= k : l= SRB : goto shiftout wrright: if k = last_SRC then wz last_SRC= k : l= SRC ' fall through ' ' Convert canonical value in K to BCD, then shift it out to the shift register whose ' clock bit pin is in L. See the comment on the symbol definition for the hijinks used here. ' shiftout: j= k // 10 : k= k / 10 * 16 + j ' canonical --> packed BCD for j= 1 to 8 ' 8 bits (two BCD digits) k= k + k ' shift MS bit of K to K_cy SDpin = k_cy ' assert MS bit to shift reg data in, pulsout l, 1 ' clock it next j wz: return ' ' These two routines use an ugly peek/poke hack for compactness and speed. They ' originally came from MicroMint documentation; the numeric offsets are magic. ' ' Both use private variables J, K, L. ' ' Read the RTC, convert packed BCD to canonical, and copy HHMMSS or YYMMDD ' into the working data, as specified by the TIME/DATE switch. ' getclock: call clockget ' read the RTC gc1: for j= 27 to 33 ' Bxx register number peek j, k ' read packed-hex l= k / 16 * 10 : k= k & $0f : k= k + l poke j, k next j 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: for j= 27 to 33 ' Bxx register number peek j, k ' read packed-hex l= k / 10 * 16 : k= k // 10 : k= k + l poke j, k next j call clockset ' write to RTC, goto gc1 ' unpack again END