diff --git a/README.md b/README.md index 40c5f7d..a26ec7f 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,33 @@ -dcf77 -====== - -_dcf77_ contains a state machine to identify a [DCF77][] signal fed in to a -regular GPIO pin. It also contains a decoder for the received signal into -date/time values. - -The driver is hardware independent and can e.g. be used with microcontrollers and -any implementation of an [embedded-hal][] crate. - -[embedded-hal]: https://github.com/japaric/embedded-hal.git -[DCF77]: https://en.wikipedia.org/wiki/DCF77 - -License -------- - -[0-clause BSD license](LICENSE-0BSD.txt). +dcf77 +====== + +_dcf77_ contains a state machine to identify a [DCF77][] signal fed in to a +regular GPIO pin. It also contains a decoder for the received signal into +date/time values. + +The driver is hardware independent and can e.g. be used with microcontrollers and +any implementation of an [embedded-hal][] crate. + +The input signal of the dcf77 statemachine shall look like this + +```text +DCF77 RF signal: + + | ||||||||||||||||||||||||||||||||||||| ||||||||||||||||||||||||||||||| ||| + | ||||||||||||||||||||||||||||||||||||| ||||||||||||||||||||||||||||||| ||| + | ||||||||||||||||||||||||||||||||||||| ||||||||||||||||||||||||||||||| ||| +...|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||... + +DCF77 receiver output that can be fed into this state machine: + ___ _______ ___ +..._| |____________________________________| |______________________________| |___... + 100ms 900ms 200ms 800ms 100ms +``` + +[embedded-hal]: https://github.com/japaric/embedded-hal.git +[DCF77]: https://en.wikipedia.org/wiki/DCF77 + +License +------- + +[0-clause BSD license](LICENSE-0BSD.txt). diff --git a/src/lib.rs b/src/lib.rs index 49935ee..ef8739c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ #![no_std] /// A structure to facilitate the decoding of a DCF77 signal which consists of 59 consecutive bits -/// of data +/// of data pub struct DCF77Time(pub u64); impl DCF77Time { @@ -48,75 +48,15 @@ impl DCF77Time { /// Return the current minutes of the hour (without verifying the information) pub fn minutes_unchecked(&self) -> u8 { - let mut minutes = 0; - if (self.0 & (1 << 21)) != 0 { - minutes += 1; - } - - if (self.0 & (1 << 22)) != 0 { - minutes += 2; - } - - if (self.0 & (1 << 23)) != 0 { - minutes += 4; - } - - if (self.0 & (1 << 24)) != 0 { - minutes += 8; - } - - if (self.0 & (1 << 25)) != 0 { - minutes += 10; - } - - if (self.0 & (1 << 26)) != 0 { - minutes += 20; - } - - if (self.0 & (1 << 27)) != 0 { - minutes += 40; - } - - minutes + self.calculate_2digit_bcd(21, 27) } /// Return the current minutes of the hour and verify parity and value < 60 pub fn minutes(&self) -> Result { - let mut parity = false; - if (self.0 & (1 << 21)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 22)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 23)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 24)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 25)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 26)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 27)) != 0 { - parity ^= true; - } - + let parity = self.calculate_parity(21, 27); let minutes = self.minutes_unchecked(); - if minutes > 59 { - return Err(()); - } - if ((self.0 & (1 << 28)) != 0) != parity { + if ((self.0 & (1 << 28)) != 0) != parity || minutes > 59 { Err(()) } else { Ok(minutes) @@ -125,67 +65,15 @@ impl DCF77Time { /// Return the current hours of the day (without verifying the information) pub fn hours_unchecked(&self) -> u8 { - let mut hours = 0; - if (self.0 & (1 << 29)) != 0 { - hours += 1; - } - - if (self.0 & (1 << 30)) != 0 { - hours += 2; - } - - if (self.0 & (1 << 31)) != 0 { - hours += 4; - } - - if (self.0 & (1 << 32)) != 0 { - hours += 8; - } - - if (self.0 & (1 << 33)) != 0 { - hours += 10; - } - - if (self.0 & (1 << 34)) != 0 { - hours += 20; - } - - hours + self.calculate_2digit_bcd(29, 34) } /// Return the current hours of the day and verify parity and value < 23 pub fn hours(&self) -> Result { - let mut parity = false; - if (self.0 & (1 << 29)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 30)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 31)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 32)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 33)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 34)) != 0 { - parity ^= true; - } - + let parity = self.calculate_parity(29, 34); let hours = self.hours_unchecked(); - if hours > 23 { - return Err(()); - } - if ((self.0 & (1 << 35)) != 0) != parity { + if ((self.0 & (1 << 35)) != 0) != parity || hours > 23 { Err(()) } else { Ok(hours) @@ -194,32 +82,7 @@ impl DCF77Time { /// Return the current day of month (without verifying the information) pub fn day_unchecked(&self) -> u8 { - let mut day = 0; - if (self.0 & (1 << 36)) != 0 { - day += 1; - } - - if (self.0 & (1 << 37)) != 0 { - day += 2; - } - - if (self.0 & (1 << 38)) != 0 { - day += 4; - } - - if (self.0 & (1 << 39)) != 0 { - day += 8; - } - - if (self.0 & (1 << 40)) != 0 { - day += 10; - } - - if (self.0 & (1 << 41)) != 0 { - day += 20; - } - - day + self.calculate_2digit_bcd(36, 41) } /// Return the current day of month and do a basic value check @@ -233,177 +96,25 @@ impl DCF77Time { } /// Return the current day of the week (without verifying the information) - /// 0 meaning Monday + /// 1 meaning Monday ... 7 meaning Sunday pub fn weekday_unchecked(&self) -> u8 { - let mut weekday = 0; - if (self.0 & (1 << 42)) != 0 { - weekday += 1; - } - - if (self.0 & (1 << 43)) != 0 { - weekday += 2; - } - - if (self.0 & (1 << 44)) != 0 { - weekday += 4; - } - weekday + self.calculate_2digit_bcd(42, 44) } /// Return the current month of the year (without verifying the information) pub fn month_unchecked(&self) -> u8 { - let mut month = 0; - if (self.0 & (1 << 45)) != 0 { - month += 1; - } - - if (self.0 & (1 << 46)) != 0 { - month += 2; - } - - if (self.0 & (1 << 47)) != 0 { - month += 4; - } - - if (self.0 & (1 << 48)) != 0 { - month += 8; - } - - if (self.0 & (1 << 49)) != 0 { - month += 10; - } - - month + self.calculate_2digit_bcd(45, 49) } /// Return the current year (without verifying the information) pub fn year_unchecked(&self) -> u16 { - let mut year = 2000; - if (self.0 & (1 << 50)) != 0 { - year += 1; - } - - if (self.0 & (1 << 51)) != 0 { - year += 2; - } - - if (self.0 & (1 << 52)) != 0 { - year += 4; - } - - if (self.0 & (1 << 53)) != 0 { - year += 8; - } - - if (self.0 & (1 << 54)) != 0 { - year += 10; - } - - if (self.0 & (1 << 55)) != 0 { - year += 20; - } - - if (self.0 & (1 << 56)) != 0 { - year += 40; - } - - if (self.0 & (1 << 57)) != 0 { - year += 80; - } - - year + let century: u16 = 2000; + century + >::into(self.calculate_2digit_bcd(50, 57)) } /// Return a tuple of (year, month, day, weekday) if it passes a parity check pub fn date(&self) -> Result<(u16, u8, u8, u8), ()> { - let mut parity = false; - if (self.0 & (1 << 36)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 37)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 38)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 39)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 40)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 41)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 42)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 43)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 44)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 45)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 46)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 47)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 48)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 49)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 50)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 51)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 52)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 53)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 54)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 55)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 56)) != 0 { - parity ^= true; - } - - if (self.0 & (1 << 57)) != 0 { - parity ^= true; - } + let parity = self.calculate_parity(36, 57); if ((self.0 & (1 << 58)) != 0) != parity { return Err(()); @@ -420,6 +131,25 @@ impl DCF77Time { Ok((year, month, day, weekday)) } } + + fn calculate_parity(&self, start: usize, end: usize) -> bool { + let mut parity = false; + let mut mask: u64 = 1 << start; + for _ in start..=end { + parity ^= (self.0 & mask) != 0; + mask = mask.wrapping_shl(1); + } + parity + } + + fn calculate_2digit_bcd(&self, start: usize, end: usize) -> u8 { + let length = end - start + 1; + let mask = (1u64 << length) - 1; + let bcd = (self.0 >> start) & mask; + let high_nibble: u8 = (bcd & 0xF0) as u8 >> 4; + let low_nibble: u8 = bcd as u8 & 0x0F; + high_nibble * 10 + low_nibble + } } enum SimpleDCF77DecoderState { @@ -432,31 +162,42 @@ enum SimpleDCF77DecoderState { } /// A structure for a simple timeslot based DCF77 decoder -pub struct SimpleDCF77Decoder { - scancount: u8, - lowcount: u8, - highcount: u8, - idlecount: u8, +pub struct SimpleDCF77Decoder { + /// Number of samples since the last phase change, that always starts with a high signal and is + /// max 2000 ms long + sample_count: u8, + /// Number of high samples during the first 100 ms in the scan phase to check if it might be a + /// transmitted 0 + zero_bit_count: u8, + /// Number of high samples between 100 and 200 ms in the scan phase to check if it might be a + /// transmitted 1 + one_bit_count: u8, + /// Number of non-idle samples after a valid bit was detected + non_idle_count: u8, + /// Current state of the decoder state: SimpleDCF77DecoderState, + /// The raw data received from the DCF77 signal data: u64, - datapos: usize, + /// Current position in the bitstream + data_pos: usize, } /// The SimpleDCF77Decoder implements a simple state machine to decode a DCF77 signal from a fed-in /// readout of a GPIO pin connected to a DCF77 receiver. To use this, create the structure, set up /// the GPIO pin the receiver is connected to as an input and call the `read_bit` method every -/// 10ms with a parameter value of `true` for a high signal level or `false` for a low signal level +/// 10ms with a parameter value of `true` for a high signal (low rf amplitude) level or `false` for +/// a low signal level (high rf amplitude). impl SimpleDCF77Decoder { /// Create a new decoder state machine pub fn new() -> Self { Self { - scancount: 0, - lowcount: 0, - highcount: 0, - idlecount: 0, + sample_count: 0, + zero_bit_count: 0, + one_bit_count: 0, + non_idle_count: 0, state: SimpleDCF77DecoderState::WaitingForPhase, data: 0, - datapos: 0, + data_pos: 0, } } @@ -492,78 +233,90 @@ impl SimpleDCF77Decoder { /// Returns the value of the latest received bit. Mainly useful for live display of the /// received bits pub fn latest_bit(&self) -> bool { - (self.data & (1 << (self.datapos - 1))) != 0 + (self.data & (1 << (self.data_pos - 1))) != 0 } /// Return the current position of the bit counter after the latest recognized end of a cycle /// which is identical to the current second of the minute pub fn seconds(&self) -> usize { - self.datapos + self.data_pos } /// Ingest the latest sample of the GPIO input the DCF77 receiver is connected to judge the / /// current position and value of the DCF77 signal bitstream pub fn read_bit(&mut self, bit: bool) { self.state = match self.state { - SimpleDCF77DecoderState::EndOfCycle | SimpleDCF77DecoderState::WaitingForPhase | SimpleDCF77DecoderState::FaultyBit => { + // wait for the first phase change 0->1 or abort if no phase change is detected within + // 1800 ms (180 samples) + SimpleDCF77DecoderState::EndOfCycle + | SimpleDCF77DecoderState::WaitingForPhase + | SimpleDCF77DecoderState::FaultyBit => { if bit { - self.lowcount = 1; - self.highcount = 0; - self.scancount = 0; + self.zero_bit_count = 1; + self.one_bit_count = 0; + self.sample_count = 0; + self.non_idle_count = 0; SimpleDCF77DecoderState::PhaseFound } else { - if self.scancount > 180 { - self.datapos = 0; - self.scancount = 0; - + if self.sample_count > 180 { + self.data_pos = 0; + self.sample_count = 0; SimpleDCF77DecoderState::EndOfCycle } else { SimpleDCF77DecoderState::WaitingForPhase } } } + // count the number of high bits in the first 100 ms and the second 100 ms to determine + // if a 0 or 1 was transmitted SimpleDCF77DecoderState::PhaseFound => { - if self.scancount < 20 { + if self.sample_count < 20 { if bit { - if self.scancount < 10 { - self.lowcount += 1; + if self.sample_count < 10 { + self.zero_bit_count += 1; } else { - self.highcount += 1; + self.one_bit_count += 1; } } SimpleDCF77DecoderState::PhaseFound } else { - let datapos = self.datapos; - self.datapos += 1; - if self.highcount > 3 { - self.data |= 1 << datapos; + let data_pos = self.data_pos; + self.data_pos += 1; + if self.one_bit_count > 3 { + self.data |= 1 << data_pos; SimpleDCF77DecoderState::BitReceived - } else if self.lowcount > 3 { - self.data &= !(1 << datapos); + } else if self.zero_bit_count > 3 { + self.data &= !(1 << data_pos); SimpleDCF77DecoderState::BitReceived } else { - // Bad signal, let's continue with the next bit + // Bad signal, let's start over + self.data_pos = 0; SimpleDCF77DecoderState::FaultyBit } } } + // wait until the 900 ms of the bit are over and then check if the signal was not idle + // for max 10 samples to start the next bit SimpleDCF77DecoderState::BitReceived | SimpleDCF77DecoderState::Idle => { if bit { - self.idlecount += 1; + self.non_idle_count += 1; } - if self.scancount >= 90 { - if self.idlecount > 10 { - self.idlecount = 0; - self.scancount = 0; + if self.sample_count >= 90 { + if self.non_idle_count < 10 { + SimpleDCF77DecoderState::WaitingForPhase + } + else{ + // Bad signal, let's start over + self.data_pos = 0; + SimpleDCF77DecoderState::FaultyBit } - SimpleDCF77DecoderState::WaitingForPhase } else { SimpleDCF77DecoderState::Idle } } }; - self.scancount += 1; + self.sample_count += 1; } }