282 lines
13 KiB
C
282 lines
13 KiB
C
/** library to get time from a DCF77 module
|
|
* @file
|
|
* @author King Kévin <kingkevin@cuvoodoo.info>
|
|
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
|
|
* @date 2016-2020
|
|
* @note peripherals used: GPIO @ref rtc_dcf77_gpio, timer @ref rtc_dcf77_timer
|
|
*/
|
|
|
|
/* standard libraries */
|
|
#include <stdint.h> // standard integer types
|
|
#include <stdlib.h> // general utilities
|
|
|
|
/* STM32 (including CM3) libraries */
|
|
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
|
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
|
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
|
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
|
#include <libopencm3/stm32/spi.h> // SPI library
|
|
#include <libopencm3/stm32/timer.h> // timer library
|
|
|
|
#include "rtc_dcf77.h" // RTC DCF77 library API
|
|
#include "global.h" // common methods
|
|
|
|
/** @defgroup rtc_dcf77_gpio output to enable DCF module and input to capture DCF signal
|
|
* @{
|
|
*/
|
|
#define RTC_DCF77_ENABLE_PIN PA2 /**< GPIO pinto enable the module */
|
|
#define RTC_DCF77_SIGNAL_PIN PA3 /**< GPIO pin to capture the DCF signal */
|
|
/** @} */
|
|
|
|
/** @defgroup rtc_dcf77_timer timer to sample DCF77 signal
|
|
* @{
|
|
*/
|
|
#define RTC_DCF77_TIMER 4 /**< timer peripheral */
|
|
/** @} */
|
|
|
|
volatile bool rtc_dcf77_time_flag = false;
|
|
struct rtc_dcf77_time_t rtc_dcf77_time = {
|
|
.valid = false,
|
|
.milliseconds = 0,
|
|
.seconds = 0,
|
|
.minutes = 0,
|
|
.hours = 0,
|
|
.day = 0,
|
|
.weekday = 0,
|
|
.month = 0,
|
|
.year = 0,
|
|
};
|
|
|
|
/** the received DCF77 frame bits */
|
|
static volatile uint64_t rtc_dcf77_frame = 0;
|
|
/** values of the DCF77 signal over 10 ms for 1 s (how many times it is high) */
|
|
static uint8_t rtc_dcf77_bins[100] = {0};
|
|
/** the bin shift for the bit phase */
|
|
static uint8_t rtc_dcf77_phase = 0;
|
|
/** the maximum phase value */
|
|
static int16_t rtc_dcf77_phase_max = INT16_MIN;
|
|
/** if the current phase has been verified */
|
|
static bool rtc_dcf77_phase_locked = false;
|
|
/** number of invalid decoding */
|
|
static uint8_t rtc_dcf77_invalid = 0;
|
|
/** maximum number of invalid decoding before switching off */
|
|
#define RTC_DCF77_INVALID_MAX 5
|
|
|
|
void rtc_dcf77_setup(void)
|
|
{
|
|
// setup enable output
|
|
rcc_periph_clock_enable(GPIO_RCC(RTC_DCF77_ENABLE_PIN)); // enable clock GPIO peripheral
|
|
gpio_set_mode(GPIO_PORT(RTC_DCF77_ENABLE_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(RTC_DCF77_ENABLE_PIN)); // set pin to output push-pull to be able to enable the module
|
|
rtc_dcf77_off(); // disable module at start
|
|
|
|
// setup signal input
|
|
rcc_periph_clock_enable(GPIO_RCC(RTC_DCF77_SIGNAL_PIN)); // enable clock for signal input peripheral
|
|
gpio_set_mode(GPIO_PORT(RTC_DCF77_SIGNAL_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(RTC_DCF77_SIGNAL_PIN)); // set signal pin to input
|
|
|
|
// setup timer to sample signal at 1kHz
|
|
rcc_periph_clock_enable(RCC_TIM(RTC_DCF77_TIMER)); // enable clock for timer peripheral
|
|
rcc_periph_reset_pulse(RST_TIM(RTC_DCF77_TIMER)); // reset timer state
|
|
timer_set_mode(TIM(RTC_DCF77_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
|
|
timer_set_prescaler(TIM(RTC_DCF77_TIMER), 1); // set prescaler to divide frequency by two, to be able to have a period of 1 kHz (72MHz/2/2^16=549Hz<1kHz)
|
|
timer_set_period(TIM(RTC_DCF77_TIMER), rcc_ahb_frequency / 2 / 1000 - 1); // set period to 1kHz (plus hand tuning)
|
|
timer_clear_flag(TIM(RTC_DCF77_TIMER), TIM_SR_UIF); // clear update event flag
|
|
timer_update_on_overflow(TIM(RTC_DCF77_TIMER)); // only use counter overflow as UEV source
|
|
timer_enable_irq(TIM(RTC_DCF77_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
|
|
nvic_enable_irq(NVIC_TIM_IRQ(RTC_DCF77_TIMER)); // catch interrupt in service routine
|
|
}
|
|
|
|
void rtc_dcf77_on(void)
|
|
{
|
|
if (!gpio_get(GPIO_PORT(RTC_DCF77_ENABLE_PIN), GPIO_PIN(RTC_DCF77_ENABLE_PIN))) { // receiver is already turned on
|
|
return; // do nothing
|
|
}
|
|
rtc_dcf77_frame = 0; // reset frame
|
|
rtc_dcf77_phase_locked = false; // reset phase lock
|
|
rtc_dcf77_phase_max = INT16_MIN; // restart searching for phase
|
|
rtc_dcf77_invalid = 0; // reset invalid count
|
|
gpio_clear(GPIO_PORT(RTC_DCF77_ENABLE_PIN), GPIO_PIN(RTC_DCF77_ENABLE_PIN)); // enable module by pulling pin low
|
|
timer_set_counter(TIM(RTC_DCF77_TIMER), 0); // reset timer counter
|
|
timer_enable_counter(TIM(RTC_DCF77_TIMER)); // start timer to sample signal
|
|
}
|
|
|
|
void rtc_dcf77_off(void)
|
|
{
|
|
gpio_set(GPIO_PORT(RTC_DCF77_ENABLE_PIN), GPIO_PIN(RTC_DCF77_ENABLE_PIN)); // disable module by pull pin high
|
|
timer_disable_counter(TIM(RTC_DCF77_TIMER)); // stop timer since we don't need to sample anymore
|
|
}
|
|
|
|
/** decode rtc_dcf77_frame DCF77 frame into rtc_dcf77_time DCF77 time
|
|
* @note check valid for validity
|
|
*/
|
|
static void rtc_dcf77_decode(void)
|
|
{
|
|
rtc_dcf77_time.valid = false; // reset validity
|
|
|
|
if (rtc_dcf77_frame == 0) { // no time received yet
|
|
return;
|
|
}
|
|
if (!(rtc_dcf77_frame & (1ULL << 20))) { // start of encoded time should always be 1
|
|
return;
|
|
}
|
|
|
|
// check minute parity
|
|
uint8_t parity = 0; // to check parity
|
|
for (uint8_t bit = 21; bit <= 28; bit++) {
|
|
if (rtc_dcf77_frame & (1ULL << bit)) {
|
|
parity++; // count the set bits
|
|
}
|
|
}
|
|
if (parity%2) { // parity should be even
|
|
return;
|
|
}
|
|
rtc_dcf77_time.minutes = 1 * ((rtc_dcf77_frame >> 21) & (0x1)) + 2 * ((rtc_dcf77_frame >> 22) & (0x1)) + 4 * ((rtc_dcf77_frame >> 23) & (0x1)) + 8 * ((rtc_dcf77_frame >> 24) & (0x1)) + 10 * ((rtc_dcf77_frame >> 25) & (0x1)) + 20 * ((rtc_dcf77_frame >> 26) & (0x1)) + 40 * ((rtc_dcf77_frame >> 27) & (0x1)); // read minute (00-59)
|
|
if (rtc_dcf77_time.minutes > 59) { // minutes should not be more than 59
|
|
return;
|
|
}
|
|
|
|
// check hour parity
|
|
parity = 0;
|
|
for (uint8_t bit = 29; bit <= 35; bit++) {
|
|
if (rtc_dcf77_frame & (1ULL << bit)) {
|
|
parity++; // count the set bits
|
|
}
|
|
}
|
|
if (parity % 2) { // parity should be even
|
|
return;
|
|
}
|
|
rtc_dcf77_time.hours = 1 * ((rtc_dcf77_frame >> 29) & (0x1)) + 2 * ((rtc_dcf77_frame >> 30) & (0x1)) + 4 * ((rtc_dcf77_frame >> 31) & (0x1)) + 8 * ((rtc_dcf77_frame >> 32) & (0x1)) + 10 * ((rtc_dcf77_frame >> 33) & (0x1)) + 20 * ((rtc_dcf77_frame >> 34) & (0x1)); // read hour (00-23)
|
|
if (rtc_dcf77_time.hours > 23) { // hours should not be more than 23
|
|
return;
|
|
}
|
|
|
|
// check date parity
|
|
parity = 0;
|
|
for (uint8_t bit = 36; bit <= 58; bit++) {
|
|
if (rtc_dcf77_frame & (1ULL << bit)) {
|
|
parity++; // count the set bits
|
|
}
|
|
}
|
|
if (parity % 2) { // parity should be even
|
|
return;
|
|
}
|
|
rtc_dcf77_time.day = 1 * ((rtc_dcf77_frame >> 36) & (0x1)) + 2 * ((rtc_dcf77_frame >> 37) & (0x1)) + 4 * ((rtc_dcf77_frame >> 38) & (0x1)) + 8 * ((rtc_dcf77_frame >> 39) & (0x1)) + 10 * ((rtc_dcf77_frame >> 40) & (0x1)) + 20 * ((rtc_dcf77_frame >> 41) & (0x1)); // read day of the month (01-31)
|
|
if (rtc_dcf77_time.day == 0 || rtc_dcf77_time.day > 31) { // day of the month should be 1-31
|
|
return;
|
|
}
|
|
rtc_dcf77_time.weekday = 1 * ((rtc_dcf77_frame >> 42) & (0x1)) + 2*((rtc_dcf77_frame >> 43) & (0x1)) + 4 * ((rtc_dcf77_frame >> 44) & (0x1)); // read day of the week (1=Monday - 7=Sunday)
|
|
if (rtc_dcf77_time.weekday == 0 || rtc_dcf77_time.weekday > 7) { // day of the week should be 1-7
|
|
return;
|
|
}
|
|
rtc_dcf77_time.month = 1 * ((rtc_dcf77_frame >> 45) & (0x1)) + 2 * ((rtc_dcf77_frame >> 46) & (0x1)) + 4 * ((rtc_dcf77_frame >> 47) & (0x1)) + 8*((rtc_dcf77_frame >> 48) & (0x1)) + 10 * ((rtc_dcf77_frame >> 49) & (0x1)); // read month of the year (01-12)
|
|
if (rtc_dcf77_time.month == 0 || rtc_dcf77_time.month > 12) { // month of the year should be 1-12
|
|
return;
|
|
}
|
|
rtc_dcf77_time.year = 1 * ((rtc_dcf77_frame >> 50) & (0x1)) + 2 * ((rtc_dcf77_frame >> 51) & (0x1)) + 4 * ((rtc_dcf77_frame >> 52) & (0x1)) + 8 * ((rtc_dcf77_frame >> 53) & (0x1)) + 10 * ((rtc_dcf77_frame >> 54) & (0x1)) + 20 * ((rtc_dcf77_frame >> 55) & (0x1)) + 40 * ((rtc_dcf77_frame >> 56) & (0x1)) + 80 * ((rtc_dcf77_frame >> 57) & (0x1)); // read year of the century (00-99)
|
|
if (rtc_dcf77_time.year > 99) { // year should be <100
|
|
return;
|
|
}
|
|
|
|
rtc_dcf77_time.valid = true; // if we managed it until here the decoding is successful
|
|
}
|
|
|
|
/** find phase of 1 seconds DCF77 signal in the bins
|
|
* searches the complete second for the highest correlation if the phase it not locked
|
|
* searches only around the last phase if locked
|
|
* saves the new phase with the highest correlation
|
|
*/
|
|
static void rtc_dcf77_phase_detector(void) {
|
|
uint8_t integral_i = 0; // which bin has the highest integral/correlation
|
|
int16_t integral_max = 0; // maximum integral value found
|
|
|
|
for (uint8_t start = 0; start < (rtc_dcf77_phase_locked ? 10 : LENGTH(rtc_dcf77_bins)); start++) { // which bin has been used to start the convolution (only use +/- 15 bits of previous phase if locked)
|
|
int16_t integral = 0; // value of the integral
|
|
for (uint8_t bin = 0; bin < LENGTH(rtc_dcf77_bins); bin++) { // go through bins to calculate correlation
|
|
int8_t dfc77_signal = -1; // the signal of the reference DCF77 signal
|
|
if (bin < 10) { // the signal is always high for the first 100 ms
|
|
dfc77_signal = 1; // use highest values
|
|
} else if (bin < 20) { // the signal has 50% chance of being high for the next 100 ms (encoding the bit)
|
|
dfc77_signal = 0; // use middle value
|
|
}
|
|
// the rest of the time the signal is low (keep lowest value)
|
|
integral += rtc_dcf77_bins[(start + bin + rtc_dcf77_phase + LENGTH(rtc_dcf77_bins) - 5) % LENGTH(rtc_dcf77_bins)] * dfc77_signal; // calculate the correlation at this point and integrate it (start with previous phase - 15 bins)
|
|
}
|
|
if (integral>integral_max) { // we found a better correlation result
|
|
integral_max = integral; // save new best result
|
|
integral_i = (start + rtc_dcf77_phase + LENGTH(rtc_dcf77_bins) - 5) % LENGTH(rtc_dcf77_bins); // start new best phase start
|
|
}
|
|
}
|
|
|
|
if ((int16_t)(integral_max + 40) > rtc_dcf77_phase_max) { // only save new phase if it is better than the last one, with some margin to compensate for the drift (perfect correlation = 100, worst correlation = -800)
|
|
rtc_dcf77_phase_max = integral_max; // save best phase value
|
|
rtc_dcf77_phase = integral_i; // save bin index corresponding to start of the phase
|
|
}
|
|
}
|
|
|
|
/** interrupt service routine called for timer */
|
|
void TIM_ISR(RTC_DCF77_TIMER)(void)
|
|
{
|
|
static uint8_t bin_state = 0; // how many samples have been stored in the bin
|
|
static uint8_t bin_i = 0; // current bin filled
|
|
static uint8_t bit_i = 0; // current bit in the DCF77 minute frame
|
|
if (timer_get_flag(TIM(RTC_DCF77_TIMER), TIM_SR_UIF)) { // overflow update event happened
|
|
timer_clear_flag(TIM(RTC_DCF77_TIMER), TIM_SR_UIF); // clear flag
|
|
// fill bin with current sample state
|
|
if (gpio_get(GPIO_PORT(RTC_DCF77_SIGNAL_PIN), GPIO_PIN(RTC_DCF77_SIGNAL_PIN))) {
|
|
rtc_dcf77_bins[bin_i]++; // only need to increase if the signal is high
|
|
}
|
|
bin_state++; // remember we filled the bin
|
|
|
|
if (bin_state>=10) { // bin has 10x1 ms samples, it is now full
|
|
bin_i = (bin_i + 1) % LENGTH(rtc_dcf77_bins); // go to next bin
|
|
rtc_dcf77_bins[bin_i] = 0; // restart bin
|
|
bin_state = 0; // restart collecting
|
|
}
|
|
|
|
if (0 == bin_i && 0 == bin_state) { // we have 1 s of samples
|
|
if (bit_i < 59) {
|
|
rtc_dcf77_phase_detector(); // detect phase in signal
|
|
// check modulation of first 100 ms
|
|
uint16_t modulation = 0;
|
|
for (uint8_t bin = 0; bin < 10; bin++) {
|
|
modulation += rtc_dcf77_bins[(rtc_dcf77_phase + bin) % LENGTH(rtc_dcf77_bins)];
|
|
}
|
|
if (modulation < 50) { // signal is not modulated, it might be the 60th pause bit
|
|
bit_i = 0; // restart frame
|
|
rtc_dcf77_frame = 0; // restart frame
|
|
rtc_dcf77_phase_max = INT16_MIN; // restart searching for phase
|
|
rtc_dcf77_phase_locked = false; // unlock phase since the decoding seems wrong
|
|
} else { // modulation detected
|
|
// check modulation of next 100 ms
|
|
modulation = 0;
|
|
for (uint8_t bin = 10; bin < 20; bin++) {
|
|
modulation += rtc_dcf77_bins[(rtc_dcf77_phase + bin) % LENGTH(rtc_dcf77_bins)];
|
|
}
|
|
if (modulation < 50) { // it's a 0
|
|
// bit is already cleared
|
|
} else { // it's a 1
|
|
rtc_dcf77_frame |= (1ULL << bit_i); // set bit
|
|
}
|
|
bit_i++; // go to next bit
|
|
}
|
|
} else { // complete DCF77 frame received
|
|
rtc_dcf77_decode(); // decode frame
|
|
if (rtc_dcf77_time.valid) { // decoded time is valid
|
|
rtc_dcf77_time.milliseconds = rtc_dcf77_phase * 10; // save milliseconds corresponding to phase
|
|
rtc_dcf77_phase_locked = true; // lock phase since decoding succeeded
|
|
rtc_dcf77_invalid = 0; // remember we had an valid decoding
|
|
rtc_dcf77_time_flag = true; // notify user we have time
|
|
} else {
|
|
rtc_dcf77_invalid++; // remember we had an invalid decoding
|
|
}
|
|
if (rtc_dcf77_invalid >= RTC_DCF77_INVALID_MAX) { // too many invalid decoding
|
|
rtc_dcf77_off(); // switch off receiver so it can re-tune
|
|
}
|
|
bit_i = 0; // restart frame
|
|
rtc_dcf77_frame = 0; // restart frame
|
|
}
|
|
}
|
|
} else { // no other interrupt should occur
|
|
while (true); // unhandled exception: wait for the watchdog to bite
|
|
}
|
|
}
|