/** * \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"; } }