#include #include #include #define F_CPU 12000000UL // 12 MHz #define SET_OUTPUT(P,B) { DDR##P |= _BV(B); } #define SET_INPUT(P,B) { DDR##P &= ~_BV(B); } #define SET_PIN(P,B) { PORT##P |= _BV(B); } #define CLR_PIN(P,B) { PORT##P &= ~_BV(B); } #define GET_PIN(P,B) ((PIN##P & _BV(B)) != 0) #define DIGIT_COUNT 4 typedef enum { MODE_LOCAL = 0, MODE_UTC, MODE_BEATS, MODE_YEAR, MODE_DOY, MODE_LAST } Mode; int tzOffset = 5; Mode mode = MODE_LOCAL; int editDigit = -1; char digits[10] = { 0x77, 0x24, 0x5d, 0x6d, 0x2e, 0x6b, 0x7b, 0x25, 0x7f, 0x2f }; // Maxes out at 3MHz, so we need ~4 nops per cycle to be on the // safe side. That, plus possible call overhead, should ensure // it works. void nops() { asm volatile("nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" ::); } void initClock() { // using 16-bit timer 1 with prescaler clock / 8. // 18750 clock pulses per 1/10th second. // Reset on OCR1A match. // WGM1: 0100 CS1: 011 TCCR1A = 0x00; TCCR1B = 0x1a; TCCR1C = 0x00; // not using the force matches OCR1A = 18750; TIMSK1 = 0x02; // turn on interrupt } void initPins() { SET_OUTPUT(C,5); // CLK SET_OUTPUT(C,4); // DATA SET_OUTPUT(C,3); // OE SET_OUTPUT(C,2); // LE } void initButtons() { // D5 - PCINT21 --| // D6 - PCINT22 |-- PCI2 // D7 - PCINT23 --| // B0 - PCINT0 ----- PCI0 SET_INPUT(D,5); // Set button pins as inputs SET_INPUT(D,6); SET_INPUT(D,7); SET_INPUT(B,0); SET_PIN(D,5); // Pull-up resistors on SET_PIN(D,6); SET_PIN(D,7); SET_PIN(B,0); PCMSK2 = 0xe0; PCMSK0 = 0x01; PCICR = 0x05; } // This is UTC time. volatile long timeInDeciSeconds = 0; // on reset, default to Jan. 1, 2009. volatile int dayOfYear = 0; volatile int year = 2009; bool isLeapYear() { return ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))); } // DST start days, zero-indexed from 2008 const int DST_START[] = { 0, // Don't care for 2008 59+7, // March 08, 2009 59+13, // March 14, 2010 59+12, // March 13, 2011 59+1+10, // March 11, 2012 LY 59+9, // March 10, 2013 59+8, // March 09, 2014 59+7 // March 08, 2015 }; // DST end days, zero-indexed from 2008 const int DST_END[] = { 0, // Don't care for 2008 304+0, // November 01, 2009 304+6, // November 07, 2010 304+5, // November 06, 2011 304+1+3, // November 04, 2012 LY 304+2, // November 03, 2013 304+1, // November 02, 2014 304+0 // November 01, 2015 }; const int UTC_EDT = -4; const int UTC_EST = -5; const int getDSTStart(int year) { return DST_START[year-2008]; } const int getDSTEnd(int year) { return DST_END[year-2008]; } bool isDaylightSavings() { return ((dayOfYear >= getDSTStart(year)) && (dayOfYear < getDSTEnd(year))); } void init() { cli(); initPins(); initButtons(); initClock(); set_sleep_mode( SLEEP_MODE_IDLE ); sei(); } void start_shift() { nops(); } void end_shift() { CLR_PIN(C,3); // OE low to turn on drivers SET_PIN(C,2); // LE is high (passing data) SET_PIN(C,5); // clock high (propegation) CLR_PIN(C,2); // LE is low (latched unchanged) } void shift_byte( unsigned char b ) { char i; for ( i = 0; i < 8; i++ ) { if ( b & 0x01 ) { // remember: invert the bits SET_PIN(C,4); // data high } else { CLR_PIN(C,4); // data low } CLR_PIN(C,5); // clock low b >>= 1; // next bit SET_PIN(C,5); // clock high (shift transition) } } void shift_digit( char n ) { if ( n < 0 ) { shift_byte(0); } else { shift_byte( (digits[n % 10]) << 1 ); } } int main( void ) { init(); while (1) { sleep_cpu(); } return 0; } // right button ISR(PCINT0_vect) { if ( !GET_PIN(B,0) ) { // Right/next digit if ( ++editDigit >= DIGIT_COUNT ) { editDigit = -1; } } } // up/down/left buttons ISR(PCINT2_vect) { long diff = 0; if ( !GET_PIN(D,5) ) { // Up button diff++; } if ( !GET_PIN(D,6) ) { // Down button diff--; } if ( !GET_PIN(D,7) ) { // Left/mode mode = (Mode)((mode+1) % MODE_LAST); } if ( editDigit != -1 ) { if (mode == MODE_UTC || mode == MODE_LOCAL) { switch ( editDigit ) { case 3: diff *= 10L; case 2: diff *= 6L; case 1: diff *= 10L; case 0: diff *= 10L * 60L; } timeInDeciSeconds += diff; } else if (mode == MODE_BEATS) { switch ( editDigit ) { case 2: diff *= 10L; case 1: diff *= 10L; case 0: diff *= 864L; } timeInDeciSeconds += diff; } else if (mode == MODE_YEAR || mode == MODE_DOY) { int e = editDigit; while (e--) { diff *= 10L; } if (mode == MODE_YEAR) { year += diff; if (year > 2015) year=2015; if (year < 2008) year=2008; } else if (mode == MODE_DOY) { dayOfYear += diff; int daysInYear = isLeapYear()?366:365; if (dayOfYear < 0) dayOfYear = daysInYear-1; if (dayOfYear >= daysInYear) dayOfYear = 0; } } } } ISR(TIMER1_COMPA_vect) { char digits[DIGIT_COUNT]; int idx; TCNT1 = 0x00; timeInDeciSeconds++; if (timeInDeciSeconds >= 10L*60L*60L*24L) { timeInDeciSeconds %= 10L*60L*60L*24L; dayOfYear++; if (dayOfYear >= (isLeapYear()?366:365)) { dayOfYear = 0; year++; } } if ( mode == MODE_UTC || mode == MODE_LOCAL ) { long timeInSeconds = timeInDeciSeconds/10L; if ( mode == MODE_LOCAL ) { timeInSeconds += (isDaylightSavings()?UTC_EDT:UTC_EST) * 60L * 60L; } timeInSeconds %= 24L * 60L * 60L; if ( timeInSeconds < 0 ) timeInSeconds += 24L * 60L * 60L; int minutes = (timeInSeconds / 60L) % 60L; int hours = (timeInSeconds / (60L*60L)) % 24L; digits[3] = hours/10L; digits[2] = hours%10L; digits[1] = minutes/10L; digits[0] = minutes%10L; } else if ( mode == MODE_BEATS ) { // conversion between UTC and Biel long adjustedDeciSeconds = timeInDeciSeconds + 1L * 60L * 60L * 10L; adjustedDeciSeconds %= 24L * 60L * 60L * 10L; long beats = (long)(adjustedDeciSeconds / 864L); digits[3] = -1; digits[2] = (beats/100L)%10; digits[1] = (beats/10L)%10; digits[0] = (beats)%10; } else if ( mode == MODE_YEAR ) { digits[3] = (year/1000L)%10; digits[2] = (year/100L)%10; digits[1] = (year/10L)%10; digits[0] = (year)%10; } else if ( mode == MODE_DOY ) { digits[3] = -1; digits[2] = (dayOfYear/100L)%10; digits[1] = (dayOfYear/10L)%10; digits[0] = (dayOfYear)%10; } if ( (editDigit >= 0) && ((timeInDeciSeconds%2L) == 0L) ) { digits[editDigit] = -1; } start_shift(); for ( idx = DIGIT_COUNT; idx > 0; idx-- ) { shift_digit( digits[idx-1] ); } end_shift(); }