### You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Project Help and Ideas » Christmas Countdown Clock

 December 04, 2015 by edSky Here is my Christmas Countdown Clock version 1.0 I use interrupts to process the button click that sets the current date/time. I modelled the setting of the time after a current LED watch called the Blink Time Watch. Hold the button for a few seconds and it enters "Set Year" mode. Then short presses increment the year. Hold long again and we go to "Set Month". Short clicks set the month. Repeat with Day, Hour, Minute and Seconds. The last long hold goes back to Run mode. I initially wanted to store the date/time as Year|DDD|Seconds where Seconds was Hours36+Minutes60+Seconds. I figured I could get the difference between current time and target time (i.e. Now and Christmas) by subtracting the two and using MODulus arithmetic to convert back to hh:mm:ss for display. This was to me the most intuitive from a mainframe or modern PC CPU point of view. It would simplify the code and I wouldn't have to "borrow" from minutes and hours and so on when subtracting. Turns out the code needed to do the "simple" math was over 4x as large as the long-hand accounting for time differences. I have been busy at my store (a toy store and this is prime time) so I haven't posted this until now. And I have a few to-do items. One is to automatically increment the year of the target (Christmas) when we actually count down to 0. Two is to do something fancy for the 24 hours of Christmas (or just display Merry Christmas). Three is to code in changes to and from Daylight Savings Time. I wanted to do this up front but figured I need to reset the time every other day or so because of drift. Five is to capture the moment of button-press and determine time-down so when you release the final mode change back to seconds it will negate the delay (i.e. it is 17:24:00 and I push the button now and hold for two seconds - when I release it is 17:24:03 not 17:24:00, but if I can add that time in...) (Four is something I can't remember or haven't even thought of yet.) ``````// realtimeclock1.c // for NerdKits with ATmega168 // mrobbins@mit.edu #define F_CPU 14745600 #include #include #include #include #include #include #include "../libnerdkits/delay.h" #include "../libnerdkits/lcd.h" //#include "../libnerdkits/uart.h" #include "countdownClock.h" /// Timer Macros to help understanding #define TIMER_TICKSPERSECONDS 100 #define TIMER_SECONDS2TICKS(et) ((et)*TIMER_TICKSPERSECONDS) #define TIMER_TICKS2SECONDS(et) ((double)(et)/TIMER_TICKSPERSECONDS) #define TIMER_TICKS2MILLISECONDS(et) ((double)(et)/(TIMER_TICKSPERSECONDS/1000.0)) #define TIMER_TICKS2MICROSECONDS(et) ((double)(et)/(TIMER_TICKSPERSECONDS/1000000.0)) // Helper Defines #define BTN_1 PC5 #define BTN_1_PORT PORTC #define BTN_1_DDR DDRC #define BTN_1_PIN PINC #define BTN_1_PCIE PCIE1 #define BTN_1_PCMSK PCMSK1 #define BTN_1_PCINT PCINT13 #define LED_1 PC4 #define LED_1_PORT PORTC #define LED_1_DDR DDRC #define LED_2 PC3 #define LED_2_PORT PORTC #define LED_2_DDR DDRC #define LED_3 PC2 #define LED_3_PORT PORTC #define LED_3_DDR DDRC /* Interrupt Info PCIFR needs to have bit PCIF1 - Pin Change Interrupt Flag 1 - set for interrupt on PCINT13 (or PC5) PCMSK1 – Pin Change Mask Register 1 - needs PCINT13 set ISR (PCINT1_vect) {} will be called and we will look at PINC and the bit for PC5 */ // Global variables // Time Collection data. int32_t tEvent[16]; // An array of time events. uint16_t tState; // A 16-bit set of state values uint8_t eventCount; // A count of the # of entries in tEvent[] int8_t displayRow[4]; // An array of indexes into tEvent, where -1 is an empty row volatile uint8_t monthDays[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; // Standard days per month. Leap will be calculated // PIN DEFINITIONS: // the_time will store the elapsed time // in hundredths of a second. // (100 = 1 second) // // note that this will overflow in approximately 248 days! // // // This variable is marked "volatile" because it is modified // by an interrupt handler. Without the "volatile" marking, // the compiler might just assume that it doesn't change in // the flow of any given function (if the compiler doesn't // see any code in that function modifying it -- sounds // reasonable, normally!). // // But with "volatile", it will always read it from memory // instead of making that assumption. volatile int32_t the_time; volatile int32_t btnDownStart; // The time the button was pressed volatile int32_t btnEventTime; // The time in ticks of the last button event volatile uint8_t qtrSecTimer; // a small timer used to count quarter-seconds volatile uint8_t clockMode; // volatile uint8_t prevClockMode; volatile DayTime theDayTime; // The Day/Time value volatile DayTime tgtDayTime; // The Target Day/Time value volatile DayTime difDayTime; // The countdown Day/Time value!!! volatile uint8_t setYY; // Set Mode Copy of YY volatile uint8_t setMM; // Set Mode Copy of MM, zero-based volatile uint8_t setDD; // Set Mode Copy of DD, one-based volatile uint8_t setHour; // Set Mode Copy of Hour volatile uint8_t setMins; // Set Mode Copy of Mins volatile uint8_t setSecs; // Set Mode Copy of Secs void Timer0_Setup() { // setup 8 bit Timer0: // CTC (Clear Timer on Compare Match mode) // TOP set by OCR0A register TCCR0A |= (1< 0x0D TCCR1B = ((1< 3) { qtrSecTimer -= 4; if (CLK_RUN == clockMode) { CountDayTimeUp(&theDayTime); CountDayTimeDown(&difDayTime); } Toggle_LED_3(); } Toggle_LED_2(); } ISR(PCINT1_vect) { int32_t btnTotalTime; // The time in ticks that the button was down uint8_t btnIsDown; // Indicates whether the button is pressed (true) or not; // Capture the time of the now most-recent event and // get the state of the button. In our implementation // The button is normally closed and pushing will open it. btnEventTime = the_time; btnIsDown = !(BTN_1_PIN & (1<= 1) // Was it a long press? { switch(clockMode) { case CLK_RUN : setYY = theDayTime.yy; DDDtoMMDD(theDayTime.ddd, &setMM, &setDD); setHour = theDayTime.hour; setMins = theDayTime.mins; setSecs = theDayTime.secs; clockMode = CLK_SET_YR; break; case CLK_SET_YR : clockMode = CLK_SET_MO; break; case CLK_SET_MO : clockMode = CLK_SET_DAY; break; case CLK_SET_DAY : clockMode = CLK_SET_HR; break; case CLK_SET_HR : clockMode = CLK_SET_MIN; break; case CLK_SET_MIN : clockMode = CLK_SET_SEC; break; case CLK_SET_SEC : theDayTime.yy = setYY; MMDDtoDDD(setMM, setDD, &theDayTime.ddd); theDayTime.hour = setHour; theDayTime.mins = setMins; theDayTime.secs = setSecs; DateDiff(&theDayTime, &tgtDayTime, &difDayTime); clockMode = CLK_RUN; break; default : clockMode = CLK_RUN; } } else { switch(clockMode) { case CLK_RUN : clockMode = CLK_UNDEF; break; case CLK_SET_YR : setYY++; if (setYY > 99) setYY = 0; if ((setYY % 4) == 0) monthDays[1] = 29; else monthDays[1] = 28; break; case CLK_SET_MO : setMM++; if (setMM > 11) setMM = 0; // When the month changes, cap the day to the max in the month if (setDD > monthDays[setMM]) setDD = monthDays[setMM]; break; case CLK_SET_DAY : setDD++; // When the day changes, cap the day to the max in the month if (setDD > monthDays[setMM]) setDD = 1; break; case CLK_SET_HR : setHour++; if (setHour > 23) setHour = 0; break; case CLK_SET_MIN : setMins++; if (setMins > 59) setMins = 0; break; case CLK_SET_SEC : setSecs++; if (setSecs > 59) setSecs = 0; break; default : break; } } } } /* Params: *pDayTime The */ void CountDayTimeUp(DayTime *pDayTime) { // For now we aren't going to worry about ddd year overflow // If so we would need to pass in the #days/year and check, // and if we did roll into a new year we would have to reset // February if a leap year. pDayTime->secs++; if (60 == pDayTime->secs) { pDayTime->secs = 0; pDayTime->mins++; if (60 == pDayTime->mins) { pDayTime->mins = 0; pDayTime->hour++; if (24 == pDayTime->hour) { pDayTime->hour = 0; pDayTime->ddd++; } } } } /* Params: *pDayTime The */ void CountDayTimeDown(DayTime *pDayTime) { // For now we aren't going to worry about ddd year overflow // If so we would need to pass in the #days/year and check, // and if we did roll into a new year we would have to reset // February if a leap year. pDayTime->secs--; if (0 > pDayTime->secs) { pDayTime->secs = 59; pDayTime->mins--; if (0 > pDayTime->mins) { pDayTime->mins = 59; pDayTime->hour--; if (0> pDayTime->hour) { pDayTime->hour = 23; pDayTime->ddd--; } } } } /* Params: *pCurDayTime The *pTgtDayTime The *pDifDayTime The */ void DateDiff(DayTime *pCurDayTime, DayTime *pTgtDayTime, DayTime *pDifDayTime) { int8_t minsAdj = 0; int8_t hourAdj = 0; int8_t daysAdj = 0; pDifDayTime->secs = (pTgtDayTime->secs - pCurDayTime->secs); if (pDifDayTime->secs < 0) { pDifDayTime->secs += 60; minsAdj = 1; } pDifDayTime->mins = (pTgtDayTime->mins - pCurDayTime->mins - minsAdj); if (pDifDayTime->mins < 0) { pDifDayTime->mins += 60; hourAdj = 1; } pDifDayTime->hour = (pTgtDayTime->hour - pCurDayTime->hour - hourAdj); if (pDifDayTime->hour < 0) { pDifDayTime->hour += 24; daysAdj = 1; } pDifDayTime->ddd = (pTgtDayTime->ddd - pCurDayTime->ddd - daysAdj); } /* Params: dayOfYear The one-based day of the year *mm The zero-based corresponding month *dd The corresponding day of the month */ void DDDtoMMDD(int16_t dayOfYear, int8_t *mm, int8_t *dd) { int8_t i = 0; int16_t ddd = dayOfYear; while ((ddd > monthDays[i]) && (i < 11)) { ddd -= monthDays[i]; i++; } *dd = ddd; *mm = i; } /* Params: mm The zero-based corresponding month dd The corresponding day of the month *dayOfYear The one-based day of the year */ void MMDDtoDDD(int8_t mm, int8_t dd, int16_t *dayOfYear) { int8_t i = 0; int16_t ddd = dd; while ((i < mm) && (i <= 11)) { ddd += monthDays[i]; i++; } *dayOfYear = ddd; } void Set_LED(uint8_t *ledPort, uint8_t ledState) { // REflect the button state in the LED if (ledState != 0) { _MMIO_BYTE(ledPort) |= (1<