355 lines
12 KiB
C
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;
|
||
|
}
|