Binary_DCF77_Clock/firmware/main.c
2007-01-02 21:30:40 +00:00

355 lines
12 KiB
C

/**
* \file main.c
* \brief Firmware for the binary DCF-77 clock
* \author Ronald Schaten
* \version $Id: main.c,v 1.1 2007/01/02 21:30:40 rschaten Exp $
*
* License: See documentation.
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include "saa1064.h"
#include "dcftime.h"
uint8_t byte[4] = { 2, 3, 1, 0 }; /** the order of the connected output-LED-rows */
uint8_t output[4], outputOld[4]; /** current and old content of the LEDs */
/** the display-modes */
enum modes {
timeasbinary,
dateasbinary,
timeasbcdhorizontal,
dateasbcdhorizontal,
timeasbcdvertical,
dateasbcdvertical,
timestamp
};
enum modes mode;
uint8_t demomode = 0;
/**
* sends the current content of output[] to the LEDs if it has changed.
*/
void setLeds(void) {
uint8_t i;
for (i = 0; i < 4; i++) {
if (output[i] != outputOld[i]) {
set_led_digit(byte[i], output[i]);
outputOld[i] = output[i];
}
}
} // function setLeds
/**
* Takes the current time and converts it into different output-formats.
* \param datetime: the current time
*/
void setOutput(dcf_datetime datetime) {
uint8_t bcdlow, bcdhigh; /* takes the low and high parts when converting to BCD */
const uint32_t TS01012000 = 946681200UL; /* The UNIX-Timestamp of 1.1.2000 */
const uint16_t monthstarts[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; /* On which day does a new month start in non-leap-years */
const uint32_t SECONDSINADAY = (60UL * 60 * 24); /* Number of seconds in a day */
uint32_t ts; /* takes the UNIX-Timestamp */
switch (mode) {
case timeasbinary:
/* hour, minute and second are displayed in rows */
output[0] = datetime.time.hour;
output[1] = datetime.time.minute;
output[2] = datetime.time.second;
output[3] = 0;
break;
case dateasbinary:
/* day of month, month, year and day of week (starting with monday
* = 1) are displayed in rows */
output[0] = datetime.date.dayofmonth;
output[1] = datetime.date.month;
output[2] = datetime.date.year;
output[3] = datetime.date.dayofweek;
break;
case timeasbcdhorizontal:
/* the time is converted to BCD, the digits are displayed by two in
* a row */
bcdlow = datetime.time.hour % 10;
bcdhigh = datetime.time.hour / 10;
output[0] = (bcdhigh << 4) | bcdlow;
bcdlow = datetime.time.minute % 10;
bcdhigh = datetime.time.minute / 10;
output[1] = (bcdhigh << 4) | bcdlow;
bcdlow = datetime.time.second % 10;
bcdhigh = datetime.time.second / 10;
output[2] = (bcdhigh << 4) | bcdlow;
output[3] = 0;
break;
case dateasbcdhorizontal:
/* the date is converted to BCD, the digits are displayed by two in
* a row */
bcdlow = datetime.date.dayofmonth % 10;
bcdhigh = datetime.date.dayofmonth / 10;
output[0] = (bcdhigh << 4) | bcdlow;
bcdlow = datetime.date.month % 10;
bcdhigh = datetime.date.month / 10;
output[1] = (bcdhigh << 4) | bcdlow;
bcdlow = datetime.date.year % 10;
bcdhigh = datetime.date.year / 10;
output[2] = (bcdhigh << 4) | bcdlow;
bcdlow = datetime.date.dayofweek;
bcdhigh = 0;
output[3] = (bcdhigh << 4) | bcdlow;
break;
case timeasbcdvertical:
/* the time is converted to BCD, the digits are displayed in
* columns */
output[0] = 0;
output[1] = 0;
output[2] = 0;
output[3] = 0;
bcdlow = datetime.time.hour % 10;
bcdhigh = datetime.time.hour / 10;
output[0] |= ((bcdhigh & 8) << 4) | ((bcdlow & 8) << 3);
output[1] |= ((bcdhigh & 4) << 5) | ((bcdlow & 4) << 4);
output[2] |= ((bcdhigh & 2) << 6) | ((bcdlow & 2) << 5);
output[3] |= ((bcdhigh & 1) << 7) | ((bcdlow & 1) << 6);
bcdlow = datetime.time.minute % 10;
bcdhigh = datetime.time.minute / 10;
output[0] |= ((bcdhigh & 8) << 1) | ((bcdlow & 8) << 0);
output[1] |= ((bcdhigh & 4) << 2) | ((bcdlow & 4) << 1);
output[2] |= ((bcdhigh & 2) << 3) | ((bcdlow & 2) << 2);
output[3] |= ((bcdhigh & 1) << 4) | ((bcdlow & 1) << 3);
bcdlow = datetime.time.second % 10;
bcdhigh = datetime.time.second / 10;
output[0] |= ((bcdhigh & 8) >> 2) | ((bcdlow & 8) >> 3);
output[1] |= ((bcdhigh & 4) >> 1) | ((bcdlow & 4) >> 2);
output[2] |= ((bcdhigh & 2) << 0) | ((bcdlow & 2) >> 1);
output[3] |= ((bcdhigh & 1) << 1) | ((bcdlow & 1) << 0);
break;
case dateasbcdvertical:
/* the date is converted to BCD, the digits are displayed in
* columns */
output[0] = 0;
output[1] = 0;
output[2] = 0;
output[3] = 0;
bcdlow = datetime.date.dayofmonth % 10;
bcdhigh = datetime.date.dayofmonth / 10;
output[0] |= ((bcdhigh & 8) << 4) | ((bcdlow & 8) << 3);
output[1] |= ((bcdhigh & 4) << 5) | ((bcdlow & 4) << 4);
output[2] |= ((bcdhigh & 2) << 6) | ((bcdlow & 2) << 5);
output[3] |= ((bcdhigh & 1) << 7) | ((bcdlow & 1) << 6);
bcdlow = datetime.date.month % 10;
bcdhigh = datetime.date.month / 10;
output[0] |= ((bcdhigh & 8) << 1) | ((bcdlow & 8) << 0);
output[1] |= ((bcdhigh & 4) << 2) | ((bcdlow & 4) << 1);
output[2] |= ((bcdhigh & 2) << 3) | ((bcdlow & 2) << 2);
output[3] |= ((bcdhigh & 1) << 4) | ((bcdlow & 1) << 3);
bcdlow = datetime.date.year % 10;
bcdhigh = datetime.date.year / 10;
output[0] |= ((bcdhigh & 8) >> 2) | ((bcdlow & 8) >> 3);
output[1] |= ((bcdhigh & 4) >> 1) | ((bcdlow & 4) >> 2);
output[2] |= ((bcdhigh & 2) << 0) | ((bcdlow & 2) >> 1);
output[3] |= ((bcdhigh & 1) << 1) | ((bcdlow & 1) << 0);
break;
case timestamp:
/* compute the UNIX-Timestamp. This function is based on http://www.mulder.franken.de/ntpdcfledclock/ */
ts = TS01012000 + SECONDSINADAY; /* 2000 was leap year */
ts += SECONDSINADAY * datetime.date.year * 365;
/* Now account for the leap years. Yes, 2000 was a leap year too. */
ts += SECONDSINADAY * ((datetime.date.year - 1) / 4);
ts += SECONDSINADAY * monthstarts[datetime.date.month - 1];
if (((datetime.date.year % 4) == 0) && (datetime.date.month > 2)) {
/* We are in a leap year and past february */
ts += SECONDSINADAY;
}
ts += SECONDSINADAY * (datetime.date.dayofmonth - 1);
ts += 3600UL * datetime.time.hour;
ts += 60 * datetime.time.minute;
ts += datetime.time.second;
output[0] = (ts >> 24);
output[1] = (ts >> 16);
output[2] = (ts >> 8);
output[3] = (ts >> 0);
break;
default:
break;
}
} // function setOutput
/**
* Sets the output to a running light. This is used when no valid time can be
* displayed.
*/
void setWaiting(void) {
static uint8_t position = 0;
output[0] = 0;
output[1] = 0;
output[2] = 0;
output[3] = 0;
if (position < 8) {
output[0] = (1 << position);
} else if (position == 8) {
output[1] = 128;
} else if (position == 9) {
output[2] = 128;
} else if (position == 18) {
output[2] = 1;
} else if (position == 19) {
output[1] = 1;
} else {
output[3] = (128 >> (position - 10));
}
position++;
if (position > 19) {
position = 0;
}
} // function setWaiting
/**
* Timer interrupt function. This is called on every timer-interrupt (which
* happens 488 times per second.
*/
void timerInterrupt(void) {
dcf_datetime datetime; /** takes the current time and date */
static uint8_t tickcounter; /** internal tick, is incremented with every timer-loop */
static uint8_t keycounter = 0; /** used to defeat bouncing buttons */
static uint8_t demomodecounter = 0; /** used to switch to demo mode */
static uint8_t modeswitched = 0; /** set when the mode has been switched, displays bars to indicate the new mode. */
tickcounter++;
/* on every second interrupt, check the state of the DCF-signal */
if (tickcounter % 2) {
if ((PINC & (1 << PINC0))) {
// signal high
dcf_signal(True);
PORTC |= (1 << PINC1);
} else {
// signal low
dcf_signal(False);
PORTC &= ~(1 << PINC1);
}
}
/* on every 32nd interrupt, check if the key is pressed. After 5 loops with
* a pressed key, switch to the next display-mode. */
if (tickcounter % 32 == 0) {
if (!(PINC & (1 << PINC2))) {
// key pressed
keycounter++;
if (keycounter > 4) {
// after 4 cycles with pressed key, switch to the next mode
keycounter = 0;
mode++;
if (mode > timestamp) {
mode = timeasbinary;
}
modeswitched = 5;
}
demomodecounter++;
if (demomodecounter > 75) {
// after 75 cycles with pressed key, switch to or from demo mode
if (demomode == 0) {
demomode = 1;
mode = timeasbinary;
output[0] = 255;
output[1] = 255;
output[2] = 255;
output[3] = 255;
} else {
demomode = 0;
mode = timeasbinary;
output[0] = 255;
output[1] = 129;
output[2] = 129;
output[3] = 255;
}
setLeds();
demomodecounter = 0;
}
} else {
// key unpressed
keycounter = 0;
demomodecounter = 0;
}
}
/* on every 128th interrupt (about 4 times per second), check if anything
* new has to be displayed. */
if (tickcounter % 128 == 0) {
if (demomode == 1) {
// set the current date and time to a fixed value to demonstrate
// how the time is displayed
datetime.is_valid = 1;
datetime.time.hour = 10;
datetime.time.minute = 35;
datetime.time.second = 10;
datetime.date.dayofmonth = 30;
datetime.date.month = 12;
datetime.date.year = 6;
datetime.date.dayofweek = 6;
} else {
datetime = dcf_current_datetime();
}
if (modeswitched > 0) {
output[0] = mode + 1;
output[1] = mode + 1;
output[2] = mode + 1;
output[3] = mode + 1;
modeswitched--;
} else if (datetime.is_valid) {
setOutput(datetime);
} else {
setWaiting();
}
/* finally, set the output */
setLeds();
}
} // function timerInterrupt
/**
* Main-function. Initializes the hardware and starts the main loop of the
* application.
* \return An integer. Whatever... :-)
*/
int main(void) {
uint8_t i;
/* set a default display-mode */
mode = timeasbinary;
/* intialize ports */
DDRC = (1 << DDC1); // set pin 1 to output
PORTC = (1 << PC1) | (1 << PC2); // activate pullups on pins 1 and 2
/* initialize SAA1064 on i2c-bus */
led_init();
set_led_brightness(1);
for (i = 0; i <= 3; i++) { /* switch off all connected LEDs */
set_led_digit(i, 0);
}
/* initialize DCF77 */
dcf_init();
/* initialize timer */
TCCR0 = (0 << CS02) | (1 << CS01) | (0 << CS00); // set divider to 8.
/* The interrupt is called every 8*256 CPU-cycles, at 1MHz we get 488
* calls per second. DCF_RATE in dcftime.h has to be set according to
* this value. */
sei();
while (1) { /* main event loop */
if (TIFR & (1 << TOV0)) {
TIFR |= 1 << TOV0; /* clear pending flag */
timerInterrupt();
}
}
return 0;
}