2020-02-17 15:16:04 +01:00
/** library to get time from a DCF77 module
* @ file
2016-08-14 18:37:58 +02:00
* @ author King Kévin < kingkevin @ cuvoodoo . info >
2020-06-06 14:35:55 +02:00
* @ copyright SPDX - License - Identifier : GPL - 3.0 - or - later
2020-02-17 15:16:04 +01:00
* @ date 2016 - 2020
2019-03-26 19:27:40 +01:00
* @ note peripherals used : GPIO @ ref rtc_dcf77_gpio , timer @ ref rtc_dcf77_timer
2016-08-14 18:37:58 +02:00
*/
/* standard libraries */
# include <stdint.h> // standard integer types
# include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
2017-10-08 17:30:22 +02:00
# include <libopencmsis/core_cm3.h> // Cortex M3 utilities
2017-10-13 15:49:48 +02:00
# include <libopencm3/cm3/nvic.h> // interrupt handler
2016-08-14 18:37:58 +02:00
# 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
2017-10-08 18:36:32 +02:00
/** @defgroup rtc_dcf77_gpio output to enable DCF module and input to capture DCF signal
* @ {
*/
2020-02-17 15:19:21 +01:00
# define RTC_DCF77_ENABLE_PIN PA2 /**< GPIO pinto enable the module */
# define RTC_DCF77_SIGNAL_PIN PA3 /**< GPIO pin to capture the DCF signal */
2017-10-08 18:36:32 +02:00
/** @} */
2017-10-13 15:49:48 +02:00
/** @defgroup rtc_dcf77_timer timer to sample DCF77 signal
2017-10-08 18:36:32 +02:00
* @ {
*/
# define RTC_DCF77_TIMER 4 /**< timer peripheral */
/** @} */
2016-08-14 18:37:58 +02:00
volatile bool rtc_dcf77_time_flag = false ;
2020-01-03 00:16:59 +01:00
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 ,
} ;
2017-10-13 15:49:48 +02:00
2020-01-03 00:16:59 +01:00
/** the received DCF77 frame bits */
2017-10-13 15:49:48 +02:00
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
2016-08-14 18:37:58 +02:00
void rtc_dcf77_setup ( void )
{
// setup enable output
2020-02-17 15:19:21 +01:00
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
2016-08-14 18:37:58 +02:00
rtc_dcf77_off ( ) ; // disable module at start
// setup signal input
2020-02-17 15:19:21 +01:00
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
2016-08-14 18:37:58 +02:00
2017-10-13 15:49:48 +02:00
// setup timer to sample signal at 1kHz
2017-10-08 18:36:32 +02:00
rcc_periph_clock_enable ( RCC_TIM ( RTC_DCF77_TIMER ) ) ; // enable clock for timer peripheral
2020-02-17 13:59:49 +01:00
rcc_periph_reset_pulse ( RST_TIM ( RTC_DCF77_TIMER ) ) ; // reset timer state
2017-10-08 18:36:32 +02:00
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
2017-10-13 15:49:48 +02:00
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)
2020-02-17 15:16:04 +01:00
timer_set_period ( TIM ( RTC_DCF77_TIMER ) , rcc_ahb_frequency / 2 / 1000 - 1 ) ; // set period to 1kHz (plus hand tuning)
2017-10-13 15:49:48 +02:00
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
2016-08-14 18:37:58 +02:00
}
void rtc_dcf77_on ( void )
{
2020-02-17 15:19:21 +01:00
if ( ! gpio_get ( GPIO_PORT ( RTC_DCF77_ENABLE_PIN ) , GPIO_PIN ( RTC_DCF77_ENABLE_PIN ) ) ) { // receiver is already turned on
2017-10-13 15:49:48 +02:00
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
2020-02-17 15:19:21 +01:00
gpio_clear ( GPIO_PORT ( RTC_DCF77_ENABLE_PIN ) , GPIO_PIN ( RTC_DCF77_ENABLE_PIN ) ) ; // enable module by pulling pin low
2017-10-13 15:49:48 +02:00
timer_set_counter ( TIM ( RTC_DCF77_TIMER ) , 0 ) ; // reset timer counter
timer_enable_counter ( TIM ( RTC_DCF77_TIMER ) ) ; // start timer to sample signal
2016-08-14 18:37:58 +02:00
}
void rtc_dcf77_off ( void )
{
2020-02-17 15:19:21 +01:00
gpio_set ( GPIO_PORT ( RTC_DCF77_ENABLE_PIN ) , GPIO_PIN ( RTC_DCF77_ENABLE_PIN ) ) ; // disable module by pull pin high
2017-10-13 15:49:48 +02:00
timer_disable_counter ( TIM ( RTC_DCF77_TIMER ) ) ; // stop timer since we don't need to sample anymore
2016-08-14 18:37:58 +02:00
}
2017-10-13 15:49:48 +02:00
/** decode rtc_dcf77_frame DCF77 frame into rtc_dcf77_time DCF77 time
* @ note check valid for validity
*/
static void rtc_dcf77_decode ( void )
2016-08-14 18:37:58 +02:00
{
2017-10-13 15:49:48 +02:00
rtc_dcf77_time . valid = false ; // reset validity
2020-02-17 15:16:04 +01:00
if ( rtc_dcf77_frame = = 0 ) { // no time received yet
2017-10-13 15:49:48 +02:00
return ;
2016-08-14 18:37:58 +02:00
}
2020-02-17 15:16:04 +01:00
if ( ! ( rtc_dcf77_frame & ( 1ULL < < 20 ) ) ) { // start of encoded time should always be 1
2017-10-13 15:49:48 +02:00
return ;
2016-08-14 18:37:58 +02:00
}
2017-10-13 15:49:48 +02:00
2016-08-14 18:37:58 +02:00
// check minute parity
2017-10-13 15:49:48 +02:00
uint8_t parity = 0 ; // to check parity
2020-02-17 15:16:04 +01:00
for ( uint8_t bit = 21 ; bit < = 28 ; bit + + ) {
if ( rtc_dcf77_frame & ( 1ULL < < bit ) ) {
2016-08-14 18:37:58 +02:00
parity + + ; // count the set bits
}
}
if ( parity % 2 ) { // parity should be even
2017-10-13 15:49:48 +02:00
return ;
2016-08-14 18:37:58 +02:00
}
2020-02-17 15:16:04 +01:00
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
2017-10-13 15:49:48 +02:00
return ;
2016-08-14 18:37:58 +02:00
}
2017-10-13 15:49:48 +02:00
2016-08-14 18:37:58 +02:00
// check hour parity
parity = 0 ;
2020-02-17 15:16:04 +01:00
for ( uint8_t bit = 29 ; bit < = 35 ; bit + + ) {
if ( rtc_dcf77_frame & ( 1ULL < < bit ) ) {
2016-08-14 18:37:58 +02:00
parity + + ; // count the set bits
}
}
2020-02-17 15:16:04 +01:00
if ( parity % 2 ) { // parity should be even
2017-10-13 15:49:48 +02:00
return ;
2016-08-14 18:37:58 +02:00
}
2020-02-17 15:16:04 +01:00
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
2017-10-13 15:49:48 +02:00
return ;
2016-08-14 18:37:58 +02:00
}
2017-10-13 15:49:48 +02:00
2016-08-14 18:37:58 +02:00
// check date parity
parity = 0 ;
2020-02-17 15:16:04 +01:00
for ( uint8_t bit = 36 ; bit < = 58 ; bit + + ) {
if ( rtc_dcf77_frame & ( 1ULL < < bit ) ) {
2016-08-14 18:37:58 +02:00
parity + + ; // count the set bits
}
}
2020-02-17 15:16:04 +01:00
if ( parity % 2 ) { // parity should be even
2017-10-13 15:49:48 +02:00
return ;
2016-08-14 18:37:58 +02:00
}
2020-02-17 15:16:04 +01:00
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
2017-10-13 15:49:48 +02:00
return ;
2016-08-14 18:37:58 +02:00
}
2020-02-17 15:16:04 +01:00
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
2017-10-13 15:49:48 +02:00
return ;
2016-08-14 18:37:58 +02:00
}
2020-02-17 15:16:04 +01:00
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
2017-10-13 15:49:48 +02:00
return ;
2016-08-14 18:37:58 +02:00
}
2020-02-17 15:16:04 +01:00
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
2017-10-13 15:49:48 +02:00
return ;
2016-08-14 18:37:58 +02:00
}
2017-10-13 15:49:48 +02:00
rtc_dcf77_time . valid = true ; // if we managed it until here the decoding is successful
2016-08-14 18:37:58 +02:00
}
2017-10-13 15:49:48 +02:00
/** 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
2020-02-17 15:16:04 +01:00
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)
2017-10-13 15:49:48 +02:00
int16_t integral = 0 ; // value of the integral
2020-02-17 15:16:04 +01:00
for ( uint8_t bin = 0 ; bin < LENGTH ( rtc_dcf77_bins ) ; bin + + ) { // go through bins to calculate correlation
2017-10-13 15:49:48 +02:00
int8_t dfc77_signal = - 1 ; // the signal of the reference DCF77 signal
2020-02-17 15:16:04 +01:00
if ( bin < 10 ) { // the signal is always high for the first 100 ms
2017-10-13 15:49:48 +02:00
dfc77_signal = 1 ; // use highest values
2020-02-17 15:16:04 +01:00
} else if ( bin < 20 ) { // the signal has 50% chance of being high for the next 100 ms (encoding the bit)
2017-10-13 15:49:48 +02:00
dfc77_signal = 0 ; // use middle value
}
// the rest of the time the signal is low (keep lowest value)
2020-02-17 15:16:04 +01:00
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)
2017-10-13 15:49:48 +02:00
}
if ( integral > integral_max ) { // we found a better correlation result
integral_max = integral ; // save new best result
2020-02-17 15:16:04 +01:00
integral_i = ( start + rtc_dcf77_phase + LENGTH ( rtc_dcf77_bins ) - 5 ) % LENGTH ( rtc_dcf77_bins ) ; // start new best phase start
2017-10-13 15:49:48 +02:00
}
}
2020-02-17 15:16:04 +01:00
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)
2017-10-13 15:49:48 +02:00
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 )
2016-08-14 18:37:58 +02:00
{
2017-10-13 15:49:48 +02:00
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
2020-02-17 15:19:21 +01:00
if ( gpio_get ( GPIO_PORT ( RTC_DCF77_SIGNAL_PIN ) , GPIO_PIN ( RTC_DCF77_SIGNAL_PIN ) ) ) {
2017-10-13 15:49:48 +02:00
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
2020-02-17 15:16:04 +01:00
bin_i = ( bin_i + 1 ) % LENGTH ( rtc_dcf77_bins ) ; // go to next bin
2017-10-13 15:49:48 +02:00
rtc_dcf77_bins [ bin_i ] = 0 ; // restart bin
bin_state = 0 ; // restart collecting
}
2020-02-17 15:16:04 +01:00
if ( 0 = = bin_i & & 0 = = bin_state ) { // we have 1 s of samples
if ( bit_i < 59 ) {
2017-10-13 15:49:48 +02:00
rtc_dcf77_phase_detector ( ) ; // detect phase in signal
// check modulation of first 100 ms
uint16_t modulation = 0 ;
2020-02-17 15:16:04 +01:00
for ( uint8_t bin = 0 ; bin < 10 ; bin + + ) {
modulation + = rtc_dcf77_bins [ ( rtc_dcf77_phase + bin ) % LENGTH ( rtc_dcf77_bins ) ] ;
2017-10-13 15:49:48 +02:00
}
2020-02-17 15:16:04 +01:00
if ( modulation < 50 ) { // signal is not modulated, it might be the 60th pause bit
2017-10-13 15:49:48 +02:00
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 ;
2020-02-17 15:16:04 +01:00
for ( uint8_t bin = 10 ; bin < 20 ; bin + + ) {
modulation + = rtc_dcf77_bins [ ( rtc_dcf77_phase + bin ) % LENGTH ( rtc_dcf77_bins ) ] ;
2017-10-13 15:49:48 +02:00
}
2020-02-17 15:16:04 +01:00
if ( modulation < 50 ) { // it's a 0
2017-10-13 15:49:48 +02:00
// bit is already cleared
} else { // it's a 1
2020-02-17 15:16:04 +01:00
rtc_dcf77_frame | = ( 1ULL < < bit_i ) ; // set bit
2017-10-13 15:49:48 +02:00
}
bit_i + + ; // go to next bit
2016-08-14 18:37:58 +02:00
}
2017-10-13 15:49:48 +02:00
} else { // complete DCF77 frame received
rtc_dcf77_decode ( ) ; // decode frame
if ( rtc_dcf77_time . valid ) { // decoded time is valid
2020-02-17 15:16:04 +01:00
rtc_dcf77_time . milliseconds = rtc_dcf77_phase * 10 ; // save milliseconds corresponding to phase
2017-10-13 15:49:48 +02:00
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
2016-08-14 18:37:58 +02:00
}
2020-02-17 15:16:04 +01:00
if ( rtc_dcf77_invalid > = RTC_DCF77_INVALID_MAX ) { // too many invalid decoding
2017-10-13 15:49:48 +02:00
rtc_dcf77_off ( ) ; // switch off receiver so it can re-tune
}
bit_i = 0 ; // restart frame
rtc_dcf77_frame = 0 ; // restart frame
2016-08-14 18:37:58 +02:00
}
2017-10-13 15:49:48 +02:00
}
} else { // no other interrupt should occur
while ( true ) ; // unhandled exception: wait for the watchdog to bite
}
2016-08-14 18:37:58 +02:00
}