你好哈布尔。
可能许多购买手表或气象站的人都在包装上看到过无线电控制时钟甚至原子钟标志。 这非常方便,因为你只需要把时钟放在桌子上,过一会儿它就会自动调整到准确的时间。
让我们弄清楚它是如何工作的并用 Python 编写一个解码器。
有不同的时间同步系统。 欧洲最流行的是德国制度
下面写的所有内容都是关于 DCF77 的。
信号接收
DCF77 是一个长波电台,工作频率为 77.5 kHz,以调幅方式传输信号。 50KW 车站距离法兰克福 25 公里,于 1959 年开始运营,并于 1973 年在准确时间中添加了日期信息。 77KHz频率下的波长很长,因此天线场的尺寸也相当不错(图片来自维基百科):
有了这样的天线和功率输入,接收区域几乎覆盖整个欧洲、白俄罗斯、乌克兰和俄罗斯的部分地区。
任何人都可以记录信号。 为此,只需转到在线接收器
在那里,我们按下下载按钮并记录一个几分钟长的片段。 当然,如果您有一个能够记录 77.5KHz 频率的“真正”接收器,则可以使用它。
当然,通过互联网接收无线电时间信号,我们不会收到真正准确的时间 - 信号传输有延迟。 但我们的目标只是了解信号的结构;为此,互联网记录就足够了。 当然,在现实生活中,需要使用专门的设备来进行接收和解码;它们将在下面讨论。
那么,我们已经收到了录音,让我们开始处理它。
信号解码
让我们使用 Python 加载该文件并查看其结构:
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()
我们看到典型的幅度调制:
为了简化解码,我们使用希尔伯特变换获取信号包络:
analytic_signal = signal.hilbert(data)
A = np.abs(analytic_signal)
plt.plot(A[:100000])
放大结果:
让我们使用低通滤波器平滑噪声发射,同时计算平均值,这对于稍后的解析很有用。
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
结果(黄线):几乎是方波信号,非常容易分析。
解析
首先您需要获取位序列。 信号结构本身非常简单。
脉冲被分成第二个间隔。 如果脉冲之间的距离为0.1s(即脉冲本身的长度为0.9s),则在位序列中添加“0”;如果距离为0.2s(即长度为0.8s),则在位序列中添加“1”。 每分钟的结束由一个 2 秒长的“长”脉冲指示,位序列重置为零,并再次开始填充。
上面的代码很容易用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
结果,我们得到了一个位序列,在我们的示例中,两秒钟的时间如下所示:
0011110110111000001011000001010000100110010101100010011000
0001111100110110001010100001010000100110010101100010011000
顺便说一句,有趣的是信号还有“第二层”数据。 位序列也使用编码
我们的最后一步:获取实际数据。 位每秒传输一次,因此总共有 59 位,其中编码了相当多的信息:
这些位的描述见
对于那些想要自己尝试的人,解码代码在剧透下给出。
源代码
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)
当我们运行该程序时,我们将看到类似以下的输出:
0011110110111000001011000001010000100110010101100010011000
Tuesday, 26.03.19, 21:41
0001111100110110001010100001010000100110010101100010011000
Tuesday, 26.03.19, 21:42
事实上,这就是所有的魔法。 这种系统的优点是解码非常简单,可以在任何微控制器上完成,甚至可以在最简单的微控制器上完成。 我们只需计算脉冲的长度,累加 60 位,然后在每分钟结束时得到准确的时间。 与其他时间同步方法(例如 GPS,或者上帝保佑,互联网:)相比,这种无线电同步几乎不需要电力 - 例如,一个常规的家庭气象站使用 2 节 AA 电池运行大约一年。 因此,连手表都是用电波同步制造的,当然更不用说挂表或街站表了。
DCF的方便和简单也吸引了DIY爱好者。 只需 10-20 美元,您就可以购买带有现成接收器和 TTL 输出的现成天线模块,该模块可以连接到 Arduino 或其他控制器。
已经为 Arduino 编写
那些愿意的人甚至可以通过安装具有无线电同步功能的新机制来升级他们老祖母的手表:
您可以使用关键字“无线电控制运动”在 eBay 上找到一个。
最后,为那些读过本文的人提供一个生活窍门。 即使接下来的几千公里内没有无线电信号发射器,自己产生这样的信号也不难。 Google Play 上有一个名为“DCF77 Emulator”的程序,可以将信号输出到耳机。 据作者介绍,如果你将耳机线绕在手表上,手表就会接收到信号(有趣的是,普通耳机不会产生 77KHz 信号,但接收可能是由于谐波所致)。 在 Android 9 上,该程序对我来说根本不起作用 - 根本没有声音(或者也许我没有听到它 - 毕竟它是 77KHz :),但也许有人会有更好的运气。 然而,有些将自己打造为成熟的 DCF 信号发生器,这很容易在同一个 Arduino 或 ESP32 上制作:
(源
结论
事实证明 DCF 系统确实非常简单和方便。 借助简单而便宜的接收器,您可以随时随地获得准确的时间,当然是在接待区。 看来,即使数字化和物联网广泛普及,这种简单的解决方案仍将在很长一段时间内受到需求。
来源: habr.com