Benutzer-Werkzeuge

Webseiten-Werkzeuge


raspberry:dcf77_modul

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen Revision Vorhergehende Überarbeitung
raspberry:dcf77_modul [2016/01/06 15:23]
gpipperr [Anschluss über Serial Interface]
raspberry:dcf77_modul [2016/01/06 15:28] (aktuell)
gpipperr [Interrupt auf den Signal Eingange legen]
Zeile 1: Zeile 1:
 +====Mit dem Raspberry ein DCF 77 Modul abfragen=====
 +
 +**Ziel:** 
 +
 +Ein DCF77 Modul zum Empfang der Zeitinformation über 77,5 kHz an einem Raspberry über GPIO anschließen und über ein Python Skript auswerten. 
 +
 +== Alternativer Anschluss ==
 +
 +Über eine Com Schnittstelle ginge das natürlich auch mit nativen Methoden (ntp Server) und dann entsprechend einfacher, GPIO Anschluss gewählt um das Protokoll besser zu verstehen.
 +
 +Siehe Materialsammlung bzgl. Anschluss über die COM Schnittstelle dazu am Ende des Artikels.
 +
 +
 +==== Grundlagen====
 +
 +Der [[http://www.ptb.de/cms/de/fachabteilungen/abt4/fb-44/ag-442/dissemination-of-legal-time/dcf77.html|Zeitzeichensender DCF77]] ist ein Langwellensender in der Nähe von Frankfurt und versorgt Deutschland mit der geltenden gesetzlichen Uhrzeit.
 +
 +Details siehe hier :  http://de.wikipedia.org/wiki/DCF77
 +
 +Der Sender arbeite auf einer Frequenz 77,5 kHz 
 +
 +Der Zeitcode baut sich so auf:
 +http://www.ptb.de/cms/fachabteilungen/abt4/fb-44/ag-442/verbreitung-der-gesetzlichen-zeit/dcf77/zeitcode.html
 +
 +Die Sekundenmarken mit einer Dauer von 0,1 s entsprechen der binären Null, die mit einer Dauer von 0,2 s der binären Eins. Die 59 Sekunde bleibt aus, die 0 Sekunde ist immer ein "LOW".
 +
 +In den ersten Bits werden Wetter Informationen versandt (siehe => http://www.meteotime.com ). 
 +
 +
 +----
 +
 +=== Online das DCF77 Signal abfragen ===
 +
 +Auf dieser Seite kann das akutelle Signal decodiert beobachtet werden  => http://www.dcf77logs.de/WebConsole.aspx
 +
 +Ideal um das eigene Programm zu überprüfen.
 +
 +----
 +
 +==== Empfang mit einen fertigen DCF77 Modul ====
 +
 +Beschrieben wird hier das Modul von [[https://www.ehajo.de/|ehajo]], im Shop unter [[http://www.ehajo.de/baus%C3%A4tze/bedrahtete-baus%C3%A4tze/dcf77-modul.html?search=dcf77|DCF77-Modul ]] für 10 € erhältlich.
 +
 +Das Modul besteht aus einen kleinen Platine mit einer Ferit Antenne und einen 4 Poligen Anschluss
 +
 +  * VCC + 1,2 - 3,3V
 +  * GND -
 +  * SIG - Ausgangssignal - **LOW** wenn DCF-Signal am Maximum ist
 +  * Enable auf GND bzw. offen => Modul ist aktiviert
 +
 +
 +Anschluss - Übersicht:
 +
 +{{:raspberry:dcf77-anschluss-v01.png| Anschluss des DCF77 Modul von ehajo}}
 +
 +
 +
 +Nach dem Anschluss der Spannungsversorgung kann es etwas dauern bis das erste Signal angezeigt wird.
 +
 +{{ :raspberry:dcf77-signal-v01.png?600 | dcf77 Signal - ersten Messung}}
 +
 +
 +Der Empfang in der Münchner Innenstadt funktioniert gut, am besten sollte die Antenne Rechtwinklig zur ungefähren Richtung Frankfurt betrieben werden, damit der Empfang gut klappt.
 +
 +
 +Bei einer ersten Messung fallen sofort alle LED Lampen in der Nähe negativ auf, ist die Schreibtisch Beleuchtung eingeschaltet, ist kein Empfang mehr möglich!
 +
 +
 +Der DCF77 Empfänger übernimmt die Dekodierung des Funksignals und wandelt das Signal in einen HIGH und Low Pegel um. Über einen GPIO Port wird das Modul am Raspberry angeschlossen.
 +
 +{{ :elektronik:dcf77_amplitude_zu_signal_pegel.png?direct&500 | DCF77 Funk Signal Pegel zu DCF77 Modul Pegel}}
 +
 +Zu jedem Beginn der Sekunden 0 bis 58 startet das HIGH Signal an SIG, dauert es nur 100ms wird das als eine logische 0 interpretiert, dauert das HIGH Signal 200ms wird eine logische 1 erkannt.
 +
 +{{ :elektronik:dcf77_1_0_codierung_pro_sec.png?direct&400 | DCF77 Signal Codierung 0 und 1 in der Sekunde 0 bis 58}}
 +
 +DCF77 Signal Codierung <fc #800000>0</fc> und <fc #4682b4>1</fc> in der Sekunde 0 bis 58
 +
 +
 +Nur in der 59 Sekunde bleibt der Pegel an SIG immer auf <fc #800000>0</fc> um den Anfang des Dataframes zu finden.
 +
 +
 +==== Umsetzung der Bits in dem Dataframe in die Zeitinformation==== 
 +
 +Sekunde 0 bis 35
 +{{ :elektronik:dcf77_codierung_sekunde_1_bis_35_v01.png | Sekunde 0 bis 35}}
 +
 +Sekunde 36 bis 59
 +{{ :raspberry:dcf77_codierung_sekunde_36_bis_59_v01.png | Sekunde 36 bis 59 }}
 +
 +==Übersicht==
 +
 +^Sekunde^Bedeutung wenn ein HIGH Pegel erkannt wird^
 +|0|Start einer neuen Minute, immer LOW|
 +|1 - 14|Wetterinformationen der Firma http://www.meteotime.com/de/Home/Default.htm, nicht öffentlich siehe zum Beispiel http://www.mikrocontroller.net/articles/DCF77_Wetterinformationen|
 +|15|Reserveantenne bzw. Rufbit für Störungsalarmierung|
 +|16|Zeitumstellung MESZ/MEZ in einer Stunde |
 +|17|Z1 - Zeitzonen Bit -  [[http://www.timeanddate.de/zeitzonen/weltweit/mez|MEZ]] (Mitteleuropäische Zeit (Normalzeit/Winterzeit))  hat Z1 den Zustand LOW und Z2 den Zustand HIGH |
 +|18|Z2 - Zeitzonen Bit - [[http://www.timeanddate.de/zeitzonen/weltweit/mesz|MESZ]] (Mitteleuropäische Sommerzeit (Sommerzeit)) hat Z1 den Zustand HIGH und Z2 den Zustand LOW|
 +|19|Ankündigungsbit A1 - Schaltsekunde wird eingefügt (eine h zuvor)|
 +|20|Telegramm beginn !Immer HIGH! |
 +|21|1 min|
 +|22|2 min|
 +|23|4 min|
 +|24|8 min|
 +|25|10 min|
 +|26|20 min|
 +|27|40 min|
 +|28| Prüfbit für gerade Parität|
 +|29|1 h|
 +|30|2 h|
 +|31|4 h|
 +|32|8 h|
 +|33|10 h|
 +|34|20 h|
 +|35|Prüfbit für h gerade Parität |
 +|36|1 Kalendertag|
 +|37|2 Kalendertag|
 +|38|4 Kalendertag|
 +|39|8 Kalendertag|
 +|40|10 Kalendertag|
 +|41|20 Kalendertag|
 +|42|1 Wochentag|
 +|43|2 Wochentag|
 +|44|4 Wochentag|
 +|45|1 Monat |
 +|46|2 Monat |
 +|47|4 Monat |
 +|48|8 Monat |
 +|49|10 Monat|
 +|50|1 Jahr|
 +|51|2 Jahr|
 +|52|4 Jahr|
 +|53|8 Jahr|
 +|54|10 Jahr |
 +|55|20 Jahr |
 +|56|40 Jahr |
 +|57|80 Jahr |
 +|58|Prüfbit für Datum gerade Parität|
 +|59|Für die Synchronisation immer LOW - ??außer bei Einfügen der Schaltsekunde auf 0.1s für LOW?? |
 +
 +
 +Die drei [[https://de.wikipedia.org/wiki/Parit%C3%A4tsbit|Paritiy Prüfbit Bits]] Felder ergänzen die Code Wörter Minute,Stunde und Datum inkl. Nummer des Wochentages auf eine gerade Zahl von Einsen. 
 +
 +Wird zum Beispiel für die Stunde die Bitfolge 100100 empfangen, ist das Prüfbit <fc #800000>0</fc>, da zwei mal 1 (=gerade Anzahl) gesendet wurde. Wird 7 Uhr empfangen, Bitfolge 111000 ist das Prüfbit <fc #4682b4>1</fc> um eine gerade Anzahl von 1 zu erzielen.
 +
 +Der Wert der einzelnen Code Wörter wird im [[https://de.wikipedia.org/wiki/BCD-Code|BCD Code]] übertragen. 
 +
 +----
 +
 +
 +==== Anschluss an den Raspberry ==== 
 +
 +
 +Um Schäden zu vermeiden den Raspberry herunterfahren und Spannungsversorgung trennen.
 +
 +Das Modul wird über die GPIO Pins angeschlossen.
 +
 +
 +  * 3,3 V +  auf Pin 1
 +  * Ground - auf Pin 6
 +  * SIG      auf Pin 11 GPIO 17
 +  * ENABLE   auf Pin 13 GPIO 27
 +
 +
 +Nach dem Neustart etwas warten und mit dem erstes einfaches Test Script überprüfen, ob bereits etwas gemessen werden kann:
 +<code python testClock.py>
 +import RPIO
 +import time
 +
 +# supress warning
 +RPIO.setwarnings(False)
 +
 +# Which RPIO Numbering you like to use
 +RPIO.setmode(RPIO.BCM)
 +
 +# OUT - Enable Port for the module
 +enable_port=27
 +RPIO.setup(enable_port,RPIO.OUT)
 +RPIO.gpio_function(enable_port)
 +RPIO.output(enable_port,RPIO.LOW)
 +
 +#IN - Read the data
 +data_port=17
 +RPIO.setup(data_port,RPIO.IN)
 +
 +#endless loop
 +run=True
 +read_count=0
 +while run :
 +        #Read data
 +        if (RPIO.input(data_port)):
 +                print "Port 17 => {0:5} :: HIGH :: Count {1} ".format(str(RPIO.input(data_port)), read_count)
 +        else:
 +                print "Port 17 => {0:5} :: LOW  :: Count {1} ".format(str(RPIO.input(data_port)), read_count)
 +        read_count+=1
 +        #wait 10ms
 +        time.sleep(0.1)
 +        #stop after 100 trys
 +        if (read_count > 100):
 +                run=False
 +# Clean up
 +RPIO.cleanup()
 +</code>
 +
 +Es ist auch nicht immer sichergestellt, das das Funksignal sauber erkannt wird, Störungen können die Auswertung der Signale erheblich stören, LED Lampen in der Nähe des Empfängers vermeiden!.
 +
 +Sehr hilfreich ist hier die Online Applikation. die permanent die DFC77 Informationen der aktuellen Sekunde anzeigt. Das ist sehr hilfreich zum Debuggen des eignen Programms um zu sehen, ob der Fehler nicht sogar beim Sender bzw. aktuell in der Übertragung liegt.
 +
 +Zum Beispiel wenn gar keine Signale von diesen dcf77logs Empfänger richtig erkannt werden können.
 +
 +=> http://www.dcf77logs.de/WebConsole.aspx.
 +
 +
 +==Zeitzone des Raspberry prüfen==
 +
 +Damit es sich einfacher Debuggen lässt, die richtige Zeitzone einstellen, falls die Uhrzeit zum Beispiel um 1 Stunde abweicht:
 +
 +<code bash>
 +#Uhrzeit auslesen
 +date
 +
 +# Wenn falsche Zeitzone:
 +
 +# Alte Definition entfernen
 +mv /etc/localtime /tmp
 +
 +# Neue Definition suchen 
 +# unter /usr/share/zoneinfo/Europe/
 +
 +#Verlinken
 +ln -s /usr/share/zoneinfo/Europe/Berlin /etc/localtime
 +</code>
 +==== Die Signale auswerten ==== 
 +
 +Die Signale können mit zwei Methoden ausgewertet werden:
 +  -  Abfragen des Pegels auf dem Signaleingang in einer Endlosschleife (mit 10ms Sleeps)
 +  -  Interrupt auf steigende Flanke setzen und damit den Beginn einer jeden Sekunde sofort erkennen
 +
 +Im ersten Test habe ich eine Lösung nach 1. versucht, allerdings scheint mir eine Lösung nach 2. effizienter (Siehe Blink Code für die Signal LED=
 +
 +=== Permanentes Pollen des Signal Einganges ===
 +
 +Jede Sekunde innerhalb der laufenden Minute wird ein Impuls mit einer Länge von 100msec (Low) oder
 +200msec (High) gesendet. 
 +
 +In der 59-zigsten Sekunde fehlt dann dieser Impuls. 
 +
 +Mit dieser Synchronisations-Lücke kann der Anfang des Datagramm bestimmt werden.
 +
 +Ablauf:
 +
 +  * Abtasten alle x ms
 +  * Anfang finden
 +  * Sekundenweise auswerten
 +
 +Dabei ist zu beachten, das eine exakte Sleep Time von 10ms auf einem Raspberry mit Python nicht erwartet werden kann. 
 +
 +Je nach Last und andere Faktoren kann der Wert zwischen 9ms und 11ms schwanken, daher suche ich bereits nach 950ms nach der HIGH Flanke um den Anfang des nächste Daten Bits zu finden.
 +
 +So ungefähr wird das zum Schluss laufen:
 +<code>
 +
 +59'igste Sekunden Lücke suchen
 +   Suche min. 1s LOW und dann die erste steigende Flanke => Anfang des Dataframes
 +
 +Start die Aufzeichnung der laufenden Minute
 +
 +Schleife von 0 bis 58
 + Messe alle 10ms den Pegel
 + Merke den Pegel=0 oder 1 (zähle diese Werte)
 + Zähle die Durchläufe bis ~ 950ms (95 Durchläufe)
 +    Da das Signal nicht immer exakt für 1000ms abgefragt werden kann:
 +     Prüfe ob Pegel schon wieder auf HIGH, wenn Ja Ende (bzw. Anfang des nächsten) des Dataframes gefunden
 +     Wenn mehr als 20 High  => Bit Wert dieser Stelle auf Pos Record auf 1
 +     Wenn nach 11 schon Low => Bit Wert dieser Stelle auf Pos Record auf 0
 +
 +Decodiere die gefundenen Werte:
 + Je nach Pos setzen den entsprehenden Bit Wert in der jeweiligen Variable 
 + Pos  0 immer 0
 + Pos. 1 bis 14  ->  Wetter Daten
 + Pos. 15        ->  Rufbit für Alarmierung
 + Ros. 16        ->  Ankündigungsbit A1
 + Pos. 17/18     ->  Zonenzeitbits
 + Pos  19        ->  Ankündigungsbit A2
 + Pos. 20 bis 28 ->  7 Bits für die Minute + Prüfbit auf gerade Anzahl Stellen
 + Pos. 29 bis 36  -> 6 Bits für die Stunde + Prüfbit auf gerade Anzahl Stellen
 + Pos. 37 bis 59  -> 22 Bits für das Datum einschließlich der Nummer des Wochentages
 +                     6 Stellen Kalender Tag
 + 3 Stellen Wochentag
 + 5 Stellen Kalendermonat
 + 8 Stellen Kalenderjahr
 + 1 prüfbit
 +  
 + Pos 59 Immer 0
 +
 +Werte die Ergebnisse aus  ( !BCD Kodierung -  1-2-4-8 Code) und schreibe Ergebnis in einen Logfile etc.
 +
 +</code>
 +
 +Ein erster Versuch inkl. der Überprüfung der Parität und setzen der OS Uhrzeit:
 +<code python readClock.py>
 +__author__ = 'gpipperr'
 +
 +"""
 +Small Python Script to decode a DFC77 Module connect to the Raspberry 
 +
 +Connect:
 + SIG to Pin 11 GPIO 17
 + ENABLE to Pin 13 GPIO 27
 +
 +Parameter -d
 +To supress the debug output start the script with -d 01
 +
 +Parameter -r
 +Define how often the time will be read
 +
 +Paramter -e
 +Not anlyse in the middle of the datagramm between 300ms and 900ms to save CPU
 +
 +Parameter -s
 +set the OS Clock to the last captured time
 +
 +Example:  run 30 minutes and show no Debug Messages and not use the energy mode to save CPU and not set the OS clock
 +
 +python readClock.py -r 30 -d  0 -e 0 -s 0
 +
 +"""
 +
 +import RPIO
 +import time
 +import datetime
 +import sys
 +import getopt
 +import exceptions
 +import signal
 +import os
 +
 +# supress warning
 +RPIO.setwarnings(False)
 +
 +# Which RPIO Numbering you like to use
 +RPIO.setmode(RPIO.BCM)
 +
 +# OUT - Enable Port for the module
 +enable_port = 27
 +RPIO.setup(enable_port, RPIO.OUT)
 +RPIO.gpio_function(enable_port)
 +RPIO.output(enable_port, RPIO.LOW)
 +
 +# IN - Read the data
 +data_port = 17
 +RPIO.setup(data_port, RPIO.IN)
 +
 +# Globals
 +datagram = []
 +debug_level = 0
 +energy_mode = 0
 +
 +# ######  define the handler if the program is stopped with ^c ######
 +# Clean exit!
 +def clean_exit():
 + RPIO.cleanup()
 + exit()
 +
 +
 +# define the handler if the program is stopped with ^c
 +def handler(signum, frame):
 + print "Catch Error {} - frame :: {}".format(signum, frame)
 + clean_exit()
 +
 +
 +# register the signal handler
 +signal.signal(signal.SIGINT, handler)
 +# ------------------------------------------------------------------
 +
 +# debug info
 +def pdebug(text):
 + if (debug_level == 1):
 + print text
 +
 +# Error Exception Handler
 +# If Bit20 is low
 +class Bit20Error(Exception):
 + def __init__(self, *args, **kwargs):
 + Exception.__init__(self, *args, **kwargs)
 + self.args = args
 + print("\n-- Error - Bit 20 is LOW - Datagram Error")
 +# If Bit0 is high
 +class Bit0Error(Exception):
 + def __init__(self, *args, **kwargs):
 + Exception.__init__(self, *args, **kwargs)
 + self.args = args
 + print("\n-- Error - Bit 0 is HIGH - Datagram Error")
 +# If Parity was wrong
 +class ParityBitError(Exception):
 + def __init__(self, *args, **kwargs):
 + Exception.__init__(self, *args, **kwargs)
 + self.args = args
 + print("\n-- Error - Parity Bit Error - Datagram Error with " + self.args[0])
 +
 +# Check the parity bit
 +def checkParityBit(data_word_type, value_list, parity_bit):
 + check = sum(value_list)
 + pdebug("-- Parity Check for {0} : Sum {1} : Parity Bit {2}".format(data_word_type, check, parity_bit))
 + # the last bit fills up the bit order to a even count
 + # even
 + if check % 2 == 0:
 + if (parity_bit) == 1:
 + raise ParityBitError("Error in :: " + data_word_type)
 + else:
 + if (parity_bit) == 0:
 + raise ParityBitError("Error in :: " + data_word_type)
 +
 +# find the 59 Second with 1000ms low pegel
 +def get59Seconde():
 + print "-- Wait on 59 Second mark"
 + # read the world time
 + seconds = time.time()
 + sleeptime = 0
 + # remember low
 + low_time = 0
 + low_count = 0
 + # remeber high
 + high_time = 0
 + high_count = 0
 + run = True
 + found59 = False
 + while run:
 + # Get act. time
 + seconds = time.time()
 + # read the port
 + if (RPIO.input(data_port)):
 + high_count += 1
 + high_time = high_time + sleeptime
 + low_count = 0
 + else:
 + low_count += 1
 + low_time = low_time + sleeptime
 + high_count = 0
 + # sleep some milli seconds
 + time.sleep(0.01)
 + # calculate the time
 + sleeptime = time.time() - seconds
 + # check for the 59s - full 100 reads low
 + # + the minimal low count from the 58 secound 80 ~ 175 to avoid read errors
 + # if (low_time > 0.99):
 + if ((low_count > 90) and (found59 == False)):
 + found59 = True
 + # Wait until we found the first HIGH of the 0 Second
 + if ((found59) and (high_count == 1)):
 + # start with the real decoding
 + pdebug("-- Fond the 59second :: PC Time {0} :: High Cnt. {1:2} :: Low Cnt.  {2:2} :: Low time {3:15} :: Sleep time {4:5}".format(datetime.datetime.now().strftime("%H:%M:%S.%f"), high_count, low_count, low_time, sleeptime))
 + decode = True
 + run = False
 + print "-- ... found"
 +
 +# Read the datagram
 +def decodeMinute():
 + global datagram
 + global energy_mode
 + akt_time = 0
 + runDecode = True
 + aktSecond = 0
 + high_count = 0
 + low_count = 0
 + highDetectedafterReads = 0
 + low_time = 0
 + high_time = 0
 + sleeptime = 0
 + seconds = time.time()
 + reads = 0
 + while runDecode:
 + seconds = time.time()
 + # read
 + if (RPIO.input(data_port)):
 + high_count += 1
 + high_time = high_time + sleeptime
 + # remember after which read count of the act second the pegel was high
 + if (highDetectedafterReads < 1):
 + highDetectedafterReads = reads
 + else:
 + low_count += 1
 + low_time = low_time + sleeptime
 + #Energy Mode - Sleep longer in the midle of the datagramm
 + if (energy_mode==1):
 + if (reads==30):
 + time.sleep(0.59)
 + reads=90
 + #Normal sleep
 + time.sleep(0.01)
 +
 + # how long take the sleep
 + sleeptime = time.time() - seconds
 +
 + # ms of the aktual second
 + akt_time = akt_time + sleeptime
 +
 + # stop after 1000ms
 + if (reads > 95):
 + # get the next high count to found the end of the timeframe
 + if (RPIO.input(data_port)):
 + if (high_count > 20):
 + datagram.append(1)
 + else:
 + datagram.append(0)
 + printSecDot()
 + pdebug("-- Akt Secound {0:2} :: Datagram Time {1:15} :: Reads {2:2}  :: High Cnt. {3:2}  :: Low Cnt. {4:2} :: High time {5:15} :: Low Time  {6:15}  Detect after reads {7:2}".format(aktSecond, akt_time, reads, high_count, low_count, high_time, low_time, highDetectedafterReads))
 + # check for valid bits
 + # if unvailed raise exception
 + # First bit must be alwasy 0
 + if (aktSecond == 0):
 + if (datagram[0] == 1):
 + raise Bit0Error
 + # Bit 20 always LOW - Start of telegramm
 + if (aktSecond == 20):
 + if (datagram[20] == 0):
 + raise Bit20Error
 + # check the parity bits of the data words
 + if (aktSecond == 28):
 + checkParityBit("MINUTE", datagram[21:28], datagram[28])
 + if (aktSecond == 35):
 + checkParityBit("HOUR", datagram[29:35], datagram[35])
 + if (aktSecond == 58):
 + checkParityBit("CALENDAR", datagram[36:58], datagram[58])
 + #set the defaults
 + reads = 0
 + akt_time = 0
 + high_time = 0
 + low_time = 0
 + low_count = 0
 + high_count = 0
 + highDetectedafterReads = 0
 + aktSecond += 1
 + else:
 + reads += 1
 + else:
 + reads += 1
 + if (aktSecond > 58):
 + runDecode = False
 + # add 59 second
 + datagram.append(0)
 +
 +
 +# get the hour
 +def getMinuteValue():
 + global datagram
 + # Check for parity errors
 + # check parity bit
 + checkParityBit("MINUTE", datagram[21:28], datagram[28])
 + minute = datagram[21] * 1 + datagram[22] * 2 + datagram[23] * 4 + datagram[24] * 8 + datagram[25] * 10 + datagram[26] * 20 + datagram[27] * 40
 + return minute
 +
 +def getHourValue():
 + global datagram
 + # Check for parity errors
 + # check parity bit
 + checkParityBit("HOUR", datagram[29:35], datagram[35])
 + hour = datagram[29] * 1 + datagram[30] * 2 + datagram[31] * 4 + datagram[32] * 8 + datagram[33] * 10 + datagram[34] * 20
 + return hour
 +
 +# get the calendar Values
 +def getDayValue():
 + global datagram
 + checkParityBit("CALENDAR", datagram[36:58], datagram[58])
 + day = datagram[36] * 1 + datagram[37] * 2 + datagram[38] * 4 + datagram[39] * 8 + datagram[40] * 10 + datagram[41] * 20
 + return day
 +def getWeekDayValue():
 + global datagram
 + checkParityBit("CALENDAR", datagram[36:58], datagram[58])
 + weekday = datagram[42] * 1 + datagram[43] * 2 + datagram[44] * 4
 + return weekday
 +def getMonthValue():
 + global datagram
 + checkParityBit("CALENDAR", datagram[36:58], datagram[58])
 + month   = datagram[45] * 1 + datagram[46] * 2 + datagram[47] * 4 + datagram[48] * 8 + +datagram[49] * 10
 + return month
 +def getYearValue():
 + global datagram
 + checkParityBit("CALENDAR", datagram[36:58], datagram[58])
 + year = datagram[50] * 1 + datagram[51] * 2 + datagram[52] * 4 + datagram[53] * 8 + datagram[54] * 10 + datagram[55] * 20 + datagram[56] * 40 + datagram[57] * 80
 + return year+2000
 +#Sommer or Wintertime MEZ or MESZ
 +def getTimeZoneValue():
 + timezone="undef"
 + if (datagram[17]==0) and (datagram[18]==1):
 + timezone="MEZ"
 + if (datagram[17]==1) and (datagram[18]==0):
 + timezone="MESZ"
 + return timezone
 +#get at normal date object to set the clock of the server
 +def getDCF77TimeStamp():
 + second = 0
 + microsecond = 0
 + dcf77_date = datetime.datetime(getYearValue(), getMonthValue(), getDayValue(), getHourValue(), getMinuteValue(), second, microsecond)
 + return dcf77_date
 +
 +#set the OS Date to the right date
 +def setOSDate(set_date):
 + #after the first sucess full read we can set the os clock
 + if (set_date==1):
 + try:
 + new_os_time_str="\"{0:4d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:00\"".format(getYearValue(),getMonthValue(),getDayValue(),getHourValue(),getMinuteValue())
 + print "-- Set the OS Clock with the data string {0} :: Diff {1}".format(new_os_time_str, (datetime.datetime.now()-getDCF77TimeStamp()))
 + os.system("date -s " + new_os_time_str + " > /dev/null")
 + except:
 + print "-- Unkown error::", sys.exc_info()
 + print "-- Finish to set OS time to  {0}".format(datetime.datetime.now().strftime("%H:%M:%S.%f"))
 +
 +# print the dataframe
 +# for debug purpose on the same line if used in decode function
 +def printData():
 + global datagram
 + # print "\r data:: {0}".format(datagram)
 + count = 1
 + sys.stdout.write("\r-- Datagramm Value::")
 + for val in datagram:
 + sys.stdout.write(str(val))
 + if count == 1:
 + sys.stdout.write("-")
 + if count == 15:
 + sys.stdout.write("-")
 + if count == 20:
 + sys.stdout.write("-")
 + if count == 21:
 + sys.stdout.write("-")
 + if count == 29:
 + sys.stdout.write("-")
 + if count == 36:
 + sys.stdout.write("-")
 + if count == 42:
 + sys.stdout.write("-")
 + if count == 45:
 + sys.stdout.write("-")
 + if count == 50:
 + sys.stdout.write("-")
 + if count == 59:
 + sys.stdout.write("-")
 + count += 1
 + sys.stdout.flush()
 +
 +
 +# print a dot on the same line
 +def printSecDot():
 + sys.stdout.write(".")
 + sys.stdout.flush()
 +
 +
 +def main(argv):
 + global datagram
 + global debug_level
 + global energy_mode
 + set_date=0
 + debug_level = 0
 + v_runs = 1
 + error_count=0
 +
 + try:
 + opts, args = getopt.getopt(argv, "hr:d:e:s:", ["help=","runs=", "debug=","energymode=","setdate="])
 + except getopt.GetoptError:
 + print sys.argv[0] + " -d <debug level 0 to 1> -r <runs> -e <0|1>  -s <0|1>"
 + sys.exit(2)
 + for opt, arg in opts:
 + if opt in ("-h", "--help"):
 + print sys.argv[0] + " -d <debug level 0 | 1 > -r <runs> -e < 0 | 1 > -s < 0 | 1 >"
 + sys.exit()
 + elif opt in ("-r", "--runs"):
 + v_runs = int(arg)
 + elif opt in ("-d", "--debug"):
 + debug_level = int(arg)
 + elif opt in ("-e", "--energymode"):
 + energy_mode = int(arg)
 + elif opt in ("-s", "--setdate"):
 + set_date = int(arg)
 +
 + print 95 * "-"
 + print "Start Reading the Time Value :: Runs :: {0} :: Debug Level {1} :: Energy Mode {2} :: Set OS Clock {3} ".format(v_runs, debug_level,energy_mode,set_date)
 +
 + success_read = False
 + #start decode the minutes
 + for cyles in range(v_runs):
 + #
 + #Start to get the start time frame
 + print 95 * "="
 + print "-- Read time data :: run {0}".format(cyles + 1)
 +
 + #If we have an read error or start
 + #if (success_read==False):
 + #find the start of the datagram
 + get59Seconde()
 +
 + #read
 + try:
 + print "-- Start to read the actual Minute Datagram"
 + decodeMinute()
 + success_read = True
 + print "\n"
 + except Bit0Error as error:
 + pdebug("-- Bit 0 Error")
 + error_count+=1
 + success_read = False
 + except Bit20Error as error:
 + pdebug("-- Bit 20 Error")
 + error_count+=1
 + success_read = False
 + except ParityBitError as error:
 + pdebug("-- Parity error")
 + error_count+=1
 + success_read = False
 + except:
 + print "-- Unkown error::", sys.exc_info()
 + success_read = False
 + error_count+=1
 +
 + #show the datagram
 + printData()
 + print "\n"
 +
 + # get date only if read was successfull
 + if (success_read):
 + try:
 + print "-- DCF77 Time     ::{0:02d}:{1:02d} ".format(getHourValue(), getMinuteValue())
 + print "-- DCF77 Calendar ::{0:02d}.{1:02d}.{2:4d} :: Day of the week {3}".format(getDayValue(),getMonthValue(), getYearValue(), getWeekDayValue())
 + print "-- DCF77 Timezone ::{0}".format(getTimeZoneValue())
 + print "-- DCF77 Timestamp::{0}".format(getDCF77TimeStamp())
 + print "--"
 + except ParityBitError as error:
 + pdebug("-- Parity error::" + error.args[0])
 + error_count+=1
 + success_read = False
 + except:
 + print "-- Unkown error::", sys.exc_info()
 + error_count+=1
 + success_read = False
 + #Set now also the date of the server
 + if (success_read):
 + setOSDate(set_date)
 +
 + # empty list for the next read
 + while len(datagram) > 0: datagram.pop()
 + success_read = False
 + print "--"
 + print "-- Finish Decode Minute :: OS internal date {0}".format(datetime.datetime.now().strftime("%H:%M:%S.%f"))
 +
 +
 + print 80 * "-"
 + print "Finish Reading the Time Value :: Runs :: {0} :: Errors {1}".format(v_runs, error_count)
 + print 80 * "-"
 +
 +# Call Main
 +if __name__ == "__main__":
 + main(sys.argv[1:])
 +
 +# Clean up
 +clean_exit()
 +
 +
 +</code>
 +
 +Nächste Schritte:
 +
 +Die Beachtung der Schaltsekunde ist noch nicht realisiert.
 +
 +Läuft das Programm in einer Schleife wird nur jeder zweiter Dataframe ausgewertet, da jedes mal wieder neu der Anfang gesucht werden muss, das klappt durch die Ausgaben und das Setzen der Zeit dann nicht jedes mal auch noch in der aktuellen Minute.
 +
 +Aufruf und Ausgabe:
 +{{ :elektronik:dcf77_test_prog_screen_v01.png | Ausgabe eines Python Scripts für die Auswertung eines DCF77 Empfängers am Raspberry}}
 +
 +
 +
 +== Performance Überlegungen==
 +
 +Der CPU Verbrauch des obigen Skripts hält sich in Grenzen, ohne weitere Last auf dem System ~ 1,6% CPU.
 +
 +Aber evtl. lässt sich noch etwas einsparen, wenn auf die Abtastung von 300ms bis 900ms verzichtet wird, d.h. Wenn der Zähler auf 30 steht, 600ms schlafen danach Zähler auf 90 und weiter prüfen.
 +
 +Scheint soweit auch zu funktionieren, allerdings ist der Vorteil kaum messbar.
 +
 +
 +
 +----
 +
 +=== Interrupt auf den Signal Eingange legen ===
 +
 +
 +Im ersten Schritt eine LED wird an einen weiteren GPIO Port angeschlossen, diese  soll im Sekunden Rhythmus blinken sobald der Pegel auf HIGH geht.
 +
 +Anschluss meines [[elektronik:led_mosfet_schalten_modul|einfaches LED Modul mit MOSFET 2N7002ET1G und einer Jumpo LED]]:
 +  * LED Input -IN  to Pin 15 GPIO 22
 +  * Ground - Pin 9
 +  * +5V    - Pin 4
 +
 +
 +Hier wird auf die Interrupt Methode gesetzt:
 +<code python>
 +import RPIO
 +import time
 +import signal
 + 
 + 
 +"""
 +Small Python Script to decode a DFC77 Module connect to the Raspberry 
 +Connects:
 + DFC77 Module - SIG to Pin 11 GPIO 17
 + LED Input -    IN  to Pin 15 GPIO 22
 +
 +runs in a endless loop
 + 
 +"""
 +#globals
 +blink_count=0
 +run=True
 +call_time=time.time()
 +
 +#define the interrupt function
 +def SetLed(gpio_id,val):
 + global blink_count
 + global call_time
 + time_gap=time.time()-call_time
 + if time_gap > 1.8:
 + blink_count=0
 + print "-- Fond First Minute "
 + #here we can start to decode the bits
 + else:
 + blink_count+=1
 + call_time=time.time()
 + global signal_state
 + RPIO.output(out_port,RPIO.HIGH)
 + print "-- Blink on Port 22 xXx  :: Count :: {0} :: Time since last Blink {1:15}".format(blink_count,time_gap)
 + time.sleep(0.2)
 + RPIO.output(out_port,RPIO.LOW)
 +
 +# Clean exit!
 +def clean_exit():
 + global run
 + RPIO.output(out_port,RPIO.LOW)
 + run=False
 + exit()
 +  
 +#define the handler if the program is stopped with ^c
 +def handler(signum, frame):
 + print "Catch Error {} - frame :: {}".format(signum,frame)
 + clean_exit()
 + 
 +# register the signal handler
 +signal.signal(signal.SIGINT, handler)
 +  
 +# supress warning
 +RPIO.setwarnings(False)
 + 
 +# Which RPIO Numbering you like to use
 +RPIO.setmode(RPIO.BCM)
 + 
 +# My RPIO PORTs
 +# OUT
 +out_port=22
 +RPIO.setup(out_port,RPIO.OUT)
 + 
 +# IN Port with the Signal
 +in_port=17
 +RPIO.setup(in_port,RPIO.IN)
 + 
 +# register the Interrupt on both
 +# RPIO.add_interrupt_callback(gpio_id=in_port,callback=SetLed, edge='both', pull_up_down=RPIO.PUD_OFF, threaded_callback=False, debounce_timeout_ms=None)
 +
 +# register the Interrupt on only Rising 
 +RPIO.add_interrupt_callback(gpio_id=in_port,callback=SetLed, edge='rising', pull_up_down=RPIO.PUD_OFF, debounce_timeout_ms=25)
 +  
 +# Non Blocking wait (own Thread will do the interrupt handling)
 +RPIO.wait_for_interrupts(threaded=True)
 + 
 +#Do something others
 +while run:
 +    # print "I'm in a loop and doing other things"
 +    time.sleep(10)
 +  
 +# Clean up
 +RPIO.cleanup()
 +</code>
 +
 +Und schon blinkt es so vor sich hin, die 59 Sekunde bleibt allerdings vorerst noch dunkel.
 +
 +Setze man nun den Interrupt auf Steigende und fallende Flanke sieht mach an den Ausgaben schön da wir LOW und HIGH gut erkennen können. Damit lässt sich ein deutlich einfacheres und genaueres Programm erstellen.
 +
 +Ablauf:
 +<code>
 +Methode 1 wird bei steigender Flanke gerufen
 +  HIGH Variable auf 0
 +  Messen wie viel Zeit seit letzten Aufruf von Methode 2 (fallende Flanke vergangen ist)
 +   => Falls > 1.7s (200ms max von 58Secounde + 800ms von 58s + 1000ms von 59 Sekunde ~ 1,7s mit Sicherheitszuschlag .-)) 
 +     => Start Minute gefunden  
 +        => Liste mit den Bitwerten um kopieren in die Ergebnis Liste
 +        => Liste mit den Bitwerten leeren
 +        => Bei Bedarf Systemzeit setzen        
 +Methode 2 wird bei fallender Flanke gerufen
 +  Über HIGH Variable messen wie viel Zeit vergangen ist
 +     => Falls > 190ms => Zur Liste mit dem Bit Werten eine 1 hinzufügen
 +     => Falls < 190ms => Zur Liste mit dem Bit Werten eine 0 hinzufügen
 +Endloss Schleife
 +    Prüfen ob alles geklappt hat, Parity Bits etc
 +    Anzeige der Ergebnisse über die Ergebnis Liste
 +    Ergebnis Liste in den Log File schreiben
 +</code>
 +
 +Mehr dann nach dem nächste Tatort .-)
 +
 +
 +----
 +
 +==== Erweiterungen ====
 +=== Weitere Erweiterungsmöglichkeiten ===
 +
 +=== Oberfläche in der Console ===
 +
 +Im nächsten Schritt kann das ganze dann mit zum Beispiel [[https://pypi.python.org/pypi/npyscreen|npyscreen]] mit einer hübschen Oberfläche optimiert werden.
 +
 +Installation von npyscreen
 +<code python>
 +yum install python-pip
 +python -m pip search npyscreen
 +python -m pip install  npyscreen
 +</code>
 +
 +Mehr dazu:
 +  * https://pypi.python.org/pypi/npyscreen
 +  * http://npyscreen.readthedocs.org/application-structure.html
 +
 +=== Anzeige der Uhrzeit===
 +
 +Anzeige der Uhrzeit auf einer 7 Segment Anzeige über Shift Register 
 + 
 +
 +
 +
 +----
 +
 +
 +----
 +==== Anschluss über Serial Interface ====
 +
 +Über eine COM Schnittstelle ginge das natürlich auch mit nativen Methoden und dann entsprechend einfacher als mit dem GPIO Anschluss.
 +
 +Bzgl. COM Schnittstelle:
 +  * http://www.obbl-net.de/dcf77.html
 +  * http://linuxwiki.de/EigenbauFunkuhr
 +  * http://www.davidhunt.ie/add-a-9-pin-serial-port-to-your-raspberry-pi-in-10-minutes/
 +  * http://blog.oscarliang.net/raspberry-pi-and-arduino-connected-serial-gpio/
 +  * http://blog.debuglevel.de/raspberry-pi-und-dcf77-empfaenger-von-conrad/
 +
 +
 +
 +----
 +
 +----
 +
 +==== Quellen ====
 +
 +Übersicht:
 +  * http://de.wikipedia.org/wiki/DCF77
 +  * http://www.ptb.de/cms/de/fachabteilungen/abt4/fb-44/ag-442/dissemination-of-legal-time/dcf77.html
 +
 +Material:
 +
 +  * https://github.com/rene0/dcf77pi
 +  * http://ems.eit.uni-kl.de/fileadmin/downloads/Task_4_B.pdf
 +  * http://www.dl3ukh.de/Bastel-Decoder.htm
 +  * http://www.picbasic.nl/frameload_uk.htm?http://www.picbasic.nl/info_dcf77_uk.htm
 +  * http://arduino-hannover.de/2012/06/14/dcf77-empfanger-mit-arduino-betreiben/
 +  * http://dokuwiki.ehajo.de/bausaetze:usb-dcf77
 +  * http://blog.blinkenlight.net/experiments/dcf77/binary-clock/
 +  * http://blog.blinkenlight.net/experiments/dcf77/phase-detection/
 +  * http://www.stefan-buchgeher.info/elektronik/dcf2/dcf_kap2.html#Kap2
 +  * http://www.kh-gps.de/dcf77.htm
 +  * http://www.endorphino.de/projects/electronics/timemanipulator/index.html
 +  * http://www.netzmafia.de/skripten/hardware/RasPi/Projekt-DCF77/index.html
 +
 +
  
"Autor: Gunther Pipperr"
raspberry/dcf77_modul.txt · Zuletzt geändert: 2016/01/06 15:28 von gpipperr