First CVS-version

This commit is contained in:
2007-01-02 21:30:40 +00:00
parent 62a932ecdd
commit 716d8a0a23
17 changed files with 3047 additions and 0 deletions

50
firmware/Makefile Normal file
View File

@ -0,0 +1,50 @@
# $Id: Makefile,v 1.1 2007/01/02 21:30:40 rschaten Exp $
# microcontroller settings
F_CPU = 1000000UL
MCU = atmega8
AVRDUDE = avrdude -p $(MCU) -P /dev/parport0 -c stk200 -E noreset,vcc
AVRDUDE = avrdude -p $(MCU) -P /dev/tts/USB0 -b 115200 -c avr109
COMPILE = avr-gcc -Wall -Os -I../common -I. -mmcu=$(MCU) -DF_CPU=$(F_CPU) #-DDEBUG_LEVEL=2
OBJECTS = saa1064.o dcftime.o main.o
# symbolic targets:
all: main.hex
.c.o:
$(COMPILE) -c $< -o $@
.S.o:
$(COMPILE) -x assembler-with-cpp -c $< -o $@
# "-x assembler-with-cpp" should not be necessary since this is the default
# file type for the .S (with capital S) extension. However, upper case
# characters are not always preserved on Windows. To ensure WinAVR
# compatibility define the file type manually.
.c.s:
$(COMPILE) -S $< -o $@
program: all
$(AVRDUDE) -U flash:w:main.hex
clean:
rm -f main.hex main.lst main.obj main.cof main.list main.map main.eep.hex main.bin *.o main.s
# file targets:
main.bin: $(OBJECTS)
$(COMPILE) -o main.bin $(OBJECTS)
main.hex: main.bin
rm -f main.hex main.eep.hex
avr-objcopy -j .text -j .data -O ihex main.bin main.hex
disasm: main.bin
avr-objdump -d main.bin
cpp:
$(COMPILE) -E main.c

33
firmware/boole.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef BOOLE_H
#define BOOLE_H
/**
* \file boole.h
* \brief Simple boolean variables
* \author Thomas Stegemann
* \version $Id: boole.h,v 1.1 2007/01/02 21:30:40 rschaten Exp $
*
* License: See documentation.
*/
enum boolean_enum { False = 0, True = 1 };
typedef enum boolean_enum boolean;
static inline boolean boole(int test) {
if (test == 0) {
return False;
} else {
return True;
}
}
static inline const char *boolean_name(boolean value) {
if (value == False) {
return "false";
} else {
return "true";
}
}
#endif /* BOOLE_H */

444
firmware/dcftime.c Normal file
View File

@ -0,0 +1,444 @@
/**
* \file dcftime.c
* \brief Decoder for DCF-77 time signals
* \author Ronald Schaten & Thomas Stegemann
* \version $Id: dcftime.c,v 1.1 2007/01/02 21:30:40 rschaten Exp $
*
* License: See documentation.
*/
#include "boole.h"
#include "dcftime.h"
// TODO: define and use meaningful states for certain situations (valid time, no values received, etc.)
// TODO: find correct start of the seconds. ATM the clock is running late by one second
// TODO: check if it is possible to give DCF_RATE as parameter for init()
typedef unsigned int dcf_sample; /**< number of the current sample */
typedef unsigned int dcf_sizetype; /**< used for the size of a month */
const dcf_sample dcf_second_samples = (DCF_RATE); /**< number of samples per second */
/** dcf signal between 30ms and 130ms => dcf logic false (lower value) */
const dcf_sample dcf_logic_false_min = (DCF_RATE)*3/100;
/** dcf signal between 30ms and 130ms => dcf logic false (upper value) */
const dcf_sample dcf_logic_false_max = (DCF_RATE)*13/100;
/** dcf signal between 140ms and 230ms => dcf logic true (lower value) */
const dcf_sample dcf_logic_true_min = (DCF_RATE)*14/100;
/** dcf signal between 140ms and 230ms => dcf logic true (upper value) */
const dcf_sample dcf_logic_true_max = (DCF_RATE)*23/100;
/** duration between begin of dcf second (== begin of signal), should be 1 * second +/- 3% (lower value) */
const dcf_sample dcf_second_tolerance_min = (DCF_RATE) - (DCF_RATE)*3/100;
/** duration between begin of dcf second (== begin of signal), should be 1 * second +/- 3% (upper value) */
const dcf_sample dcf_second_tolerance_max = (DCF_RATE) + (DCF_RATE)*3/100;
/** definition of logical signal states */
enum dcf_logic_signal_enum {
dcf_signal_no, /**< no signal */
dcf_signal_false, /**< 'false' signal */
dcf_signal_true, /**< 'true' signal */
dcf_signal_invalid /**< invalid signal */
};
/** definition of logical signal states */
typedef enum dcf_logic_signal_enum dcf_logic_signal;
/** format of the received data, filled during reception */
struct dcf_receiving_data_struct {
dcf_date date; /**< date */
dcf_time time; /**< time */
boolean parity; /**< parity of the received data */
boolean is_valid; /**< data is valid */
dcf_logic_signal current_signal; /**< logical state of the received data */
dcf_sample low_samples; /**< counts low signal samples per second */
dcf_sample high_samples; /**< counts high signal samples per second */
};
/** definition of the received data, filled during reception */
typedef struct dcf_receiving_data_struct dcf_receiving_data;
/** format of the DCF data.
* dcf_current_datetime() and dcf_sample() may be called from different contexts. To avoid changing the current_datetime while it is read:
* if use_first_current_datetime is true: dcf_current_datetime reads current_datetime[0] and dcf_sample changes current_datetime[1]
* if use_first_current_datetime is false: vice versa
*/
struct dcf_data_struct {
dcf_datetime current_datetime[2]; /**< two full datasets */
boolean use_first_current_datetime; /**< flag if the first or the second dataset is used */
dcf_sample current_datetime_sample; /**< number of the current sample */
dcf_receiving_data receiving_data; /**< data being filled */
};
/*
global data
*/
static struct dcf_data_struct dcf_data; /**< full set of received dcf data */
/*
dcf_time
*/
/**
* Initialize a dcf_time value.
* \param pTime: pointer to a dcf_time variable
*/
static void dcf_time_init(dcf_time * pTime) {
pTime->second = 0;
pTime->minute = 0;
pTime->hour = 0;
pTime->is_dst = False;
}
/**
* Increment a time-value by one second.
* \param pTime: pointer to a dcf_time variable
* \return True if the date has to be incremented, too. Otherwise False.
*/
static boolean dcf_time_inc(dcf_time * pTime) {
++(pTime->second);
if (pTime->second == 60) {
pTime->second = 0;
++(pTime->minute);
if (pTime->minute == 60) {
pTime->minute = 0;
++(pTime->hour);
if (pTime->hour == 24) {
pTime->hour = 0;
return True; /* overflow => increment date */
}
}
}
return False;
}
/**
* Check if a time-value makes sense.
* \param pTime: pointer to a dcf_time variable
* \return True if the time is logically correct. Otherwise False.
*/
static boolean dcf_time_is_valid(dcf_time * pTime) {
return (pTime->second <= 60)
&& (pTime->minute <= 60)
&& (pTime->hour <= 24);
}
/*
dcf_date
*/
/**
* Initialize a dcf_date value.
* \param pDate: pointer to a dcf_date variable
*/
static void dcf_date_init(dcf_date * pDate) {
pDate->dayofweek = dcf_sunday;
pDate->dayofmonth = 1;
pDate->month = dcf_january;
pDate->year = 0;
}
/**
* Calculate the number of days in a month.
* \param pDate: pointer to a dcf_time variable
* \return The number of days in the given month.
*/
static dcf_sizetype dcf_date_days_in_month(dcf_date * pDate) {
switch (pDate->month) {
case dcf_february:
if (pDate->year % 4 != 0)
return 28; /* year not divisible by 4 */
else if (pDate->year != 0)
return 29; /* year divisible by 4 and not divisible by 100 */
else if (((pDate->dayofmonth % 7) + 1) != pDate->dayofweek)
return 28; /* year divisible by 100 and not divisible by 400 */
else
return 29; /* year divisible by 400 */
/*
if year is divisble by 400 (eg year 2000) the 1st february is a tuesday (== 2 (== 1+1))
if year divided by 400 remains 100 1st February is a monday
if year divided by 400 remains 200 1st February is a saturday
if year divided by 400 remains 300 1st February is a thursday
this repeats every 400 years, because 400 year are 3652425/25 day
which is 7*521775/25, therefore divisible by 7
which means every 400 years the day of week are the same
! dayofmonth and dayofweek must be synchronized to get the right value
*/
case dcf_april:
case dcf_june:
case dcf_september:
case dcf_november:
return 30;
default:
return 31;
}
}
/**
* Increment a date-value by one day.
* \param pDate: pointer to a dcf_date variable
*/
static void dcf_date_inc(dcf_date * pDate) {
++(pDate->dayofweek);
if (pDate->dayofweek == 8) {
pDate->dayofweek = 1;
}
++(pDate->dayofmonth);
if (pDate->dayofmonth == (dcf_date_days_in_month(pDate) + 1)) {
pDate->dayofmonth = 1;
++(pDate->month);
if (pDate->month == 13) {
pDate->month = 1;
++(pDate->year);
if (pDate->year == 100) {
pDate->year = 0;
}
}
}
}
/**
* Check if a date-value makes sense.
* \param pDate: pointer to a dcf_date variable
* \return True if the date is logically correct. Otherwise False.
*/
static boolean dcf_date_is_valid(dcf_date * pDate) {
return (1 <= pDate->dayofweek)
&& (pDate->dayofweek <= 7)
&& (1 <= pDate->dayofmonth)
&& (pDate->dayofmonth <= dcf_date_days_in_month(pDate))
&& (1 <= pDate->month)
&& (pDate->month <= 12)
&& (pDate->year <= 99);
}
/*
dcf_datetime
*/
/**
* Initialize a dcf_datetime value.
* \param pDatetime: pointer to a dcf_datetime variable
*/
static void dcf_datetime_init(dcf_datetime * pDatetime) {
pDatetime->is_valid = False;
pDatetime->has_signal = False;
dcf_time_init(&(pDatetime->time));
dcf_date_init(&(pDatetime->date));
}
/**
* Increment a datetime-value by one second.
* \param pDatetime: pointer to a dcf_datetime variable
*/
static void dcf_datetime_inc(dcf_datetime * pDatetime) {
if (dcf_time_inc(&(pDatetime->time))) {
dcf_date_inc(&(pDatetime->date));
}
}
/*
dcf_receiving_data
*/
/**
* Initialize a dcf_receiving_data value.
* \param pReceive: pointer to a dcf_receiving_data variable
*/
static void dcf_receiving_data_init(dcf_receiving_data * pReceive) {
pReceive->current_signal = dcf_signal_no;
pReceive->parity = False;
pReceive->is_valid = True;
pReceive->low_samples = 0;
pReceive->high_samples = 0;
dcf_time_init(&(pReceive->time));
dcf_date_init(&(pReceive->date));
}
/**
* Calculate the time and date while the bits are received.
* \param signal: True if the received bit is 200ms, False if the bit is 100ms.
*/
static void dcf_logic(boolean signal) {
dcf_data.receiving_data.parity ^= signal;
switch (dcf_data.receiving_data.time.second) {
case 16: dcf_data.receiving_data.parity = True; break;
case 17: dcf_data.receiving_data.time.is_dst = signal; break;
case 18: if(dcf_data.receiving_data.parity) dcf_data.receiving_data.is_valid = False; break;
case 19: dcf_data.receiving_data.parity = True; break;
case 20: if(!signal) dcf_data.receiving_data.is_valid = False; break;
case 21: dcf_data.receiving_data.time.minute = signal ? 1 : 0; break;
case 22: dcf_data.receiving_data.time.minute += signal ? 2 : 0; break;
case 23: dcf_data.receiving_data.time.minute += signal ? 4 : 0; break;
case 24: dcf_data.receiving_data.time.minute += signal ? 8 : 0; break;
case 25: dcf_data.receiving_data.time.minute += signal ? 10 : 0; break;
case 26: dcf_data.receiving_data.time.minute += signal ? 20 : 0; break;
case 27: dcf_data.receiving_data.time.minute += signal ? 40 : 0; break;
case 28: if(dcf_data.receiving_data.parity) dcf_data.receiving_data.is_valid = False; break;
case 29: dcf_data.receiving_data.time.hour = signal ? 1 : 0; break;
case 30: dcf_data.receiving_data.time.hour += signal ? 2 : 0; break;
case 31: dcf_data.receiving_data.time.hour += signal ? 4 : 0; break;
case 32: dcf_data.receiving_data.time.hour += signal ? 8 : 0; break;
case 33: dcf_data.receiving_data.time.hour += signal ? 10 : 0; break;
case 34: dcf_data.receiving_data.time.hour += signal ? 20 : 0; break;
case 35: if(dcf_data.receiving_data.parity) dcf_data.receiving_data.is_valid = False; break;
case 36: dcf_data.receiving_data.date.dayofmonth = signal ? 1 : 0; break;
case 37: dcf_data.receiving_data.date.dayofmonth += signal ? 2 : 0; break;
case 38: dcf_data.receiving_data.date.dayofmonth += signal ? 4 : 0; break;
case 39: dcf_data.receiving_data.date.dayofmonth += signal ? 8 : 0; break;
case 40: dcf_data.receiving_data.date.dayofmonth += signal ? 10 : 0; break;
case 41: dcf_data.receiving_data.date.dayofmonth += signal ? 20 : 0; break;
case 42: dcf_data.receiving_data.date.dayofweek = signal ? 1 : 0; break;
case 43: dcf_data.receiving_data.date.dayofweek += signal ? 2 : 0; break;
case 44: dcf_data.receiving_data.date.dayofweek += signal ? 4 : 0; break;
case 45: dcf_data.receiving_data.date.month = signal ? 1 : 0; break;
case 46: dcf_data.receiving_data.date.month += signal ? 2 : 0; break;
case 47: dcf_data.receiving_data.date.month += signal ? 4 : 0; break;
case 48: dcf_data.receiving_data.date.month += signal ? 8 : 0; break;
case 49: dcf_data.receiving_data.date.month += signal ? 10 : 0; break;
case 50: dcf_data.receiving_data.date.year = signal ? 1 : 0; break;
case 51: dcf_data.receiving_data.date.year += signal ? 2 : 0; break;
case 52: dcf_data.receiving_data.date.year += signal ? 4 : 0; break;
case 53: dcf_data.receiving_data.date.year += signal ? 8 : 0; break;
case 54: dcf_data.receiving_data.date.year += signal ? 10 : 0; break;
case 55: dcf_data.receiving_data.date.year += signal ? 20 : 0; break;
case 56: dcf_data.receiving_data.date.year += signal ? 40 : 0; break;
case 57: dcf_data.receiving_data.date.year += signal ? 80 : 0; break;
case 58: if(dcf_data.receiving_data.parity) dcf_data.receiving_data.is_valid = False; break;
}
++(dcf_data.receiving_data.time.second);
}
/**
* Copy the values from receiving_data to current_datetime.
*/
static void dcf_store(void) {
if ((dcf_data.receiving_data.is_valid)
&& dcf_time_is_valid(&(dcf_data.receiving_data.time))
&& dcf_date_is_valid(&(dcf_data.receiving_data.date))) {
dcf_data.current_datetime_sample = 0;
if (dcf_data.use_first_current_datetime) {
dcf_data.current_datetime[1].time = dcf_data.receiving_data.time;
dcf_data.current_datetime[1].date = dcf_data.receiving_data.date;
dcf_data.current_datetime[1].is_valid = True;
dcf_data.use_first_current_datetime = False;
} else {
dcf_data.current_datetime[0].time = dcf_data.receiving_data.time;
dcf_data.current_datetime[0].date = dcf_data.receiving_data.date;
dcf_data.current_datetime[0].is_valid = True;
dcf_data.use_first_current_datetime = True;
}
}
}
/**
* Copy valid time and increment it.
*/
static void dcf_inc(void) {
if (dcf_data.use_first_current_datetime) {
dcf_data.current_datetime[1] = dcf_data.current_datetime[0];
dcf_datetime_inc(&(dcf_data.current_datetime[1]));
dcf_data.use_first_current_datetime = False;
} else {
dcf_data.current_datetime[0] = dcf_data.current_datetime[1];
dcf_datetime_inc(&(dcf_data.current_datetime[0]));
dcf_data.use_first_current_datetime = True;
}
}
/*
exported functions, documented in header file
*/
void dcf_init(void) {
dcf_data.use_first_current_datetime = True;
dcf_data.current_datetime_sample = 0;
dcf_datetime_init(&(dcf_data.current_datetime[0]));
dcf_datetime_init(&(dcf_data.current_datetime[1]));
dcf_receiving_data_init(&(dcf_data.receiving_data));
}
void dcf_signal(boolean signal) {
if (dcf_data.receiving_data.low_samples > dcf_second_samples) {
if (dcf_data.receiving_data.time.second == 59) {
dcf_data.receiving_data.time.second = 0;
dcf_store();
} else {
dcf_data.receiving_data.time.second = 0;
}
dcf_data.receiving_data.low_samples = 0;
dcf_data.receiving_data.is_valid = True;
}
/* calculate receiving date time */
if (signal) {
dcf_data.receiving_data.low_samples = 0;
++(dcf_data.receiving_data.high_samples);
} else {
++(dcf_data.receiving_data.low_samples);
if (dcf_data.receiving_data.high_samples == 0) {
} else if (dcf_data.receiving_data.high_samples < dcf_logic_false_min) {
/* too short signal */
dcf_data.receiving_data.is_valid = False;
dcf_data.receiving_data.current_signal = dcf_signal_invalid;
} else if (dcf_data.receiving_data.high_samples < dcf_logic_false_max) {
/* short signal, logic 0 */
dcf_logic(False);
dcf_data.receiving_data.current_signal = dcf_signal_false;
} else if (dcf_data.receiving_data.high_samples < dcf_logic_true_min) {
/* signal cannot be assigned to true or false */
dcf_data.receiving_data.is_valid = False;
dcf_data.receiving_data.current_signal = dcf_signal_invalid;
} else if (dcf_data.receiving_data.high_samples < dcf_logic_true_max) {
/* long signal, logic 1 */
dcf_logic(True);
dcf_data.receiving_data.current_signal = dcf_signal_true;
} else {
/* too long signal */
dcf_data.receiving_data.is_valid = False;
dcf_data.receiving_data.current_signal = dcf_signal_invalid;
}
dcf_data.receiving_data.high_samples = 0;
}
/* calculate current date time */
++(dcf_data.current_datetime_sample);
if (dcf_data.current_datetime_sample == dcf_second_samples) {
dcf_data.current_datetime_sample = 0;
dcf_inc();
}
}
dcf_datetime dcf_current_datetime(void) {
if (dcf_data.use_first_current_datetime) {
dcf_data.current_datetime[0].has_signal = dcf_data.receiving_data.is_valid;
return dcf_data.current_datetime[0];
} else {
dcf_data.current_datetime[1].has_signal = dcf_data.receiving_data.is_valid;
return dcf_data.current_datetime[1];
}
}
const char *dcf_dayofweek_name(dcf_dayofweek dow) {
switch (dow) {
case 1:
return "Mo";
case 2:
return "Tu";
case 3:
return "We";
case 4:
return "Th";
case 5:
return "Fr";
case 6:
return "Sa";
case 7:
return "Su";
default:
return "??";
}
}
const char *dcf_is_dst_name(dcf_is_dst dst) {
if (dst) {
return "ST";
} else {
return "WT";
}
}

127
firmware/dcftime.h Normal file
View File

@ -0,0 +1,127 @@
#ifndef DCFTIME_H
#define DCFTIME_H
/**
* \file dcftime.h
* \brief Decoder for DCF-77 time signals
* \author Ronald Schaten & Thomas Stegemann
* \version $Id: dcftime.h,v 1.1 2007/01/02 21:30:40 rschaten Exp $
*
* License: See documentation.
*/
#include "boole.h"
/*
dcf-signal samples per second
*/
#ifndef DCF_RATE
#define DCF_RATE 244 /**< number of samples per second. dcf_signal() should be called this often */
#endif
#if (DCF_RATE < 100) || (250 < DCF_RATE)
#error DCF_RATE should be between 100 and 250
#endif
typedef unsigned int dcf_second; /**< seconds (0-59) */
typedef unsigned int dcf_minute; /**< minutes (0-59) */
typedef unsigned int dcf_hour; /**< hours (0-24) */
typedef unsigned int dcf_dayofmonth; /**< day of month (1-31) */
typedef unsigned int dcf_year; /**< year (0-99) */
typedef boolean dcf_is_dst; /**< daylight saving: True: MESZ, False: MEZ */
/** definition of weekdays */
enum dcf_dayofweek_enum {
dcf_monday = 1, /**< monday = 1 */
dcf_tuesday, /**< tuesday */
dcf_wednesday, /**< wednesday */
dcf_thursday, /**< thursday */
dcf_friday, /**< friday */
dcf_saturday, /**< saturday */
dcf_sunday, /**< sunday = 7 */
};
/** definition of weekdays */
typedef enum dcf_dayofweek_enum dcf_dayofweek;
/** definition of months */
enum dcf_month_enum {
dcf_january = 1, /**< january = 1 */
dcf_february, /**< february */
dcf_march, /**< march */
dcf_april, /**< april */
dcf_may, /**< may */
dcf_june, /**< june */
dcf_july, /**< july */
dcf_august, /**< august */
dcf_september, /**< september */
dcf_october, /**< october */
dcf_november, /**< november */
dcf_december /**< december = 12 */
};
/** definition of months */
typedef enum dcf_month_enum dcf_month;
/** format of the dcf_time */
struct dcf_time_struct {
dcf_second second; /**< seconds */
dcf_minute minute; /**< minutes */
dcf_hour hour; /**< hours */
dcf_is_dst is_dst; /**< daylight saving time */
};
/** definition of dcf_time */
typedef struct dcf_time_struct dcf_time;
/** format of the dcf_date */
struct dcf_date_struct {
dcf_dayofweek dayofweek; /**< day of week */
dcf_dayofmonth dayofmonth; /**< day of month */
dcf_month month; /**< month */
dcf_year year; /**< year */
};
/** definition of dcf_date */
typedef struct dcf_date_struct dcf_date;
/** format of the dcf_datetime */
struct dcf_datetime_struct {
dcf_time time; /**< the time */
dcf_date date; /**< the time */
boolean is_valid; /**< if is_valid is False: no complete signal received, do not use date and times */
boolean has_signal; /**< if has_signal is True: currently receiving signal */
};
/** definition of dcf_datetime */
typedef struct dcf_datetime_struct dcf_datetime;
/**
* Initialize the DCF-module. Call dcf_init before any other DCF function.
*/
void dcf_init(void);
/**
* Tell the DCF-module if the signal is high or low. This function decides if
* the received bit is a long or a short one, and if it is usable at all. It
* should be called regularly, the number of calls per second is defined in
* DCF_RATE.
* \param signal: True if the input signal is high, False if it is low.
*/
void dcf_signal(boolean signal);
/**
* Fetch the current date and time.
* \return The current date and time in a dcf_datetime structure
*/
dcf_datetime dcf_current_datetime(void);
/**
* Get the name of the current weekday.
* \param dow: Day of the current week. Monday = 1, tuesday = 2...
* \return Pointer to the name
*/
const char* dcf_dayofweek_name(dcf_dayofweek dow);
/**
* Get the name of the current daylight saving time (summertime, wintertime).
* \param dst: daylight saving time bit from the time signal
* \return Pointer to the name
*/
const char* dcf_is_dst_name(dcf_is_dst dst);
#endif

354
firmware/main.c Normal file
View File

@ -0,0 +1,354 @@
/**
* \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;
}

130
firmware/saa1064.c Normal file
View File

@ -0,0 +1,130 @@
/**
* \file saa1064.c
* \brief I2C-connection to the SAA1064 LED-driver
* \author Ronald Schaten
* \version $Id: saa1064.c,v 1.1 2007/01/02 21:30:40 rschaten Exp $
*
* License: See documentation.
*/
/* based on http://www.mulder.franken.de/ntpdcfledclock/ */
#include <avr/io.h>
#include <util/delay.h>
#include "saa1064.h"
/* The Port used for the connection */
#define LEDPORT PORTC
#define LEDPIN PINC
#define LEDDDR DDRC
/* Which pins of the port */
#define SDAPIN PC4
#define SCLPIN PC5
/* the I2C addresses of the SAA 1064 LED drivers */
#define SAA_AD1 0x70 // or 0x76?
#define I2C_READ 0x01
#define I2C_WRITE 0x00
/* Should be at least 27 (80 / 3) at 8 MHz */
/* This was the conservative value used for testing. However, half as much should work as well. */
#define DELAYVAL 3
void led_init(void) {
/* activate pullups */
LEDPORT |= (1 << SCLPIN) | (1 << SDAPIN);
}
/* Send START, defined as high-to-low SDA with SCL high.
* Expects SCL and SDA to be high already!
* Returns with SDA and SCL low. */
static void I2C_start(void) {
/* Change to output mode. */
LEDDDR |= (1 << SDAPIN) | (1 << SCLPIN);
/* change SDA to low */
LEDPORT &= ~(1 << SDAPIN);
_delay_loop_1(DELAYVAL);
/* and SCL too */
LEDPORT &= ~(1 << SCLPIN);
_delay_loop_1(DELAYVAL);
}
/* Send STOP, defined as low-to-high SDA with SCL high.
* Expects SCL and SDA to be low already!
* Returns with SDA and SCL high. */
static void I2C_stop(void) {
/* Set SCL */
LEDPORT |= (1 << SCLPIN);
_delay_loop_1(DELAYVAL);
/* Set SDA */
LEDPORT |= (1 << SDAPIN);
_delay_loop_1(DELAYVAL);
/* Probably safer to tristate the bus */
LEDDDR &= ~((1 << SDAPIN) | (1 << SCLPIN));
}
/* Transmits the byte in what.
* Returns 1 if the byte was ACKed, 0 if not.
* Expects SCL and SDA to be low already!
* Returns with SDA and SCL low. */
static uint8_t I2C_transmit_byte(uint8_t what) {
uint8_t i;
for (i = 0; i < 8; i++) {
/* First put data on the bus */
if (what & 0x80) {
LEDPORT |= (1 << SDAPIN);
}
_delay_loop_1(DELAYVAL);
/* Then set SCL high */
LEDPORT |= (1 << SCLPIN);
_delay_loop_1(DELAYVAL);
/* Take SCL back */
LEDPORT &= ~(1 << SCLPIN);
_delay_loop_1(DELAYVAL);
/* And SDA too */
LEDPORT &= ~(1 << SDAPIN);
_delay_loop_1(DELAYVAL);
what <<= 1;
}
/* OK that was the data, now we read back the ACK */
/* We need to tristate SDA for that */
LEDPORT |= (1 << SDAPIN);
LEDDDR &= ~(1 << SDAPIN);
/* Give the device some time */
_delay_loop_1(DELAYVAL);
_delay_loop_1(DELAYVAL);
_delay_loop_1(DELAYVAL);
/* Then set SCL high */
LEDPORT |= (1 << SCLPIN);
_delay_loop_1(DELAYVAL);
_delay_loop_1(DELAYVAL);
_delay_loop_1(DELAYVAL);
i = LEDPIN & (1 << SDAPIN); /* Read ACK */
/* Take SCL back */
LEDPORT &= ~(1 << SCLPIN);
_delay_loop_1(DELAYVAL);
/* No more tristate, we pull SDA again */
LEDPORT &= ~(1 << SDAPIN);
LEDDDR |= (1 << SDAPIN);
_delay_loop_1(DELAYVAL);
return (i == 0);
}
void set_led_digit(uint8_t digit, uint8_t val) {
I2C_start();
/* Address device */
I2C_transmit_byte(SAA_AD1 | I2C_WRITE);
I2C_transmit_byte((digit & 3) + 1); /* Address Digit Register on device */
I2C_transmit_byte(val); /* Send value for Digit */
I2C_stop();
}
void set_led_brightness(uint8_t led_brightness) {
I2C_start();
I2C_transmit_byte(SAA_AD1 | I2C_WRITE); /* Address first driver */
I2C_transmit_byte(0); /* Address Config Register on device */
I2C_transmit_byte(((led_brightness & 0x07) << 4) | 0x07); /* Send Settings */
I2C_stop();
}

27
firmware/saa1064.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef _SAA1064_H_
#define _SAA1064_H_
/**
* \file saa1064.h
* \brief I2C-connection to the SAA1064 LED-driver
* \author Ronald Schaten
* \version $Id: saa1064.h,v 1.1 2007/01/02 21:30:40 rschaten Exp $
*
* License: See documentation.
*/
/* based on http://www.mulder.franken.de/ntpdcfledclock/ */
/* This sets one digit on the LED module.
* digit is the number of the digit (0 - 7)
* val is a bitfield that contains the values to set. */
void set_led_digit(uint8_t digit, uint8_t val);
/* Configures the brightness of the LED module, or rather: the current the driver allows through them.
* The values 0 through 7 can be used, corresponding to 0 through 21 mA */
void set_led_brightness(uint8_t led_brightness);
/* Initialize the LED module... This basically enables the pullups on the I2C Bus pins */
void led_init(void);
#endif