clock-share.pde

/*
Fancy clock version 1.1
Eric Ayars

Arduino-based clock with:
    2-line backlit LCD
    Time, Date, Temperature
    Date-dependent event notification
    Automatic daylight-savings time adjustment
    (possibly) automatic backlight adjustment for ambient conditions
    Alarm
    USB port for setting clock to computer time

Based on:
    1307 real-time clock (I2C interface)
    LM35 thermometer
    Arduino 328 (enough memory that way for event storage!)
*/

#include <avr/pgmspace.h>
#include <Wire.h>
#include <LiquidCrystal.h>
#define CLOCK_ADDRESS 0x68
#define TEMPERATURE_PIN 0
#define BACKLIGHT 3
#define MIN_BACKLIGHT 50
#define MAX_BACKLIGHT 255
#define MESSAGE_LENGTH 17
#define DEBOUNCE 50
#define MAX_ALARM 7
#define BUZZER 13

boolean DaylightSavings = false;
char Message[MESSAGE_LENGTH]    = "                ";
char TopLine[MESSAGE_LENGTH]    = "                ";
char BottomLine[MESSAGE_LENGTH] = "                ";
char SetMessage[]               = "   Clock Set    ";
char AlarmMessage[]             = "   Alarm Set    ";
byte Temp1;
byte Year = 9;
byte Month = 12;
byte Day = 26;
byte DoW = 8;
byte Hour = 3;
byte Minute = 41;
byte Second = 30;
byte oldHour = 255;     // These three are set to absurd values to trigger
byte oldMinute = 255;   // the change-sensitive bits in loop().
byte oldSecond = 255;   // 
byte oldDay = 255;      //
boolean OldButtonState = false;
boolean NewButtonState = false;
volatile byte Rotor = 0;
byte UIDelay=100;

// Button/rotary switch connections
byte Button = 10;
byte A = 2;
byte B = 11;

byte AlarmOn = 0;               // lowest bits are flags indicating which alarms are on.
byte AlarmMinute[MAX_ALARM];    // Array of alarm minutes...
byte AlarmHour[MAX_ALARM];      // ...hours...
byte AlarmDays[MAX_ALARM];      // ...and days.
char OnAlarms[8] = "SMTWHFS";   // Indicators for alarm on these days...
char OffAlarms[8] = "smtwhfs";  // ...and off.
boolean AlarmRinging = false;   // Is the alarm sounding?
boolean BuzzerState = false;    // Is the alarm (while sounding) currently on or off?

char *WeekDay[7] = { 
        "Sunday          ", 
        "Monday          ", 
        "Tuesday         ",
        "Wednesday       ", 
        "Thursday        ",
        "Friday          ", 
        "Saturday        "};

LiquidCrystal lcd(8,9,7,6,5,4);
float tconvert = 500.0/1024.0;  // converts A/D value to temperature.

void SetClock() {
    // This is the user interface routine for setting the clock. 
    // The 1307 chip is actually set using SetTime().

    UpdateTimeDisplayPlus(Hour, Minute, Year, DaylightSavings);
    SetBottomLine(DoW, Month, Day);
    writeLCD(0,TopLine);
    writeLCD(1,BottomLine);

    // Here's where we set the hour 
    Rotor = Hour + 48;      // The extra value on rotor allows easier turn-back.
    while (!ButtonPressed()) {
        delay(UIDelay);
        Hour = Rotor % 24;
        UpdateTimeDisplayPlus(Hour, Minute, Year, DaylightSavings);
        writeLCD(0,TopLine);
        lcd.setCursor(0,0);
        lcd.cursor();
    }

    // Here we set the minute
    Rotor = Minute + 120;   // The extra value on rotor allows easier turn-back.
    while (!ButtonPressed()) {
        delay(UIDelay);
        Minute = Rotor % 60;
        UpdateTimeDisplayPlus(Hour, Minute, Year, DaylightSavings);
        writeLCD(0,TopLine);
        lcd.setCursor(3,0);
        lcd.cursor();
    }

    // Here we set the year
    Rotor = Year;
    while (!ButtonPressed()) {
        delay(UIDelay);
        Year = Rotor;
        UpdateTimeDisplayPlus(Hour, Minute, Year, DaylightSavings);
        writeLCD(0,TopLine);
        lcd.setCursor(10,0);
        lcd.cursor();
    }

    // Here we set the Daylight Savings status
    // I set the initial value of the rotor to 127 or 128: odd is 
    // true, even is false.
    if (DaylightSavings) {
        Rotor = 127;
    } else {
        Rotor = 128;
    }
    while (!ButtonPressed()) {
        delay(UIDelay);
        DaylightSavings = (boolean)(Rotor%2);
        UpdateTimeDisplayPlus(Hour, Minute, Year, DaylightSavings);
        writeLCD(0,TopLine);
        lcd.setCursor(13,0);
        lcd.cursor();
    }

    // Set the day of the week
    Rotor = DoW + 16;   // The extra value on rotor allows easier turn-back.
    while (!ButtonPressed()) {
        delay(UIDelay);
        DoW = constrain((Rotor % 8), 1, 7);
        SetBottomLine(DoW, Month, Day);
        writeLCD(1, BottomLine);
        lcd.setCursor(0,1);
        lcd.cursor();
    }

    // Set the Month
    Rotor = Month + 26; // The extra value on rotor allows easier turn-back.
    while (!ButtonPressed()) {
        delay(UIDelay);
        Month = constrain((Rotor % 13),1,12);
        SetBottomLine(DoW, Month, Day);
        writeLCD(1, BottomLine);
        lcd.setCursor(11,1);
        lcd.cursor();
    }

    // Set the Date
    Rotor = Day;
    while (!ButtonPressed()) {
        delay(UIDelay);
        switch (Month) {
            // Thirty days hath November,
            // All the rest I can't remember.
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                Day = Rotor % 32;
                break;
            case 4:
            case 6:
            case 9:
            case 11:
                Day = Rotor % 31;
                break;
            case 2:
                Day = Rotor % 30;
                break;
        }
        if (Day==0) {
            Day++;
        }
        SetBottomLine(DoW, Month, Day);
        writeLCD(1, BottomLine);
        lcd.setCursor(14,1);
        lcd.cursor();
    }

    // Turn cursor off
    lcd.noCursor();

    // Save the new stuff, with second set to zero
    setTime(Year, Month, Day, DoW, Hour, Minute, 0x00);

    // Mark that things have changed so the next loop() does
    // what is needed, if needed.
    oldSecond = 255;
    oldMinute = 255;
    oldHour = 255;
    oldDay = 255;
}

void setTime(byte Year, byte Month, byte Day, byte DoW, byte Hour, byte Minute, byte Second) {
    //  1) Sets the date and time on the ds1307
    //  2) Starts the clock
    //  3) Sets hour mode to 24 hour clock
    //  4) Assumes you're passing in valid numbers
    Wire.beginTransmission(CLOCK_ADDRESS);
    Wire.send(0);
    Wire.send(decToBcd(Second));
    Wire.send(decToBcd(Minute));
    Wire.send(decToBcd(Hour));      
    Wire.send(decToBcd(DoW));
    Wire.send(decToBcd(Day));
    Wire.send(decToBcd(Month));
    Wire.send(decToBcd(Year));
    Wire.endTransmission();
}

void DisplayAlarmInfo(byte Alarm) {
    // This function sets the bottom line to be useful information for setting
    // the alarm(s).

    // Convert from 24-hour internal to 12-hour display
    Temp1 = (AlarmHour[Alarm]) % (byte)12;
    if (Temp1 == 0) {
        Temp1 = 12;
    } 
    if (Temp1 / 10) {
        // write the 1 in this case.
        BottomLine[0] = '1';
    } else {
        BottomLine[0] = ' ';
    }
    // Second digit of hour:
    BottomLine[1] = (char)(Temp1 % 10 + 48);
    // minutes
    BottomLine[2] = ':';
    BottomLine[3] = (char)(AlarmMinute[Alarm] / 10 + 48);
    BottomLine[4] = (char)(AlarmMinute[Alarm] % 10 + 48);

    // Take care of am/pm
    if (AlarmHour[Alarm] > 11) {
        BottomLine[5] = 'p';
    } else {
        BottomLine[5] = 'a';
    }

    BottomLine[6] = ' ';

    // Is this alarm on?
    if ((AlarmOn >> Alarm) & 1) {   // Clever, here. :-( This checks the alarm bit for this alarm.
        BottomLine[7] = '!';
    } else {
        BottomLine[7] = '-';
    }

    BottomLine[8] = ' ';

    // List status for each day
    for (byte j=0;j<7;j++) {
        if ((AlarmDays[Alarm] >> j) & 1) {      // Check which days things are on using bit-shift.
            BottomLine[9+j] = OnAlarms[j];      // Capital letters for "on this day".
        } else {
            BottomLine[9+j] = OffAlarms[j]; // lower-case letters for "not on this day".
        }
    }

    // Update display
    writeLCD(1,BottomLine);
}

void SetAlarm() {
    // Picks an alarm number, sets it.

    byte ThisAlarm = 0;

    const char *M = PSTR(" Set Alarm # 0  ");
    for (byte j=0;j<MESSAGE_LENGTH-1;j++) {
        TopLine[j] = (char)pgm_read_byte(M++);
    }
    
    // First select which alarm to set
    Rotor = 3*MAX_ALARM;
    while (!ButtonPressed()) {
        ThisAlarm = Rotor % MAX_ALARM;
        TopLine[13] = (char)ThisAlarm + 49; 
        // (Add 49 instead of 48 so that the alarm counts 1-7 rather than 0-6.)
        writeLCD(0,TopLine);
        // Depending on which alarm is selected, display info on second line.
        DisplayAlarmInfo(ThisAlarm);
        // Mark what is being changed
        lcd.setCursor(13,0);
        lcd.cursor();
        delay(UIDelay);
    }

    // Now an alarm number has been selected, so set it.

    // Set the hour
    Rotor = AlarmHour[ThisAlarm] + (byte)48;
    while (!ButtonPressed()) {
        AlarmHour[ThisAlarm] = Rotor % (byte)24;
        DisplayAlarmInfo(ThisAlarm);
        lcd.setCursor(1,1);
        lcd.cursor();
        delay(UIDelay);
    }

    // Set the Minute
    Rotor = AlarmMinute[ThisAlarm] + (byte)120;
    while (!ButtonPressed()) {
        AlarmMinute[ThisAlarm] = Rotor % (byte)60;
        DisplayAlarmInfo(ThisAlarm);
        lcd.setCursor(3,1);
        lcd.cursor();
        delay(UIDelay);
    }

    // Set the activity
    if ((AlarmOn >> ThisAlarm) & 1) {   // Check this alarm's on-status-bit.
        Rotor = (byte)127;
    } else {
        Rotor = (byte)128;
    }
    while (!ButtonPressed()) {
        if (Rotor % 2) {
            AlarmOn = AlarmOn | ((byte)1 << ThisAlarm); // Turn on this alarm's on-status-bit.
        } else {
            AlarmOn = AlarmOn & ((byte)255 - ((byte)1<<ThisAlarm)); // Turn off this on-status-bit.
        }
        DisplayAlarmInfo(ThisAlarm);
        lcd.setCursor(7,1);
        lcd.cursor();
        delay(UIDelay);
    }

    // Set the days it should ring
    for (byte j=0;j<MAX_ALARM;j++) {
        if (AlarmDays[ThisAlarm] & ((byte)1<<j)) {
            // On this day
            Rotor = (byte)127;
        } else { 
            Rotor = (byte)128;
        }
        while (!ButtonPressed()) {
            if (Rotor % 2) {
                // Turn this day's bit on
                AlarmDays[ThisAlarm] = AlarmDays[ThisAlarm] | ((byte)1<<j);
            } else {
                // Turn this day's bit off
                AlarmDays[ThisAlarm] = AlarmDays[ThisAlarm] & ((byte)255-((byte)1<<j));
            }
            DisplayAlarmInfo(ThisAlarm);
            lcd.setCursor((9+j),1);
            lcd.cursor();
            delay(UIDelay);
        }
    }
    
    // Save alarm status to EEPROM
    SaveStatus();

    // Set oldDay to invalid value so that the bottom line gets set back to normal.
    oldDay = 255;

    // Clear the cursor
    lcd.noCursor();
}

void UpdateTimeDisplayPlus(byte Hour, byte Minute, byte Year, boolean DaylightSavings) {
    // Updates TopLine to reflect current time, with addition of 
    // year and daylight savings time status.
    UpdateTimeDisplay(Hour, Minute);
    TopLine[8] = '2';
    TopLine[9] = '0';
    TopLine[10] = (char)(Year/10) + 48;
    TopLine[11] = (char)(Year%10) + 48;
    TopLine[12] = ' ';
    if (DaylightSavings) {
        TopLine[13] = 'D';
    } else {
        TopLine[13] = ' ';
    }
    TopLine[14] = 'S';
    TopLine[15] = 'T';
}

void UpdateRotation() {
    // This is the subroutine that runs every time pin 2
    // goes low.
    if (digitalRead(B)) {
        Rotor++;
    } else {
        Rotor--;
    }
}

boolean ButtonPressed() {
    // I'm using the internal pull-up resistors on the Arduino, so
    // logic is inverted. LOW means the button is pressed.
    // This routine, however, returns true if button has been pressed and false
    // otherwise. It's sensitive to the transition between unpressed and pressed.
    boolean WasIt;  // as in, "Was It pressed?"
    NewButtonState = !digitalRead(Button);
    if ((!OldButtonState) && (NewButtonState)) {
        // Button was unpressed and is now pressed
        WasIt = true;
    } else {
        WasIt = false;
    }
    OldButtonState = NewButtonState;
    return(WasIt);
}

void DealWithButton() {
    // Function to deal with button presses.
    // If the alarm is beeping it turns the beep off and returns.
    // Otherwise, it starts the whole "set time/date/alarm" routine.

    byte Next=0;    // flag to keep track of next action.
    if (AlarmRinging) {
        // Alarm is ringing, so turn it off and return.
        AlarmRinging = false;
        BuzzerState = false;
        digitalWrite(BUZZER, LOW);
    } else {
        // User must be trying to set the clock or the alarm.
        for (byte j=0;j<MESSAGE_LENGTH;j++) {
            TopLine[j] = SetMessage[j];
            BottomLine[j] = AlarmMessage[j];
        }
        Rotor=0;
        TopLine[0] = '*';
        writeLCD(0,TopLine);
        writeLCD(1,BottomLine);

        // Now wait for selection.
        unsigned long StartTimeOut = millis();
        while (!ButtonPressed()) {
            delay(UIDelay);     // user interface delay
            Rotor = Rotor % 2;

            // move selection display according to current rotor position.
            if (Rotor == 1) {
                TopLine[0] = ' ';
                BottomLine[0] = '*';
                Next = 1;
            } else {
                TopLine[0] = '*';
                BottomLine[0] = ' ';
                Next = 0;
            }
            writeLCD(0,TopLine);
            writeLCD(1,BottomLine);

            // Check for time-out
            if ((millis()-StartTimeOut) > (unsigned long)30000) {
                // No buttonpress for 30 seconds, go back to keeping time.
                Next = 2;
                // Since it skips SetClock() or SetAlarm() here, we need
                // to reset the bottom line display. 
                oldDay = 255;
                break;
            }
        }
        if (Next == 0) {
            // Set clock
            SetClock();
        } else if (Next == 1) {
            // Set alarm
            SetAlarm();
        }
    }
}

void writeLCD(int lineNum, char* contents) {
    lcd.setCursor(0,lineNum);
    lcd.print(contents);
}

void Retrieve(const char *M) {
    // Retrieves a string from PROGMEM, puts it into BottomLine.
    for (byte j=0;j<MESSAGE_LENGTH-1;j++) {
        BottomLine[j] = (char)pgm_read_byte(M++);
    }
}

void GetMessage(byte Year, byte Month, byte Day, byte DoW) {
    // There's no way to store all the necessary strings in 
    // RAM on an ATMega168, or even ATMega328. Instead, I'm 
    // storing the strings in PROGMEM. The PSTR() designation
    // stores the indicated string in program memory, then 
    // the Retrieve() function pulls it out (temporarily)
    // for use by the program.

    // At the very least, the Message should be the day/date,
    // which has been calculated prior to this.
    strcpy(Message, BottomLine);

    // Now look for special cases.
    switch (Month) {
        case 1:
            // Martin Luther King, Jr. Day
            if ((DoW==2) && (Day>14) && (Day<22)) {
                // Third Monday of January.
                Retrieve(PSTR("MLK Jr. Day     "));
            }
            switch (Day) {
                case 1:
                    Retrieve(PSTR("Happy New Year! "));
                    break;
                case 5:
                    Retrieve(PSTR("Nat'l Bird Day  "));
                    break;
                case 18:
                    Retrieve(PSTR("Pooh Day        "));
                    break;
                case 20:
                    Retrieve(PSTR("Inauguration Day"));
                    break;
                case 23:
                    Retrieve(PSTR("Cheryl's B-day  "));
                    break;
            }
            break;
        case 2:
            //President's day
            if ((DoW==2) && (Day>14) && (Day<22)) {
                // Third Monday of Feb.
                Retrieve(PSTR("President's Day "));
            }
            //Super Bowl (or Paskenta Road Race)
            if ((DoW==1) && (Day<8)) {
                // First Sunday of Feb.
                Retrieve(PSTR("Super Bowl      "));
            }
            switch (Day) {
                case 2:
                    Retrieve(PSTR("Groundhog Day   "));
                    break;
                case 14:
                    Retrieve(PSTR("Valentine's Day "));
                    break;
                case 29:
                    Retrieve(PSTR("Leap Day        "));
                    break;
            }
            break;
        // This sort of thing continues for each month of the year.
        // I currently have about 150 of these "specials", and on a 
        // 328 there's room for one each day of the year.
        case 11:
            if (DoW==5) {
                // Deal with Thanksgiving.
                if ((Day > 21) && (Day < 29)) {
                    // This is the fourth Thursday of November
                    Retrieve(PSTR("Thanksgiving!   "));
                    break;
                }
            }
            switch (Day) {
                // Here's an example of how to do another type of special.
                // This one not only announces the birthday but displays the age.
                case 19: {
                    byte dt, tdt, odt; 
                    char Temp[MESSAGE_LENGTH] = "Eric is xx!     ";
                    dt = Year + 32;
                    tdt = dt / 10;
                    odt = dt % 10;
                    Temp[8] = (char)tdt+48;
                    Temp[9] = (char)odt+48;
                    strcpy(Message, Temp);
                    break;
                    }
            }
            break;
    }
}

int GetTemperature() {
    ////Serial.println("GetTemperature()");
    // Centigrade
    //return (int)(analogRead(TEMPERATURE_PIN)*tconvert);
    // Fahrenheit
    return (int)((analogRead(TEMPERATURE_PIN)*tconvert)*1.8 + 32);
}

void SaveStatus() {
    // Saves permanent information in 1307 EEPROM.
    //  Information saved consists of the following bytes
    //  8:  Daylight Savings state 
    //  9:  AlarmOn byte
    //  10-30:  sequential elements of AlarmHour array, AlarmMinute array, and AlarmDays array.
    //  The order is 
    //      AlarmHour[0], AlarmMinute[0], AlarmDays[0],
    //      AlarmHour[1], AlarmMinute[1], AlarmDays[1],
    //  etc.

    Wire.beginTransmission(CLOCK_ADDRESS);

    // Start with memory location 8 (the first 7 are time).
    Wire.send(0x08);

    // Write the Daylight Savings flag.
    Wire.send((byte)DaylightSavings);
    
    // Write the AlarmOn byte.
    Wire.send(AlarmOn);

    // Write the array data.
    for (byte j=0;j<MAX_ALARM;j++) {
        Wire.send(AlarmHour[j]);
        Wire.send(AlarmMinute[j]);
        Wire.send(AlarmDays[j]);
    }

    // And done...
    Wire.endTransmission();
}

void LoadStatus() {
    // Recalls data from 1307 EEPROM on powerup.
    // Start by pointing to the data we want
    Wire.beginTransmission(CLOCK_ADDRESS);
    Wire.send(0x08);
    Wire.endTransmission();

    // now get data bytes
    Wire.requestFrom(CLOCK_ADDRESS, 2+(MAX_ALARM*3));
    DaylightSavings     = Wire.receive();
    AlarmOn             = Wire.receive();
    for (byte j=0;j<MAX_ALARM;j++) {
        AlarmHour[j]    = Wire.receive();
        AlarmMinute[j]  = Wire.receive();
        AlarmDays[j]    = Wire.receive();
    }
}

byte decToBcd(byte val) {
    // Convert normal decimal numbers to binary coded decimal
    return ( (val/10*16) + (val%10) );
}

byte bcdToDec(byte val) {
    // Convert binary coded decimal to normal decimal numbers
    return ( (val/16*10) + (val%16) );
}

void getTime(byte *Year, byte *Month, byte *Day, byte *DoW, byte *Hour, byte *Minute, byte *Second) {
    // Gets the date and time from the ds1307
    // Reset the register pointer
    Wire.beginTransmission(CLOCK_ADDRESS);
    Wire.send(0);
    Wire.endTransmission();

    Wire.requestFrom(CLOCK_ADDRESS, 7);

    // A few of these need masks because certain bits are control bits
    *Second = bcdToDec(Wire.receive() & 0x7f);  // second
    *Minute = bcdToDec(Wire.receive());         // minute
    *Hour   = bcdToDec(Wire.receive() & 0x3f);  // hour, change for 12 hour am/pm
    *DoW    = bcdToDec(Wire.receive());         // Day of week
    *Day    = bcdToDec(Wire.receive());         // Day of month
    *Month  = bcdToDec(Wire.receive());         // month
    *Year   = bcdToDec(Wire.receive());         // year
}

void UpdateTimeDisplay(byte Hour, byte Minute) {
    // Updates TopLine, characters 0-10, with time and alarm info.
    Temp1 = Hour % 12;  // Convert 24h clock to 12h
    if (Temp1==0) {     // 00 is actually 12 on am/pm clock.
        Temp1 = 12;
    }
    if (Temp1 / 10) {
        // write the 1 in this case.
        TopLine[0] = '1';
    } else {
        TopLine[0] = ' ';
    }
    // Second digit of hour:
    TopLine[1] = (char)(Temp1 % 10 + 48);
    // minutes
    TopLine[2] = ':';
    TopLine[3] = (char)(Minute / 10 + 48);
    TopLine[4] = (char)(Minute % 10 + 48);
    // Take care of am/pm
    if (Hour > 11) {
        TopLine[5] = 'p';
    } else {
        TopLine[5] = 'a';
    }
    TopLine[6] = 'm';
    TopLine[7] = ' ';
    // Alarm indicator at [10] and [11]
    if (AlarmOn) {
        TopLine[8] = 'o';
        TopLine[9] = 'n';
    } else {
        TopLine[8] = '-';
        TopLine[9] = '-';
    }
    TopLine[10] = ' ';
}

void SetBottomLine(byte DoW, byte Month, byte Day) {
    // Loads the day/date information into the second line of the display.
    for (byte j=0;j<MESSAGE_LENGTH;j++) {
        BottomLine[j] = WeekDay[DoW-1][j];
    }
    // Update date display
    if (Month<10) {
        BottomLine[11] = ' ';
    } else {
        BottomLine[11] = '1';
    }
    BottomLine[12] = (char)(Month%10)+48;
    BottomLine[13] = '/';
    if (Day<10) {
        BottomLine[14] = (char)Day+48;
        BottomLine[15] = ' ';
    } else {
        BottomLine[14] = (char)(Day/10)+48;
        BottomLine[15] = (char)(Day%10)+48;
    }
}

void UpdateTemperatureDisplay() {
    // Updates TopLine, characters 12-15, with temperature.
    int Temp = GetTemperature();
    TopLine[11] = (char)Temp/10 + 48;
    TopLine[12] = (char)Temp%10 + 48;
    TopLine[13] = (char)223;
    TopLine[14] = 'F';
    TopLine[15] = ' ';
}

void setup() {

    // Configure the switch, button, and buzzer.
    pinMode(Button, INPUT);
    pinMode(A, INPUT);
    pinMode(B, INPUT);
    pinMode(BUZZER, OUTPUT);

    // Start the I2C interface to the 1307 clock
    Wire.begin();

    // Start the LCD
    lcd.begin(2,16);
    lcd.clear();

    // Attach interrupt to pin A of the rotary switch
    attachInterrupt(0, UpdateRotation, FALLING);

    // Get things from the eeprom
    LoadStatus();

    // Set old information to absurd values to trigger new second/minute/hour/day actions

    oldHour = 255;      // These three are set to absurd values to trigger
    oldMinute = 255;    // the change-sensitive bits in loop().
    oldSecond = 255;    // 
    oldDay = 255;       //
}

void loop() {
    /*  In this main loop, several things should happen.
        
        Every quarter second:
            Check the time.
            Check to see if the button is pressed.
                If so, handle it and come back when finished.
            If an alarm is ringing, toggle the buzzer on/off.
        
        Every second:
            Update the time display.
            Update temperature display.
            If it's an even second, swap message/date info on line 2.

        Every minute:
            Check to see if an alarm should be ringing.
                Turn it on or off as necessary.
            
        Every hour:
            Check for daylight savings time shifts and other specials.
            Adjust backlight for daylight/evening conditions.

        Every day:
            Check for a message to put on line 2.
            Update the display of line 2
    */

    // These are the things that happen every cycle.
    getTime(&Year, &Month, &Day, &DoW, &Hour, &Minute, &Second);

    if (ButtonPressed()) {
        DealWithButton();
    }

    if (AlarmRinging) {
        // Toggle buzzer on/off
        BuzzerState = !BuzzerState;
        digitalWrite(BUZZER, BuzzerState);
    }

    // These are the things that happen every second:
    if (Second != oldSecond) {
        oldSecond = Second;

        UpdateTimeDisplay(Hour, Minute);
        UpdateTemperatureDisplay();

        // Write to the LCD
        writeLCD(0, TopLine);
        if (Second%4<2) {
            // Display this for 2 seconds
            writeLCD(1, Message);
        } else {
            // Display that for 2 seconds
            writeLCD(1, BottomLine);
        }
    }

    // These are the things that happen every minute:
    if (Minute != oldMinute) {
        oldMinute = Minute;

        // Deal with alarms
        // Assume that nothing is happening.
        AlarmRinging = false;

        // Now check to see if anything IS happening.
        if (AlarmOn) {  
            // Loop through the set of alarms, check which is on
            for (byte j=0;j<MAX_ALARM;j++) {
                if ((AlarmOn >> j) & 1) {   // Check each AlarmOn bit
                    // alarm j is on, check time for alarm j.
                    if ((AlarmHour[j]==Hour) && (AlarmMinute[j]==Minute)) {
                        // Check day for alarm j
                        if ((AlarmDays[j]>>(Day-1)) & 1) {
                            // Alarm j should be ringing.
                            AlarmRinging = true;
                        }
                    }
                }
            }
        }
    }

    // These are the hourly events:
    if (Hour != oldHour) {
        oldHour = Hour;

        // Adjust backlight level
        if ((Hour>20) || (Hour<6)) {
            // Nighttime, so dim the lights.
            analogWrite(BACKLIGHT, MIN_BACKLIGHT);
        } else {
            analogWrite(BACKLIGHT, MAX_BACKLIGHT);
        }

        // Check for time changes every Sunday
        if (DoW==1) {
            // It's Sunday. Check the rest of the requirements
            if ((Month==3) && (Day>7) && (Day<15) && (!DaylightSavings)) {
                // This is the day to "Spring Forward"
                if ((Hour==2) && (Minute==0) && (Second==0)) {
                    // set reminder
                    Retrieve(PSTR("Set clocks ++   "));
                    // Set hour up by one.
                    Hour++;
                    setTime(Year, Month, Day, DoW, Hour, Minute, Second);
                    // Set DaylightSavings flag
                    DaylightSavings = true;
                    // Save DS flag to EEPROM
                    SaveStatus();
                }
            }
            if ((Month==11) && (Day<8) && (DaylightSavings)) {
                // This is the day to "Fall Back", and we haven't fallen back yet.
                if ((Hour==2) && (Minute==0) && (Second==0)) {
                    // Remind people
                    Retrieve(PSTR("Set clocks back "));
                    // Decrement hour
                    Hour--;
                    setTime(Year, Month, Day, DoW, Hour, Minute, Second);
                    // Clear DS flag
                    DaylightSavings = false;
                    // Save DS flag to EEPROM
                    SaveStatus();
                }
            }
        }
    }

    // These are the daily events:
    if (Day != oldDay) {
        oldDay = Day;
    
        SetBottomLine(DoW, Month, Day);
        // Update the day's special message
        GetMessage(Year, Month, Day, DoW);
    }

    // Wait around a bit before repeating.
    delay(250);
}

Generated by GNU enscript 1.6.4.