Xin chào Habr.
Có lẽ nhiều người mua đồng hồ hoặc trạm thời tiết đã nhìn thấy Đồng hồ điều khiển bằng sóng vô tuyến hoặc thậm chí là logo Đồng hồ nguyên tử trên bao bì. Điều này rất tiện lợi, vì bạn chỉ cần đặt đồng hồ lên bàn, một lúc sau nó sẽ tự động điều chỉnh về thời gian chính xác.
Hãy tìm hiểu cách nó hoạt động và viết bộ giải mã bằng Python.
Có nhiều hệ thống đồng bộ hóa thời gian khác nhau. Phổ biến nhất ở châu Âu là hệ thống của Đức
Mọi thứ được viết dưới đây sẽ là về DCF77.
Tiếp nhận tín hiệu
DCF77 là trạm sóng dài hoạt động ở tần số 77.5 kHz và truyền tín hiệu ở dạng điều chế biên độ. Trạm 50KW nằm cách Frankfurt 25 km, bắt đầu hoạt động từ năm 1959, đến năm 1973 thông tin ngày tháng được bổ sung chính xác về thời gian. Bước sóng ở tần số 77 KHz rất dài nên kích thước của trường ăng-ten cũng khá tốt (ảnh từ Wikipedia):
Với ăng-ten và nguồn điện đầu vào như vậy, khu vực thu sóng bao phủ gần như toàn bộ Châu Âu, Belarus, Ukraine và một phần của Nga.
Bất cứ ai cũng có thể ghi lại tín hiệu. Để thực hiện việc này, chỉ cần truy cập bộ thu trực tuyến
Ở đó, chúng tôi nhấn nút tải xuống và ghi lại một đoạn dài vài phút. Tất nhiên, nếu bạn có một bộ thu “thực” có khả năng ghi tần số 77.5KHz, bạn có thể sử dụng nó.
Tất nhiên, khi nhận tín hiệu thời gian vô tuyến qua Internet, chúng ta sẽ không nhận được thời gian thực sự chính xác - tín hiệu được truyền đi có độ trễ. Nhưng mục tiêu của chúng tôi chỉ là hiểu cấu trúc của tín hiệu; đối với điều này, việc ghi lại trên Internet là quá đủ. Tất nhiên, trong đời thực, các thiết bị chuyên dụng được sử dụng để thu và giải mã, chúng sẽ được thảo luận dưới đây.
Vậy là chúng ta đã nhận được đoạn ghi âm, hãy bắt đầu xử lý nó nhé.
Giải mã tín hiệu
Hãy tải tệp bằng Python và xem cấu trúc của nó:
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()
Chúng ta thấy điều chế biên độ điển hình:
Để đơn giản hóa việc giải mã, hãy lấy đường bao tín hiệu bằng phép biến đổi Hilbert:
analytic_signal = signal.hilbert(data)
A = np.abs(analytic_signal)
plt.plot(A[:100000])
Kết quả phóng to:
Hãy làm giảm tiếng ồn phát ra bằng bộ lọc thông thấp, đồng thời tính giá trị trung bình, giá trị này sẽ hữu ích cho việc phân tích cú pháp sau này.
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
Kết quả (đường màu vàng): tín hiệu sóng gần như vuông khá dễ phân tích.
Phân tích cú pháp
Đầu tiên bạn cần lấy chuỗi bit. Bản thân cấu trúc tín hiệu rất đơn giản.
Các xung được chia thành các khoảng thứ hai. Nếu khoảng cách giữa các xung là 0.1 giây (tức là độ dài của xung là 0.9 giây), hãy thêm “0” vào chuỗi bit; nếu khoảng cách là 0.2 giây (tức là độ dài là 0.8 giây), hãy thêm “1”. Sự kết thúc của mỗi phút được biểu thị bằng một xung “dài”, dài 2 giây, chuỗi bit được đặt lại về XNUMX và quá trình điền lại bắt đầu.
Ở trên rất dễ viết bằng 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
Kết quả là chúng ta nhận được một chuỗi bit, trong ví dụ của chúng ta trong hai giây, nó trông như thế này:
0011110110111000001011000001010000100110010101100010011000
0001111100110110001010100001010000100110010101100010011000
Nhân tiện, điều thú vị là tín hiệu cũng có “lớp dữ liệu thứ hai”. Chuỗi bit cũng được mã hóa bằng cách sử dụng
Bước cuối cùng của chúng tôi: lấy dữ liệu thực tế. Các bit được truyền đi một lần trong một giây nên chúng ta có tổng cộng 59 bit, trong đó khá nhiều thông tin được mã hóa:
Các bit được mô tả trong
Đối với những người muốn tự mình thử nghiệm, mã giải mã được cung cấp dưới phần spoiler.
Mã nguồn
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)
Khi chạy chương trình, chúng ta sẽ thấy kết quả tương tự như sau:
0011110110111000001011000001010000100110010101100010011000
Tuesday, 26.03.19, 21:41
0001111100110110001010100001010000100110010101100010011000
Tuesday, 26.03.19, 21:42
Trên thực tế, đó là tất cả sự kỳ diệu. Ưu điểm của hệ thống như vậy là việc giải mã cực kỳ đơn giản và có thể được thực hiện trên bất kỳ bộ vi điều khiển đơn giản nhất nào. Chúng tôi chỉ cần đếm độ dài của các xung, tích lũy 60 bit và vào cuối mỗi phút, chúng tôi sẽ có được thời gian chính xác. So với các phương pháp đồng bộ hóa thời gian khác (chẳng hạn như GPS hoặc Internet :), việc đồng bộ hóa vô tuyến như vậy hầu như không cần điện - ví dụ: một trạm thời tiết thông thường tại nhà chạy trong khoảng một năm với 2 pin AA. Vì vậy, ngay cả đồng hồ đeo tay cũng được chế tạo với chức năng đồng bộ hóa sóng vô tuyến, tất nhiên là chưa kể đến đồng hồ treo tường hay đồng hồ trên đường phố.
Sự tiện lợi và đơn giản của DCF cũng thu hút những người đam mê DIY. Chỉ với 10-20 USD, bạn có thể mua mô-đun ăng-ten làm sẵn với bộ thu và đầu ra TTL làm sẵn, có thể kết nối với Arduino hoặc bộ điều khiển khác.
Đã được viết cho Arduino
Những người muốn thậm chí có thể nâng cấp chiếc đồng hồ của bà già mình bằng cách cài đặt một cơ chế mới với tính năng đồng bộ hóa vô tuyến:
Bạn có thể tìm thấy một cái trên ebay bằng cách sử dụng từ khóa “Chuyển động được điều khiển bằng sóng vô tuyến”.
Và cuối cùng, một mẹo hay dành cho những ai đã đọc đến đây. Ngay cả khi không có một máy phát tín hiệu vô tuyến nào trong vài nghìn km tới, việc tự tạo ra tín hiệu như vậy không khó. Có một chương trình trên Google Play có tên là “Trình mô phỏng DCF77” giúp xuất tín hiệu đến tai nghe. Theo tác giả, nếu bạn quấn dây tai nghe quanh đồng hồ, chúng sẽ bắt được tín hiệu (điều thú vị là vì tai nghe thông thường sẽ không tạo ra tín hiệu 77KHz mà khả năng thu sóng có thể là do sóng hài). Trên Android 9, chương trình hoàn toàn không hoạt động đối với tôi - đơn giản là không có âm thanh (hoặc có thể tôi không nghe thấy nó - xét cho cùng thì đó là 77KHz :), nhưng có lẽ ai đó sẽ gặp may mắn hơn. Tuy nhiên, một số lại tự biến mình thành bộ tạo tín hiệu DCF chính thức, dễ dàng thực hiện trên cùng một Arduino hoặc ESP32:
(một nguồn
Kết luận
Hệ thống DCF hóa ra thực sự khá đơn giản và tiện lợi. Với sự trợ giúp của một máy thu đơn giản và rẻ tiền, bạn có thể có được thời gian chính xác mọi lúc, mọi nơi, tất nhiên là ở khu vực lễ tân. Có vẻ như ngay cả khi số hóa và Internet vạn vật được phổ biến rộng rãi, những giải pháp đơn giản như vậy vẫn sẽ được yêu cầu trong thời gian dài.
Nguồn: www.habr.com