' ' Software only clock, using the TIME function of the M2 parts. ' Has single bit input, single bit output, and time-skew calibration for enhanced accuracy well beyond the native TIME function ' Plays Westminster chimes on the 12,30,45, and hour. And shuts off chimes during sleeping hours. ' ' ' (c)2012 by AS, ' full project details at www.corticalcafe.com ' Code released under the GPLv3 ' ' 'Usage: ' ' To set the clock: ' If you are within 25 minutes AFTER the current hour, then do the following: ' - Power-up the clock ' - Within 10 seconds, press and hold the button, the clock will advance and beep the hour (in 24hr time) ' - Hold the button in until the hour is correct (don't worry about the minutes yet) ' - While looking at an ACCURATE clock (http://www.time.gov/ works well), wait until the next hour is about to come around, ' and press the button at the exact time that the hour changes. ' ' If you are within 25 minutes BEFORE the next hour, then do the following: ' - Power-up the clock ' - Within 10 seconds, press and hold the button, the clock will advance and beep the hour (in 24hr time) ' - Hold the button in until the hour is correct (don't worry about the minutes yet) ' - Advance the hour 1 more time so that the clock indicates that it is set to the NEXT hour. ' - While looking at an ACCURATE clock (http://www.time.gov/ works well), wait until the next hour is about to come around, ' and press the button at the exact time that the hour changes. ' ' Of course, you can also edit the code in the "init" section tfor he correct time and reprogram. ' ' Congratulations, your 1-bit clock is now set to the current time. ' ' ' The chip you are using was never intended to be an accurate time base. It's temporal accuracy will vary by manufacturing, ' voltage, temperature, and probably a few other parameters. My clock drifts about 20 minutes/24 hours. Not very good, as this ' ends ups gaining/losing over an hour each week. In my experience, when driven with 2.6v, a timeskew of +35 seconds/hour corrects the time drift quite nicely. ' But this is not hardcoded, since I imagine each implementation will need a customs calibration. ' That's why his program provides a straightforward method of calibration to greatly improve accuracy. ' After the clock is set, pressing the button at the exact change of hour (as determined by an accurate external clock) ' calibrates this clock's measured time change with the actual time change that occured. From this, the timeskew is calculated ' and appied. ' ' ' To calibrate the clock (must be performed within a singe AM or PM period (eg, between midnight-noon, or noon-midnight) ' - While looking at an ACCURATE clock (http://www.time.gov/ works well), wait until the next hour is about to come around, ' and press the button at the exact time that the hour changes. ' - Wait 1 or more hours. And again, while looking at an ACCURATE clock (http://www.time.gov/ works well), wait until ' the next hour is about to come around, and press the button at the exact time that the hour changes. ' ' ' ' If you're wondering how the setting/calibration process works, here's the concept: ' - If you press the button within 10 seconds of powering up, or within 10 seconds of correcting the time, the clock assumes ' that you want to increment the hour ' - If it's been more than 10 seconds since you powered up (or last pressed the button), then the clock assumes you are ' indicating the top of the hour, and that minutes are now 0. It uses this whole hour button press to reset the time to the ' nearest whole hour. (whether it is up to 29 minutes ahead, or 29 minutes behind the current time). ' - If it's been running for more than 1 hour since you corrected the time, and you haven't crossed a 12 hr boundary ' (noon/midnight) since last calibration, then it will also use this event to calculate and apply a new time skew correction. ' - at any poiint thereafter, you can press the button at the exact hour, and the chip will set the time to the nearest ' hour. If you indicate 2 exact hour settings within a single 12 hour period, it will uses these events to recalculate and ' and adjust the skew. ' - Time skew is corrected at the last second of the last minute of each hour. ' - The clock outputs the current time across the serial interface every 15 seconds, but you don't need to use this ' ' ' ' 08M2 variable table (for convenience) ' b0:b1 b2:b3 b4:b5 b6:b7 b8:b9 b10:b11 b12:b13 b14:b15 b16:b17 b18:b19 b20:b21 b22:b23 b24:b25 b26:b27 ' w0 w1 w2 w3 w4 w5 w6 w7 w8 w9 w10 w11 w12 w13 ' ' ' ' ' History: ' 20121002a - first pass, simple explicit hour/min/second variables ' 20121002b - now using timer to keep track of num seconds since midnight or noon. ' This method will allow easier detection/correction of clock skew and more accuracy. ' Inaccuracy can occur via clock skew (internal timer inaccuracy), and any time the internal timer ' is reset (since it is likely to lose a bit of time at each reset). ' Using a 12 hour timer cycle only requires 2 resets per day, so minimal loss at this period. ' 20121003 - addition of a time-skew parameter which can be used to improve clock accuracy ' 20121004 - added chimes, audio output, so we don't need a PC connected ' pins symbol piezo=C.2 'LED pin (uses TUNE which is fixed to C.2 on the 08M2!) symbol buttn=C.4 'Touch sensor ' variables symbol mode=b0 symbol curHour=b1 symbol curMinute=b2 symbol curSecond=b3 symbol tmpB1=b4 'tmp byte variable symbol tmpB2=b5 'tmp byte variable symbol tmpB3=b6 'tmp byte variable symbol postMidday=b7 '0=AM, 1=PM symbol chimeMode=b8 'chime mode, see constants symbol skewDirection=b9 symbol skewAdded=b10 'flag that indicates whether the skew was already added symbol chimeTempo=b11 symbol timerOffset=w13 symbol secondsElapsed=w12 symbol tmpW1=w11 symbol tmpW2=w10 symbol tmpW3=w9 symbol lastCalibOffset=w8 'time (secs since midnight) of last calibration 'timeskew=time-offsetOfLastCalib+43000*periodsSinceCalib symbol timeSkew=w7 'skew, number of seconds to add or subtract in a 24 hour period 'because Picaxe has no negative numbers, actual value is timeSkew-ONEHALF_32BITS 'constants symbol PAUSE_DELAY=1000 'pause delay (ms) symbol TIMER_SECONDS_RESETVAL=43200 'max timer val before reset (12 hours=43200 seconds) symbol SEC_PER_MIN=60 symbol SEC_PER_HOUR=3600 symbol MIN_PER_HOUR=60 symbol HOUR_PER_DAY=24 symbol SERIAL_TIME_ANNOUNCE=15 'how often to announce the time via the serial port (in seconds, up to 60) 'chime params symbol HOURLY_TONE=96 symbol HOURLY_DURATION=12 '(in 10 MS intervals) symbol HOURLY_PAUSE=200 'chime start and end 'these are listed in seconds from noon/MN, so (1=3600, 2=7200, 3=10800, 4=14400, ' 5=1800, 6=21600, 7=25200, 8=28800, 9=32400, 10=36000, 11=39600, 12=43200) symbol HOURLY_CHIME_AM_ON=25200 symbol HOURLY_CHIME_AM_OFF=43200 symbol HOURLY_CHIME_PM_ON=0 symbol HOURLY_CHIME_PM_OFF=32400 'chime mode symbol CHIME_OFF=0 'chimes off symbol CHIME_AUTO_ON=1 'chime auto, and noisy symbol CHIME_AUTO_OFF=2 'chimes auto, and silent symbol TEMPO_NORMAL=4 symbol TEMPO_FAST=1 'audible alerts symbol ALERT_TONE=32 symbol ALERT_DURATION=12 '(in 10 MS intervals) symbol ALERT_PAUSE=200 '(in MS) init: ' identify progam and version on hardware, allows a serial terminal to identify what code is running on a picaxe chip. ' just open a terminal window, and reboot the picaxe. sertxd("SmplClock20121009", cr,lf) 'name truncated to save bytes! enabletime 'set initial time at powerup (24hr time format) curHour=14 curMinute=09 curSecond=0 chimeMode=CHIME_AUTO_ON timeSkew=38 'no skew at the start, assume the timer is accurate gosub setTime 'set internal time gosub getTime 'TEST ONLY lastCalibOffset=time 'set pullup %10000 'set C.4 as a button (bits are in oder: 76543210) 'main loop mainloop: 'debug pause PAUSE_DELAY gosub getTime 'get time from internal timer tmpB1=curSecond%SERIAL_TIME_ANNOUNCE if tmpB1=0 then gosub serialTime endif '16 bit time variable is not large enough to count to 24 hours in seconds, so 'we need to reset earlier. Easiest is to reset at 12 hours and use AM/PM flag if time>TIMER_SECONDS_RESETVAL then time=0 postMidday=1-postMidday 'toggle AM/PM lastCalibOffset=0 'lastCalibOffset becomes maximal over 12 hour period endif if curMinute=30 and curSecond=00 then 'reset skew flag at half past the hour skewAdded=0 endif 'add/subtract skew at last second of last minute of every hour if curMinute=59 and curSecond=59 and skewAdded=0 then if skewDirection=0 then tmpW1=time+timeSkew time=tmpW1 skewAdded=1 else tmpW1=time-timeSkew time=tmpW1 skewAdded=1 endif endif 'toggle chime mode as appropriate if chimeMode!=CHIME_OFF then if postMidday=0 then if time>=HOURLY_CHIME_AM_ON and time=HOURLY_CHIME_PM_ON and time11 then postMidday=1 curHour=curHour % 12 else postMidday=0 endif time=curHour*SEC_PER_HOUR time=curMinute*SEC_PER_MIN+time time=curSecond+time ' sertxd("setTime: ", #curHour,":", #curMinute,":", #curSecond,", time=", #time,cr,lf) return 'get time to explicit vars getTime: tmpW1=time curHour= tmpW1 / SEC_PER_HOUR 'get num of hours tmpW2=curHour*SEC_PER_HOUR 'then find seconds remaining after hours are removed tmpW1=tmpW1-tmpW2 curMinute= tmpW1 / SEC_PER_MIN 'get num of minutes tmpW2=curMinute*SEC_PER_MIN 'then find seconds remaining after minutes are removed tmpW1=tmpW1-tmpW2 curSecond= tmpW1 'get num of seconds 'adjust for AM/PM, and 12 hour timer limitation if postMidday=1 then curHour=curHour+12 endif ' sertxd("getTime: ", #curHour,":", #curMinute,":", #curSecond,", time=", #time,cr,lf) return 'calculate correction factor (time skew) 'this routine assumes that it is only being called exactly at the whole hour change (minutes=00, eg, 5:00pm). 'It resets the clock to the closest hour, 'And if it's been > 1 hour since the clock was started or the last skew correction, then 'it calculates and applies a new skew accordingly. 'It won't work across 12 hour boundaries, so you must start the clock and adjust the skew entirely within a single AM or PM period calcSkew: ' reset clock to the closest hour gosub getTime sertxd("Current:",cr,lf) gosub serialTime 'for this routine, we need to keep hours within 0-11, if curHour>11 then postMidday=1 curHour=curHour % 12 else postMidday=0 endif tmpW1=time-lastCalibOffset 'get measured elapsed seconds since last calibration 'sertxd("tmpW1=", #tmpW1, ", time=", #time,", lastCalibOffset", #lastCalibOffset,cr,lf) 'if it's been less than a few seconds since last button press, user must want us to increment the hour! if tmpW1 < 7 then curHour=curHour+1 'else, user wants us to reset clock to nearest whole hour else if curMinute < 29 then curMinute=0 else if curMinute >31 then curMinute=0 curHour=curHour+1 if curHour=12 then ' did we advance over noon/midnight? curHour=0 postMidday=1-postMidday 'toggle AM/PM endif endif 'adjust AM/PM if postMidday=1 then curHour=curHour+12 endif curSecond=0 gosub setTime 'and reset corrected time ' gosub alertTone 'indicate that time has been changed 'if it's been < 55 minutes since calibration/powerup, or we've crossed a 12hr boundary, then 'just set the clock, and skip the skew adjustment if tmpW1<3300 or lastCalibOffset=0 then goto endSkew endif 'sertxd("CONT: tmpW1=", #tmpW1, ", time=", #time,", lastCalibOffset", #lastCalibOffset,cr,lf) tmpW3=timeSkew 'save old skew tmpB1=skewDirection 'and direction tmpW2=time-lastCalibOffset 'get actual elapsed seconds since last calibration 'calculate # of seconds that are inaccurate since last calibration, and direction if tmpW1>tmpW2 then timeSkew=tmpW1-tmpW2 skewDirection=1 else timeSkew=tmpW2-tmpW1 skewDirection=0 endif 'now we need to normalize this in order to figure out how many seconds should be added/subtracted during each hour 'this is a ratio: ' SkewPerHour/3600=TotalSkewMeasured/TotalSecondsMeasured 'But the challenge is to calculate this accurately without overflowing 16 bits or losing all resolution 'The key to this is breaking the calculation up into chunks which don't exceed 2^16 or divide down to zero. ' the same ratio can be written as: ' SkewPerHour/60=TotalSkewMeasured/TotalMinutesMeasured 'which is easier to calculate: tmpW2=tmpW2/60 'this computes the total minutes since last calibration timeSkew=timeSkew*60 timeSkew=timeSkew/tmpW2 'timeSkew is now the number of seconds to add/subtract each hour! if timeSkew < 150 then 'add new skew and current skew together, preserve direction if tmpB1=0 and skewDirection=0 then timeSkew=timeSkew+tmpW3 else if tmpB1=1 and skewDirection=1 then timeSkew=timeSkew+tmpW3 else if tmpW3>timeSkew then timeSkew=tmpW3-timeSkew skewDirection=tmpB1 else timeSkew=timeSkew-tmpW3 endif endif else sertxd("Timeskew Err",cr,lf) 'There's a problem... time skew is more than 1 hr/day (150sec/hr), endif gosub alertTone 'indicate that skew has been changed endSkew: gosub getTime sertxd("New:",cr,lf) gosub serialTime lastCalibOffset=time 'reset skewTimer, in case we want to adjust the skew later today gosub chimeHour 'indicate current time return alertTone: sound piezo, (ALERT_TONE, ALERT_DURATION) pause ALERT_PAUSE return 'Westminster chimes chime15: tune piezo, 8,($08,$06,$04,$EB) return chime30: tune piezo, 8,($04,$08,$06,$EB) tune piezo, 8,($04,$06,$08,$C4) return chime45: tune piezo, 8,($08,$04,$06,$EB) tune piezo, 8,($2B,$06,$08,$C4) tune piezo, 8,($08,$06,$04,$EB) return chime00: tune piezo, 8,($04,$08,$06,$EB) tune piezo, 8,($04,$06,$08,$C4) tune piezo, 8,($08,$04,$06,$EB) tune piezo, 8,($2B,$06,$08,$C4) return chimeHour: for tmpB1=1 to curHour tune piezo, chimeTempo,($E4) pause HOURLY_PAUSE next return