Hej Habr.
Förmodligen har många som köper en klocka eller väderstation sett Radio Controlled Clock eller till och med Atomic Clock-logotypen på förpackningen. Detta är väldigt bekvämt, eftersom du bara behöver lägga klockan på bordet, och efter ett tag kommer den automatiskt att anpassa sig till den exakta tiden.
Låt oss ta reda på hur det fungerar och skriva en avkodare i Python.
Det finns olika tidssynkroniseringssystem. Det mest populära i Europa är det tyska systemet
Allt som skrivs nedan kommer att handla om DCF77.
Signalmottagning
DCF77 är en långvågsstation som arbetar med en frekvens på 77.5 kHz och sänder signaler i amplitudmodulering. 50KW-stationen ligger 25 km från Frankfurt, den började fungera 1959 och 1973 lades datuminformation till den exakta tiden. Våglängden vid en frekvens på 77 KHz är mycket lång, så dimensionerna på antennfältet är också ganska anständiga (foto från Wikipedia):
Med en sådan antenn och strömingång täcker mottagningsområdet nästan hela Europa, Vitryssland, Ukraina och en del av Ryssland.
Vem som helst kan spela in en signal. För att göra detta, gå bara till online-mottagaren
Där trycker vi på nedladdningsknappen och spelar in ett flera minuter långt fragment. Naturligtvis, om du har en "riktig" mottagare som kan spela in 77.5KHz-frekvensen, kan du använda den.
Genom att ta emot radiotidssignaler via Internet kommer vi naturligtvis inte att få riktigt exakt tid - signalen sänds med en fördröjning. Men vårt mål är bara att förstå strukturen på signalen, för detta räcker internetinspelningen mer än nog. I verkliga livet används naturligtvis specialiserade enheter för att ta emot och avkoda; de kommer att diskuteras nedan.
Så vi har fått inspelningen, låt oss börja bearbeta den.
Signalavkodning
Låt oss ladda filen med Python och se dess struktur:
from scipy.io import wavfile
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
sample_rate, data = wavfile.read("dcf_websdr_2019-03-26T20_25_34Z_76.6kHz.wav")
plt.plot(data[:100000])
plt.show()
Vi ser typisk amplitudmodulering:
För att förenkla avkodningen, låt oss ta signalenveloppen med hjälp av Hilbert-transformen:
analytic_signal = signal.hilbert(data)
A = np.abs(analytic_signal)
plt.plot(A[:100000])
Förstorat resultat:
Låt oss jämna ut brusemissioner med ett lågpassfilter och samtidigt beräkna medelvärdet, vilket kommer att vara användbart senare för analys.
b, a = signal.butter(2, 20.0/sample_rate)
zi = signal.lfilter_zi(b, a)
A, _ = signal.lfilter(b, a, A, zi=zi*A[0])
avg = (np.amax(A) + np.amin(A))/2
Resultat (gul linje): en nästan fyrkantsvågsignal som är ganska lätt att analysera.
Parsing
Först måste du få bitsekvensen. Signalstrukturen i sig är väldigt enkel.
Pulserna är uppdelade i andra intervall. Om avståndet mellan pulserna är 0.1 s (dvs längden på själva pulsen är 0.9 s), lägg till "0" till bitsekvensen; om avståndet är 0.2 s (dvs längden är 0.8 s), lägg till "1". Slutet av varje minut indikeras av en "lång" puls, 2s lång, bitsekvensen nollställs och fyllningen börjar igen.
Ovanstående är lätt att skriva i Python.
sig_start, sig_stop = 0, 0
pos = 0
bits_str = ""
while pos < cnt - 4:
if A[pos] < avg and A[pos+1] > avg:
# Signal begin
sig_start = pos
if A[pos] > avg and A[pos+1] < avg:
# Signal end
sig_stop = pos
diff = sig_stop - sig_start
if diff < 0.85*sample_rate:
bits_str += "1"
if diff > 0.85*sample_rate and diff < 1.25*sample_rate:
bits_str += "0"
if diff > 1.5*sample_rate:
print(bits_str)
bits_str = ""
pos += 1
Som ett resultat får vi en sekvens av bitar, i vårt exempel under två sekunder ser det ut så här:
0011110110111000001011000001010000100110010101100010011000
0001111100110110001010100001010000100110010101100010011000
Förresten, det är intressant att signalen också har ett "andra lager" av data. Bitsekvensen kodas också med hjälp av
Vårt sista steg: få de faktiska uppgifterna. Bitar sänds en gång per sekund, så vi har totalt 59 bitar, där ganska mycket information är kodad:
Bitarna beskrivs i
För den som vill experimentera på egen hand ges avkodningskoden under spoilern.
Källkod
def decode(bits):
if bits[0] != '0' or bits[20] != '1':
return
minutes, hours, day_of_month, weekday, month, year = map(convert_block,
(bits[21:28], bits[29:35], bits[36:42], bits[42:45],
bits[45:50], bits[50:58]))
days = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday')
print('{dow}, {dom:02}.{mon:02}.{y}, {h:02}:{m:02}'.format(h=hours, m=minutes, dow=days[weekday],
dom=day_of_month, mon=month, y=year))
def convert_ones(bits):
return sum(2**i for i, bit in enumerate(bits) if bit == '1')
def convert_tens(bits):
return 10*convert_ones(bits)
def right_parity(bits, parity_bit):
num_of_ones = sum(int(bit) for bit in bits)
return num_of_ones % 2 == int(parity_bit)
def convert_block(bits, parity=False):
if parity and not right_parity(bits[:-1], bits[-1]):
return -1
ones = bits[:4]
tens = bits[4:]
return convert_tens(tens) + convert_ones(ones)
När vi kör programmet kommer vi att se utdata som liknar detta:
0011110110111000001011000001010000100110010101100010011000
Tuesday, 26.03.19, 21:41
0001111100110110001010100001010000100110010101100010011000
Tuesday, 26.03.19, 21:42
Det är faktiskt allt som är magin. Fördelen med ett sådant system är att avkodningen är extremt enkel och kan göras på vilken som helst, även den enklaste mikrokontrollern. Vi räknar helt enkelt längden på pulserna, ackumulerar 60 bitar, och i slutet av varje minut får vi den exakta tiden. Jämfört med andra metoder för tidssynkronisering (GPS, till exempel, eller gud förbjude, internet:) kräver sådan radiosynkronisering praktiskt taget ingen elektricitet - till exempel går en vanlig väderstation för hemmabruk i ungefär ett år på 2 AA-batterier. Därför är även armbandsur gjorda med radiosynkronisering, för att förstås inte tala om väggur eller gatustationsklockor.
Bekvämligheten och enkelheten hos DCF lockar också gör-det-själv-entusiaster. För bara $10-20 kan du köpa en färdig antennmodul med färdig mottagare och TTL-utgång, som kan kopplas till en Arduino eller annan styrenhet.
Redan skriven för Arduino
De som önskar kan till och med uppgradera sin gamla mormors klocka genom att installera en ny mekanism med radiosynkronisering:
Du kan hitta en på ebay med nyckelorden "Radio Controlled Movement".
Och till sist, ett life hack för dem som har läst så här långt. Även om det inte finns en enda radiosignalsändare inom de närmaste tusentals km, är det inte svårt att själv generera en sådan signal. Det finns ett program på Google Play som heter "DCF77 Emulator" som matar ut signalen till hörlurar. Enligt författaren, om du lindar hörlurarnas tråd runt klockan, kommer de att fånga upp signalen (det är intressant hur, eftersom vanliga hörlurar inte kommer att producera en 77KHz-signal, men mottagningen beror förmodligen på övertoner). På Android 9 fungerade programmet inte alls för mig - det fanns helt enkelt inget ljud (eller jag kanske inte hörde det - det är trots allt 77KHz:), men kanske någon har bättre tur. Vissa gör sig dock till en fullfjädrad DCF-signalgenerator, som är lätt att göra på samma Arduino eller ESP32:
(källa
Slutsats
DCF-systemet visade sig egentligen vara ganska enkelt och bekvämt. Med hjälp av en enkel och billig mottagare kan du ha den exakta tiden alltid och överallt, givetvis i receptionen. Det verkar som att även trots utbredd digitalisering och Internet of Things kommer sådana enkla lösningar att efterfrågas under lång tid.
Källa: will.com