445 lines
18 KiB
C
445 lines
18 KiB
C
/**
|
|
* \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";
|
|
}
|
|
}
|