Kleine DCF77-Bastelei

Hardware- und Elektronikbasteleien, Ansteuerung von Schnittstellen und Peripherie.
Fragen zu "Consumer"-Problemen kommen in Offtopic.
Benutzeravatar
Helle
Beiträge: 566
Registriert: 11.11.2004 16:13
Wohnort: Magdeburg

Kleine DCF77-Bastelei

Beitrag von Helle »

Bei meiner letzten Pollin-Bestellung konnte ich nicht widerstehen und habe deren DCF77-Modul mitbestellt (knapp 5 €). Im Gegensatz zu anderen Modulen (z.B.Conrad) muss man selbst noch etwas Elektronik dazu aufbauen; genau das Richtige. Schaltungs-Vorschläge findet man einige, aber was der eine empfiehlt, ist für den anderen großer Käse, z.B. als Eingang erstmal einen Schmitt-Trigger zu setzen. Da der Oszi aber am Modul-Ausgang schon brauchbare Rechteck-Signale zeigte, habe ich auch darauf verzichtet. Hier meine Schaltung:
[img]
http://www.mdcc-fun.de/k.helbing/DCF77/DCF77.jpg
[/img]
Der Eingangs-Widerstand sollte nicht kleiner als 1 MOhm sein, da der Ausgang des Moduls nur mit max. 5 µA belastet werden darf. Wird eine ungeregelte oder andere Versorgungs-Spannung verwendet, sollte die Spannung für das Modul z.B. mit einer Zener-Diode stabilisiert werden, max. sind 3,3 Volt zulässig.
Als Schnittstelle zum PC bietet sich der serielle Port an; wird von PB direkt unterstützt und der kleine Bastelfreund hat sicher an seinem PC sowas zumindest nachgerüstet.
Für die Abfrage habe ich RI (Ring-Indicator) verwendet (liegt so schön auffindbar direkt unter Masse).

Und hier der Code, der sicher noch verbessert werden kann:

Code: Alles auswählen

;- Dekodierung des DCF77-Signals
;- verwendete Hardware: DCF-Empfangsmodul DCF1 von Pollin
;- "Helle" Klaus Helbing, 27.06.2015, PB 5.31 (x64)
;- siehe auch: https://de.wikipedia.org/wiki/DCF77

Declare.l ReadValues()
Declare.l Fehler()

Global T1.q
Global T2.q
Global Sekunde.l
Global IsHigh.l
Global IsLow.l

Global Dim BCD.l(7)
  BCD(0) = 1
  BCD(1) = 2  
  BCD(2) = 4  
  BCD(3) = 8
  BCD(4) = 10  
  BCD(5) = 20    
  BCD(6) = 40  
  BCD(7) = 80    
  
Global Dim WTag.s(7)  
  WTag(0) = "Fehler!"
  WTag(1) = "Montag"
  WTag(2) = "Dienstag"
  WTag(3) = "Mittwoch"
  WTag(4) = "Donnerstag"
  WTag(5) = "Freitag"  
  WTag(6) = "Samstag"  
  WTag(7) = "Sonntag"  
  
Global Dim JMonat.s(12)  
  JMonat(0) = "Fehler!"
  JMonat(1) = "Januar"
  JMonat(2) = "Februar"
  JMonat(3) = "März"
  JMonat(4) = "April"
  JMonat(5) = "Mai"  
  JMonat(6) = "Juni"  
  JMonat(7) = "Juli"    
  JMonat(8) = "August"
  JMonat(9) = "September"
  JMonat(10) = "Oktober"  
  JMonat(11) = "November"  
  JMonat(12) = "Dezember"      

Global Dim TMonat.l(12)  
  TMonat(0) = 0
  TMonat(1) = 31
  TMonat(2) = 28             ;Schaltjahr wird extra ermittelt
  TMonat(3) = 31
  TMonat(4) = 30
  TMonat(5) = 31  
  TMonat(6) = 30  
  TMonat(7) = 31    
  TMonat(8) = 31
  TMonat(9) = 30
  TMonat(10) = 31  
  TMonat(11) = 30  
  TMonat(12) = 31
  
Global Dim RI.l(59)  

Global AktBitNeu$ = ""
Global AktBitAlt$ = ""
Global Verlauf$ = ""
Legende$ = "M Wetter-Daten RAZZAS  Min. P  St. P MTag WT  Mon.  Jahr  P-"
;Sekunde 	Legende
;00 	  	M: Beginn neue Minute, immer "0" 	 
;01-14 	  Wetter-Daten: Wetterinformationen der Firma MeteoTime	 
;15 	    R: Rufbit
;16 	    A: Ankündigung Wechsel MEZ/MESZ bzw. MESZ/MEZ am Ende dieser Stunde	 
;17 	    Z: Zonenbit1 "0": MEZ, "1": MESZ
;18 	    Z: Zonenbit0 "0": MESZ, "1": MEZ
;19 	    A: Ankündigung Schaltsekunde am Ende dieser Stunde 	 
;20 	  	S: Startbit der Zeitinformation, immer "1"
;21-27 	  Min.:	BCD-codierte Minute (7 Bits 1,2,4,8,10,20,40) 	 
;28 	    P: Paritätsbit der Minute (even Parity, Sekunden-Bits 21-27 	 
;29-34 	  St.: BCD-codierte Stunde (6 Bits 1,2,4,8,10,20) 	 
;35 	    P: Paritätsbit der Stunde (even Parity, Sekunden-Bits 29-24) 	 
;36-41 	  MTag:	BCD-codierter Tag (6 Bits 1,2,4,8,10,20) 	 
;42-44 	  WT:	BCD-codierter Wochentag (3 Bits 1,2,4) 	1=Montag 7=Sonntag
;45-49 	  Mon.:	BCD-codierter Monat (5 Bits 1,2,4,8,10) 	 
;50-57 	  Jahr:	BCD-codierte Jahrzahl (8 Bits 1,2,4,8,10,20,40,80) 	ohne Jahrhundert, auf "20" gesetzt
;58 	    P: Paritätsbit des Datums (even Parity, Sekunden-Bits 36-57) 	 
;59 	  	-: keine Sekundenmarke 	 
Trenner$ = "=--------------=-=-=-=======-======-======---=====--------="

For i = 1 To 256
  If OpenSerialPort(0, "COM" + Str(i), 9600, #PB_SerialPort_NoParity, 8, 1, #PB_SerialPort_NoHandshake, 256, 256)
    If IsSerialPort(0)
      Port = i
      CloseSerialPort(0)
      Break
    EndIf  
    CloseSerialPort(0)
  EndIf    
Next

If Port = 0
  MessageRequester("Fehler!", "Kann keine verfügbare COM-Schnittstelle finden!")  
  End 
EndIf  

If OpenSerialPort(0, "COM" + Str(Port), 9600, #PB_SerialPort_NoParity, 8, 1, #PB_SerialPort_NoHandshake, 256, 256)
  If OpenWindow(0, 0, 0, 500, 330, "Helles DCF77   PC: COM" + Str(Port), #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget)
    StickyWindow(0, 1)
    FontHigh = Int(10.0 / (GetDeviceCaps_(GetDC_(WindowID(0)), #LOGPIXELSY) / 96.0))     ;Font anpassen
    LoadFont(0, "Courier New", FontHigh)    ;proportional
    TextGadget(1, 10, 5, 100, 15, "Stunde:")
    TextGadget(2, 190, 5, 300, 15, "??")
    TextGadget(3, 10, 20, 100, 15, "Minute:")
    TextGadget(4, 190,20, 300, 15, "??")  
    TextGadget(5, 10, 35, 100, 15, "Wochentag:")
    TextGadget(6, 190, 35, 300, 15, "??")
    TextGadget(7, 10, 50, 100, 15, "Tag:")
    TextGadget(8, 190, 50, 300, 15, "??.")
    TextGadget(9, 10, 65, 100, 15, "Monat:")
    TextGadget(10, 190, 65, 300, 15, "??")
    TextGadget(11, 10, 80, 100, 15, "Jahr:")
    TextGadget(12, 190, 80, 300, 15, "20" + "??")

    TextGadget(13, 10, 100, 170, 15, "Umstellung MEZ/MESZ:")
    TextGadget(14, 190, 100, 300, 15, "????")
    TextGadget(15, 10, 115, 150, 15, "Aktuell MEZ/MESZ:")
    TextGadget(16, 190, 115, 300, 15, "????")

    TextGadget(17, 10, 130, 150, 15, "Schalt-Sekunde:")
    TextGadget(18, 190, 130, 300, 15, "????")

    TextGadget(19, 10, 155, 80, 15, "Status:")
    TextGadget(20, 190, 155, 300, 15, "Warte auf Signal an RI")
    
    TextGadget(21, 10, 180, 150, 15, "Low-Level-Zeit:")    
    TextGadget(22, 190, 180, 300, 15, "????ms")    
    TextGadget(23, 10, 195, 150, 15, "High-Level-Zeit:")    
    TextGadget(24, 190, 195, 300, 15, "????ms")
    
    TextGadget(25, 10, 220, 480, 15, "0         1         2         3         4         5")    
    TextGadget(26, 10, 235, 480, 15, "012345678901234567890123456789012345678901234567890123456789")
    
    TextGadget(27, 10, 250, 480, 15, AktBitNeu$)
    TextGadget(28, 10, 265, 480, 15, AktBitAlt$)    
    TextGadget(29, 10, 280, 480, 15, Trenner$)    
    TextGadget(30, 10, 295, 480, 15, Legende$)    
    TextGadget(31, 10, 310, 480, 15, Verlauf$)    
    
    For i = 1 To 31
      SetGadgetFont(i, FontID(0))
    Next
 
    AddWindowTimer(0, 0, 5)

    While GetSerialPortStatus(0, #PB_SerialPort_RI) = 0    ;also RI = Low 
      If WaitWindowEvent() = #PB_Event_CloseWindow
        CloseSerialPort(0)             ;zur Sicherheit
        End
      EndIf
      Delay(5)
    Wend
    SetGadgetText(20, "Anfang neue Minute wird gesucht")
    IsHigh = 1
    BindEvent(#PB_Event_Timer, @ReadValues())

    T1 = ElapsedMilliseconds() 

    While WaitWindowEvent() <> #PB_Event_CloseWindow : Wend

  EndIf
  
  CloseSerialPort(0) 
 Else 
  MessageRequester("Fehler!", "Kann die gewählte COM-Schnittstelle nicht öffnen!") 
EndIf

End

Procedure.l ReadValues()
  Port_Status_RI = GetSerialPortStatus(0, #PB_SerialPort_RI)
  If Port_Status_RI And IsLow          ;also RI = High. Praktisch steigende Flanke = Sekunden-Beginn
    IsHigh = 1
    IsLow = 0

    If Sekunde > 59
      SetGadgetText(20, "Fehler. Suche Anfang neue Minute")            
      Verlauf$ + "Ü"                   ;Überlauf
      Fehler()
      ProcedureReturn 
    EndIf      

    T2 = ElapsedMilliseconds()
    RI_Status_Dauer = T2 - T1    
    If RI_Status_Dauer > 750 And RI_Status_Dauer < 850
      SetGadgetText(22, "Sekunde " + Str(Sekunde) + "-" + Str(Sekunde + 1) + ": " + Str(RI_Status_Dauer) + "ms")
      Sekunde + 1        
     ElseIf RI_Status_Dauer >= 850 And RI_Status_Dauer < 950
      SetGadgetText(22, "Sekunde " + Str(Sekunde) + "-" + Str(Sekunde + 1) + ": " + Str(RI_Status_Dauer) + "ms")        
      Sekunde + 1        
     ElseIf RI_Status_Dauer > 1750     ;Ende Minute 
      SetGadgetText(22, "Sekunde " + Str(Sekunde) + "-" + Str(Sekunde + 1) + ": " + Str(RI_Status_Dauer) + "ms") 
      If Sekunde = 58
        SetGadgetText(20, "")          ;oder Text
        If RI(0) = 1
          SetGadgetText(20, "Fehler. Suche Anfang neue Minute")            
          Verlauf$ + "x"               ;Start-Fehler
          Fehler()
          ProcedureReturn           
        EndIf

        If RI(16)            ;1: Am Ende dieser Stunde wird MEZ/MESZ umgestellt
          SetGadgetText(14, "Ja, am Ende dieser Stunde")
         Else 
          SetGadgetText(14, "Nein, nicht am Ende dieser Stunde")
        EndIf
        If RI(19)            ;1: Am Ende dieser Stunde wird eine Schaltsekunde eingefügt
          SetGadgetText(18, "Ja, am Ende dieser Stunde")
         Else 
          SetGadgetText(18, "Nein, nicht am Ende dieser Stunde")
        EndIf

        If RI(17) = RI(18)   ;17: 0=MEZ, 1= MESZ; 18: 0=MESZ, 1=MEZ  
          SetGadgetText(20, "Fehler. Suche Anfang neue Minute")            
          Verlauf$ + "Z"                   ;Zeitzonen-Fehler
          Fehler()
          ProcedureReturn           
         Else
          If RI(17)
            SetGadgetText(16, "MESZ")
           Else
            SetGadgetText(16, "MEZ")
          EndIf
        EndIf

        If RI(20) = 0
          SetGadgetText(20, "Fehler. Suche Anfang neue Minute")            
          Verlauf$ + "z"               ;Start-Fehler
          Fehler()
          ProcedureReturn           
        EndIf
        
        Minute = RI(21) + (RI(22) * 2) + (RI(23) * 4) + (RI(24) * 8) + (RI(25) * 10) + (RI(26) * 20) + (RI(27) * 40)
        If Minute < 60
          SetGadgetText(4, RSet(Str(Minute), 2, "0"))          
         Else   
          SetGadgetText(4, "Fehler! Minute > 59") 
          Verlauf$ + "m"
          Fehler()
          ProcedureReturn
        EndIf   
        
        PariTest = 0                   ;Minute
        For i = 21 To 27               ;Minuten-Bits, s.o.
          PariTest + RI(i)
        Next
        If ((PariTest & 1) - RI(28)) <> 0
          SetGadgetText(20, "Fehler Parität Minute!")
          Verlauf$ + "m"      
          Fehler()
          ProcedureReturn
        EndIf        
       
        Stunde = RI(29) + (RI(30) * 2) + (RI(31) * 4) + (RI(32) * 8) + (RI(33) * 10) + (RI(34) * 20)
        If Stunde < 24
          SetGadgetText(2, RSet(Str(Stunde), 2, "0"))          
         Else   
          SetGadgetText(2, "Fehler! Stunde > 23") 
          Verlauf$ + "S"      
          Fehler()
          ProcedureReturn
        EndIf          
        
        PariTest = 0                   ;Stunde
        For i = 29 To 34
          PariTest + RI(i)
        Next
        If ((PariTest & 1) - RI(35)) <> 0
          SetGadgetText(20, "Fehler Parität Stunde!")
          Verlauf$ + "s"      
          Fehler()
          ProcedureReturn
        EndIf        
        
        Wochentag = RI(42) + (RI(43) * 2) + (RI(44) * 4)
        If Wochentag < 8 And Wochentag > 0
          SetGadgetText(6, WTag(Wochentag))
         Else
          SetGadgetText(6, "Fehler! Wochentag > 7")
          Verlauf$ + "W"      
          Fehler()
          ProcedureReturn
        EndIf          
          
        MTag = RI(36) + (RI(37) * 2) + (RI(38) * 4) + (RI(39) * 8) + (RI(40) * 10) + (RI(41) * 20)
        SetGadgetText(8, RSet(Str(MTag), 2, "0") + ".")    ;Check weiter unten       
         
        Monat = RI(45) + (RI(46) * 2) + (RI(47) * 4) + (RI(48) * 8) + (RI(49) * 10)
        If Monat < 13 And Monat > 0
          SetGadgetText(10, JMonat(Monat)) 
         Else
          SetGadgetText(10, "Fehler! Monat > 12")
          Verlauf$ + "M"      
          Fehler()
          ProcedureReturn
        EndIf
         
        Jahr = RI(50) + (RI(51) * 2) + (RI(52) * 4) + (RI(53) * 8) + (RI(54) * 10) + (RI(55) * 20) + (RI(56) * 40) + (RI(57) * 80)
        If Jahr < 100 ;And Jahr > 14
          SetGadgetText(12, "20" + RSet(Str(Jahr), 2, "0"))          
         Else   
          SetGadgetText(12, "Fehler! Jahr > 99") 
          Verlauf$ + "J"      
          Fehler()
          ProcedureReturn
        EndIf          
        
        PariTest = 0                   ;für Datum
        For i = 36 To 57
          PariTest + RI(i)
        Next
        If ((PariTest & 1) - RI(58)) <> 0
          SetGadgetText(20, "Fehler Parität Datum!")
          Verlauf$ + "d"      
          Fehler()
          ProcedureReturn
        EndIf

        If Monat = 2 And Jahr % 4 = 0  ;Februar
          MTag - 1                      ;Korrektur für Schaltjahr
        EndIf 

        If MTag > TMonat(Monat)
          SetGadgetText(8, "Fehler! Tag stimmt nicht!") 
          Verlauf$ + "t"      
          Fehler()
          ProcedureReturn   
        EndIf

        Verlauf$ + "0"                 ;Null für alles o.K. 
        SetGadgetText(31, Verlauf$)       
        SetGadgetText(27, AktBitNeu$)      
        SetGadgetText(22, "Sekunde " + Str(Sekunde + 1) + ": " + Str(RI_Status_Dauer) + "ms")
        SetGadgetText(20, "Aktuell. Neue Minute hat begonnen")          
        AktBitAlt$ = AktBitNeu$
        SetGadgetText(28, AktBitAlt$)
        SetGadgetColor(28, #PB_Gadget_FrontColor, $008800)
        AktBitNeu$ = ""
        
       Else   
        SetGadgetText(20, "Evtl. Minuten-Anfang gefunden")        
        AktBitNeu$ = ""
        Verlauf$ + "A"      
        SetGadgetText(31, Verlauf$)         
      EndIf                            ;Sekunde = 58

      Sekunde = 0         
 
    EndIf                              ;If RI_Status_Dauer > 750 And RI_Status_Dauer < 850
;---------------------------------------------------
   ElseIf Port_Status_RI = 0 And IsHigh     ;also RI = Low. Praktisch fallende Flanke = Ende Impuls
    IsHigh = 0
    IsLow = 1
    T1 = ElapsedMilliseconds()
    If Sekunde > 59
      Sekunde = 0
      SetGadgetText(20, "Fehler. Suche Anfang neue Minute")            
      AktBitNeu$ = ""     
      SetGadgetText(27, AktBitNeu$) 
      Verlauf$ + "Ü"                   ;Überlauf
      SetGadgetText(31, Verlauf$)
      T1 = ElapsedMilliseconds()
      ProcedureReturn 
    EndIf      

    RI_Status_Dauer = T1 - T2
    If RI_Status_Dauer >= 150 And RI_Status_Dauer < 250    ;Spielraum muss sein
      RI(Sekunde) = 1
      AktBitNeu$ + "1"   
      SetGadgetText(27, AktBitNeu$)        
     ElseIf RI_Status_Dauer >= 50 And RI_Status_Dauer < 150
      RI(Sekunde) = 0        
      AktBitNeu$ + "0"   
      SetGadgetText(27, AktBitNeu$)  
     ElseIf RI_Status_Dauer < 50
      Fehler()
    EndIf

    SetGadgetText(24, "Sekunde " + Str(Sekunde) + ": " + Str(RI_Status_Dauer) + "ms")

    If Len(Verlauf$) > 59
      Verlauf$ = Mid(Verlauf$, 2, 59)
    EndIf



  EndIf
EndProcedure  

Procedure.l Fehler()
  AktBitNeu$ = ""     
  SetGadgetText(27, AktBitNeu$) 
  SetGadgetText(31, Verlauf$)
  T1 = ElapsedMilliseconds()
  Sekunde = 0
EndProcedure
Das Ergebnis sieht dann etwa so aus:
[img]
http://www.mdcc-fun.de/k.helbing/DCF77/Screen-DCF.png
[/img]

Gruß
Helle
Benutzeravatar
7x7
Beiträge: 591
Registriert: 14.08.2007 15:41
Computerausstattung: ganz toll
Wohnort: Lelbach

Re: Kleine DCF77-Bastelei

Beitrag von 7x7 »

Helle hat geschrieben:Bei meiner letzten Pollin-Bestellung konnte ich nicht widerstehen...
Ja...das kenne ich! Ist halt der Reiz, etwas interessantes zu haben, was man aber nicht wirklich braucht! :mrgreen:

Stand letztens im Aldi und habe mit mir gekämpft, die per Internet fernsteuerbare Überachungskammera mit IR-Beleuchtung für lumpige 49,-€ NICHT zu kaufen....es gab -trotz krampfhaftem Nachdenkens- definitiv keinen plausiblen Einsatzzweck :mrgreen:
- alles was ich hier im Forum sage/schreibe ist lediglich meine Meinung und keine Tatsachenbehauptung
- unkommentierter Quellcode = unqualifizierter Müll
Benutzeravatar
cxAlex
Beiträge: 2111
Registriert: 26.06.2008 10:42

Re: Kleine DCF77-Bastelei

Beitrag von cxAlex »

Hui, da kommt der Elektroniker in mir wieder hoch.

Damit solltest du am Dienstag gegen Mitternacht (30. Juli 2015) eine Schaltsekunde beobachten können, ein Screenshot ab 23:00 wär interessant ;)

Gruß, Alex
Projekte: IO.pbi, vcpu
Pausierte Projekte: Easy Network Manager, µC Emulator
Aufgegebene Projekte: ECluster

Bild

PB 5.1 x64/x86; OS: Win7 x64/Ubuntu 10.x x86
Benutzeravatar
Falko
Admin
Beiträge: 3531
Registriert: 29.08.2004 11:27
Computerausstattung: PC: MSI-Z590-GC; 32GB-DDR4, ICore9; 2TB M2 + 2x3TB-SATA2 HDD; Intel ICore9 @ 3600MHZ (Win11 Pro. 64-Bit),
Acer Aspire E15 (Win11 Home X64). Purebasic LTS 6.0
Kontaktdaten:

Re: Kleine DCF77-Bastelei

Beitrag von Falko »

Toll, wie einfach das geht. Danke für das Teilen :allright:

Gruß,
Falko
Bild
Win10 Pro 64-Bit, PB_5.4,GFA-WinDOS, Powerbasic9.05-Windows, NSBasic/CE, NSBasic/Desktop, NSBasic4APP, EmergenceBasic
Antworten