{{******************************************************************************} { FileName............: Dcf77.spin } { Project.............: } { Author(s)...........: MM } { Version.............: 1.00 } {------------------------------------------------------------------------------} { DCF77 (clock) control } { } { Copyright (C) 2006-2007 M.Majoor } { } { This program is free software; you can redistribute it and/or } { modify it under the terms of the GNU General Public License } { as published by the Free Software Foundation; either version 2 } { of the License, or (at your option) any later version. } { } { This program is distributed in the hope that it will be useful, } { but WITHOUT ANY WARRANTY; without even the implied warranty of } { MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the } { GNU General Public License for more details. } { } { You should have received a copy of the GNU General Public License } { along with this program; if not, write to the Free Software } { Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. } { } {------------------------------------------------------------------------------} { } { Version Date Comment } { 1.00 20070727 - Initial release } {******************************************************************************} {------------------------------------------------------------------------------} DCF77 is a time signal being transmitted by 'radio'. The time signal being transmitted is based on an atomic clock. This code assumes we have a DCF77 receiver with a digital output. This output is connected to one of the available input pins. The output pin of the DCF77 receiver changes it output according to the received radio signal. This radio signal is an amplitude modulated signal. The amplitude level is converted into a digital signal by the DCF77 receiver. A typical output signal of a DCF77 receiver is: ┌──┐ ┌──┐ ┌─┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┘ └─────────────────┘ └───────────────────┘ └────────────────── The spacing of these pulses is 1 second. Every second the amplitude signal is being lowered for a small duration (0.1 s or 0.2 s). This lowered amplitude is being output as a pulse here. The duration of the pulse defines whether it represents a digital '0' or a digital '1'. These digital '0' and '1' together form a digital representation of the time. This digital stream of bits is being transmitted within one minute. The next minute a new digital stream starts. For synchronization purposes there will be no pulse when the 59's digital signal is being transmitted. This is used to indicate the start of the next digital stream (and the next minute). The pulse length is converted into a binary signal according to its length: 0.1s --> '0' 0.2s --> '1' The digital stream format is (with the first received bit at the right): 5 555555555 44444 444 443333 3333332 22222222 211111 11111 Sec 9 876543210 98765 432 109876 5432109 87654321 098765 432109876543210 D P84218421 18421 421 218421 P218421 P4218421 SAZZAR 30000 0 00 200 1000 2211 R = Call bit (irregularities in DCF77 control facilities) A1 = '1' Imminent change-over of time from CET <-> CEST Transmitted 1 hour prior to change (refelected in Z1/Z2) Z1 = Zone time bit 0 '10' = CET ; UTC + 1 hour Z2 = Zone time bit 1 '01' = CEST; DST ; dayligt saving time, UTC + 2 hours A2 = '1' Imminent change-over of leap second Transmitted 1 hour prior to change (January 1/July 1) S = Startbit coded time information (always '1') 1 = Minute (BCD) 2 = ,, 4 = ,, 8 = ,, 10 = ,, 20 = ,, 40 = ,, P1 = Parity bit preceeding 7 bits (all bits including parity equals even number) 1 = Hour (BDC) 2 = ,, 4 = ,, 8 = ,, 10 = ,, 20 = ,, P2 = Parity bit preceeding 6 bits (all bits including parity equals even number) 1 = Calendar day (BCD) 2 = ,, 4 = ,, 8 = ,, 10 = ,, 20 = ,, 1 = Day of the week (BCD) 1 = Monday 2 = ,, 4 = ,, 1 = Month (BCD) 2 = ,, 4 = ,, 8 = ,, 10 = ,, 1 = Year (BCD) 2 = ,, 4 = ,, 8 = ,, 10 = ,, 20 = ,, 40 = ,, 80 = ,, P3 = Parity bit preceeding 22 bits (all bits including parity equals even number) D = No pulse here except for leap second ('0' pulse) -> the next (leap) second then has no pulse. The pulse following the 'no pulse' indicates start of next minute/data stream. The DCF device is connected as follows: 3V3 ┌────────┐  R │ DCF │ 10k 3V3 ──┳──┳──┤ ├─┻── Input C  │ device │ ┌────┻──┻──┤ │  └────────┘ R = 1kΩ C = 1uF + 1nF The resistor here has one major purpose: filtering out any noise from the 3V3 power supply, which is typically connected directly to the Propeller device. Since the DCF signal itself is a low frequency (77.5 kHz), it falls within the frequency range of the Propeller chip itself, which can lead to problems. Without this resistor the DCF device was unable to function properly. The resistor has very little impact on the voltage available to the DCF device. Because the DCF device draws very little current, the voltage drop over the resistor is very low (0.08V here). {------------------------------------------------------------------------------}} CON CDcfIn = 22 ' Input pin for DCF77 _>Hive ADM-Port 1 ->Expansionsbus B17 CDcfOut = 24 ' Output pin for DCF77 signal (debug/visualization) ->Hive-Administra-LED CDcfLevel = 1 ' Level for '1' signal CNoSync = 0 ' Not in sync (never sync data received) CInSync = 1 ' In sync (no error since last sync) CInSyncWithError = 2 ' Not in sync (error since last sync), but time is up to date CCest = 1 ' CEST timezone (daylight saving time) CCet = 2 ' CET timezone CAm = 0 ' AM CPm = 1 ' PM VAR byte Cog ' Active cog long Stack[26] ' Stack for cog byte Bits[8] ' Current detection of pulses (bit access) long BitLevel ' Current bit level (NOT the signal level!) long BitError ' Current bit status byte BitNumber ' Current index of bit (== seconds) ' Time settings byte DataCount ' Incremented when data below updated byte TimeIndex ' Indicates the active index for the time settings ' Typically the background writes in one of the registers ' and if they all check out it makes them available by ' changing the TimeIndex. byte InSync ' Synchronization indication byte TimeZone[2] byte Seconds[2] byte Minutes[2] byte Hours[2] ' 0..23 hour indication byte HoursAmPm[2] ' 1..12 hour indication (used with AM/PM) byte AmPm[2] byte WeekDay[2] byte Day[2] byte Month[2] word Year[2] {{------------------------------------------------------------------------------ Params : - Returns : TRUE if cog available Descript: Start DCF acquisition Notes : ------------------------------------------------------------------------------}} PUB Start: Success { DIRA[dcfstart]~~ outa[dcfstart]:=1 waitcnt((clkfreq * 2)+ cnt) outa[dcfstart]:=0 } result := Cog := cognew(DcfReceive, @Stack) {{------------------------------------------------------------------------------ Params : - Returns : - Descript: Stop cog and DCF acquisition Notes : ------------------------------------------------------------------------------}} PUB Stop if Cog == 0 ' Only if cog is active return cogstop(Cog) ' Stop the cog {{------------------------------------------------------------------------------ Params : - Returns : - Descript: Interfaces to variables Notes : ------------------------------------------------------------------------------}} PUB GetActiveSet: Value result := TimeIndex PUB GetInSync: Value result := InSync PUB GetTimeZone: Value result := TimeZone[TimeIndex] PUB GetSeconds: Value result := Seconds[TimeIndex] PUB GetMinutes: Value result := Minutes[TimeIndex] PUB GetHours: Value result := Hours[TimeIndex] PUB GetWeekDay: Value result := WeekDay[TimeIndex] PUB GetDay: Value result := Day[TimeIndex] PUB GetMonth: Value result := Month[TimeIndex] PUB GetYear: Value result := Year[TimeIndex] PUB GetBit(Index): Value result := Bits[Index] PUB GetDataCount: Value result := DataCount PUB GetBitNumber: Value result := BitNumber PUB GetBitLevel: Value result := BitLevel PUB GetBitError: Value result := BitError {{------------------------------------------------------------------------------ Params : - Returns : - Descript: Handle DCF reception Notes : At fixed intervals the DCF input is polled. Every second the data is checked and the data updated. This code does not compensate for a leap second. However, this is handled by a resynchronization. We use a state machine so we can divide everything up. Digital output: On : In sync (no error) 1 Hz : In sync with DCF77 signal (rising edge is start second) 3 Hz : In sync with DCF77 signal (59th second) Active in first 0.5 second 10 Hz : Previous bit had error Active in first 0.5 second 20 Hz : Resyncing (waiting for pulse, max 1 s); followed by bit error signal This is the only variable in length (time) signal The last 100 ms of the 2nd 0.5 second contains a small 40 ms pulse when a binary '1' has been detected (for a '0' no pulse is generated) If no signal is being received then the following output is repeatedly generated: 20 Hz (1s), 10 Hz (0.5s), no signal (0.5s) ------------------------------------------------------------------------------}} PUB DcfReceive | LLocalTime, LIntervalCounts, LState, LWaitInterval, LBitNumber, LBitError, LLevels, LBitLevel, LIndex, LAccu, LParity, LError, LNewData DIRA[CDcfIn]~ DIRA[CDcfOut]~~ DataCount := 0 LLocalTime := 0 InSync := CNoSync LNewData := FALSE LWaitInterval := CNT ' Get current system counter LState := 99 ' Last state == initiates new state LIntervalCounts := (CLKFREQ / (1000 / 10)) #>381 ' Interval counts TimeIndex := 0 LIndex := 1 repeat ' The state machine consists of 100 equal steps ' Each of these steps have a time span of 10 ms, getting to a total ' of 1 second waitcnt(LWaitInterval += LIntervalCounts) ' Wait for next interval ' We keep the local time running independent from the received DCF signal ' because that might need synchronization. Only when synchronization has taken place ' the local time is synchronized with the DCF. This only happens every minute, when ' the received data checks out correctly LLocalTime++ case LLocalTime 001: ' Update local time ' Note: the date is not adjusted if Seconds[TimeIndex] == 59 Seconds[TimeIndex] := 0 if Minutes[TimeIndex] == 59 Minutes[TimeIndex] := 0 if HoursAmPm[TimeIndex] == 12 HoursAmPm[TimeIndex] := 1 if AmPm[TimeIndex] == CAm AmPm[TimeIndex] := CPm else AmPm[TimeIndex] := CAm else HoursAmPm[TimeIndex]++ if Hours[TimeIndex] == 23 Hours[TimeIndex] := 0 if WeekDay[TimeIndex] == 7 WeekDay[TimeIndex] := 1 else WeekDay[TimeIndex]++ else Hours[TimeIndex]++ else Minutes[TimeIndex]++ else Seconds[TimeIndex]++ 100: LLocalTime := 0 ' Handling the 0/1 detection ' We allow a 10% margin of error: ' 0 .. 0.3s 0/1 signal detection ' 0.3 .. 0.9s signal must be 0 ' 0.9 .. 1 s not checked ' 1 .. 2 s only when resync active LState++ case LState 01..30 : if INA[CDcfIn] == CDcfLevel LLevels++ ' We only need to check one level 31..90 : if INA[CDcfIn] == CDcfLevel LBitError := TRUE ' Any signal here is an error 101..200: if INA[CDcfIn] == CDcfLevel LState := 0 ' Restart state machine ' We divide the second up into several parts, including handling data of the ' previous second. ' In the last state (100) data from the current second are copied to the data ' which is handled the next second case LState 091: if (LLevels => 15) ' Decide if we detected a binary '0' or '1' LBitLevel := TRUE Bits[LBitNumber / 8] |= (1 << (LBitNumber // 8)) else LBitLevel := FALSE Bits[LBitNumber / 8] &= !(1 << (LBitNumber // 8)) 092: ' Check for illogical data (this might also be the missing pulse occuring every minute) if LBitNumber <> 59 LBitError := LBitError | (LLevels =< 5) | (LLevels => 25) 093: ' We can check the received data immediately ' The background operates on the inactive settings if LBitLevel LParity++ case LBitNumber 0 : if LNewData ' If new data, switch over to new data set Seconds[LIndex] := 0 ' Synchronize seconds ' Note: we can not synchronize in the ' 59th seconds because the 'local time' ' state machine adjusts the minutes/hours ' when the seconds reaches '60' LLocalTime := 0 ' Synchronize the 'local time' state machine if TimeIndex == 0 ' Switch to different active set TimeIndex := 1 LIndex := 0 else TimeIndex := 0 LIndex := 1 InSync := CInSync OUTA[CDcfOut]~~ ' Output on LNewData := FALSE LError := FALSE 15 : ' R = Call bit (irregularities in DCF77 control facilities) 16 : ' A1 = '1' Imminent change-over of time from CET <-> CEST ' Transmitted 1 hour prior to change (refelected in Z1/Z2) 19 : ' A2 = '1' Imminent change-over of leap second ' Transmitted 1 hour prior to change (January 1/July 1) 20 : if !LBitLevel ' S = Startbit coded time information (always '1') LError := TRUE 17, 42, 45, 50 : if LBitLevel ' Start new data LAccu := 1 else LAccu := 0 21, 29, 36 : if LBitLevel ' Start new data and parity controlled data LAccu := 1 LParity := 1 else LAccu := 0 LParity := 0 18, 22, 30, 37, 43, 46, 51: if LBitLevel ' 2 LAccu += 2 case LBitNumber 18: TimeZone[LIndex] := LAccu if (LAccu == %00) or (LAccu == %11) LError := TRUE 23, 31, 38, 44, 47, 52 : if LBitLevel ' 4 LAccu += 4 case LBitNumber 44: WeekDay[LIndex] := LAccu 24, 32, 39, 48, 53 : if LBitLevel ' 8 LAccu += 8 25, 33, 40, 49, 54 : if LBitLevel ' 10 LAccu += 10 case LBitNumber 49: Month[LIndex] := LAccu 26, 34, 41, 55 : if LBitLevel ' 20 LAccu += 20 case LBitNumber 34: Hours[LIndex] := LAccu if LAccu > 11 ' 1..12 Hour + AM/PM AmPm[LIndex] := CPm else AmPm[LIndex] := CAm if LAccu > 12 HoursAmPm[LIndex] := LAccu - 12 else if LAccu == 0 HoursAmPm[LIndex] := 12 else HoursAmPm[LIndex] := LAccu 41: Day[LIndex] := LAccu 27, 56 : if LBitLevel ' 40 LAccu += 40 case LBitNumber 27: Minutes[Lindex] := LAccu 57 : if LBitLevel ' 80 LAccu += 80 Year[LIndex] := 2000 + LAccu 28, 35, 58 : if (LParity & %1) <> 0 LError := TRUE 59 : ' D = No pulse here except for leap second ('0' pulse) -> the next (leap) second ' then has no pulse. ' The pulse following the 'no pulse' indicates start of next minute/data stream. if !LError LNewData := TRUE 100: ' Copy current second data to data we will be handling the next second ' and (re)set data for next second if !LBitError ' An error switches to the next state (resync) LState := 0 ' otherwise restart state machine BitLevel := LBitLevel LBitLevel := FALSE BitError := LBitError LBitError := FALSE BitNumber := LBitNumber ' Last to change because foreground might check this one ' to read others LLevels := 0 if BitError ' A sync error resets the second counter LBitNumber := 0 if InSync == CInSync InSync := CInSyncWithError ' 'Out of sync' if we were 'in sync' else LBitNumber++ ' Next second if LBitNumber == 60 ' We could check for leap second here, but ... LBitNumber := 0 DataCount++ ' Adjust data indicator for foreground 201: LState := 0 ' Resync failed: restart state machine ' Output ' time out biterror sec59 level Note: 'biterror' and 'sec59' never active at same time ' 1 1 1 1 1 ' 10 0 ' 17 0 ' 20 1 ' 30 0 ' 34 1 ' 40 1 ' 50 0 0 0 0 ' 75 1 ' 91 1 ' 95 0 0 0 0 ' 101 1 1 1 1 ' .. t t t t ' 195 0 0 0 0 if InSync <> CInSync ' Only control the output when not in sync case LState 001 : OUTA[CDcfOut]~~ ' Always on 010, 020, 030, 040: if BitError ' 10 Hz signal (bit error) !OUTA[CDcfOut] 017, 034, 075 : if !BitError AND (LBitNumber == 59) ' 3 Hz signal (in sync and 59th second) !OUTA[CDcfOut] 091 : if LBitLevel ' Bit is '1' !OUTA[CDcfOut] ' Always off 050, 095 : OUTA[CDcfOut]~ 101, 105, 110, 115, 120, 125, 130, 135, 140, 145, 150, 155, 160, 165, 170, 175, 180, 185, 190, 195: !OUTA[CDcfOut]