====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