====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 0 und 1 in der Sekunde 0 bis 58 Nur in der 59 Sekunde bleibt der Pegel an SIG immer auf 0 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 0, da zwei mal 1 (=gerade Anzahl) gesendet wurde. Wird 7 Uhr empfangen, Bitfolge 111000 ist das Prüfbit 1 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: 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() 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: #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 ==== 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: 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. Ein erster Versuch inkl. der Überprüfung der Parität und setzen der OS Uhrzeit: __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 -r -e <0|1> -s <0|1>" sys.exit(2) for opt, arg in opts: if opt in ("-h", "--help"): print sys.argv[0] + " -d -r -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() 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: 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() 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: 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 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 yum install python-pip python -m pip search npyscreen python -m pip install npyscreen 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