البرامج المعرفة للراديو - كيف يعمل؟ الجزء 3

مرحبا يا هبر.

الجزء الثاني فحص الجوانب العملية لاستخدام حقوق السحب الخاصة. في هذا الجزء ، سنبحث في كيفية تلقي بيانات القمر الصناعي للطقس NOAA باستخدام Python وجهاز الاستقبال RTL-SDR غير المكلف (30 دولارًا). سيعمل الرمز المدروس في كل مكان - على أنظمة Windows و OSX و Linux وحتى على Raspberry Pi.



الذي يهتم ، واصلت تحت خفض.

SoapySDR


هناك الكثير من الشركات المصنعة لمختلف أجهزة حقوق السحب الخاصة ، وسيكون من غير المناسب للغاية دعم كل منها على حدة ، وسيكون مكلفًا من حيث شراء الأجهزة للاختبار. من حيث المبدأ ، للوصول الموحد ، هناك مكتبتان أصبحتا المعيار بشكل أساسي. الأول هو واجهة Extio DLL القديمة ، والتي ربما لا تقل عن 10 سنوات ، والثاني هو مكتبة SoapySDR الأكثر حداثة ، والتي سنبحثها .

SoapySDR هي مجموعة من المكتبات عبر الأنظمة الأساسية مكتوبة بلغة C ++ توفر وصولاً موحدًا إلى أجهزة حقوق السحب الخاصة (SDR) ، كل من المستقبلات وأجهزة الاستقبال. إذا قامت الشركة المصنعة بتصنيع هذه الواجهة ، فسيعمل الجهاز تلقائيًا مع عدد كبير إلى حد ما من البرامج الشائعة (GQRX ، GNU Radio ، CubicSDR ، إلخ). لدى جميع الشركات المصنعة المناسبة تقريبًا ، باستثناء بعضها ، (مع اغتنام هذه الفرصة ، أرسل تحياتي إلى EE) دعم SoapySDR ، يمكن العثور على قائمة بالأجهزة المدعومة في صفحة المشروع . كما ترى ، فهي كبيرة جدًا وتتضمن HackRF و USRP و SDRPlay و LimeSDR و RTL-SDR و Red Pitaya وغيرها الكثير.

مكتبة SoapySDR عبارة عن منصة مشتركة ، أي سيعمل الرمز المكتوب من أجله تحت أنظمة Windows و OSX و Linux وحتى على Raspberry Pi. بالنسبة لنظام التشغيل Windows ، تعد المكتبات المطلوبة جزءًا من حزمة PothosSDR ؛ وبالنسبة للأنظمة الأساسية الأخرى ، سيتعين تجميع SoapySDR بشكل مستقل. من الضروري تجميع جزأين - المكتبة نفسها ، و "المكوّن الإضافي" للمستقبل المرغوب ، وفي حالتنا سيكون SoapyRTLSDR (تحت نظام Windows ، يمكن أيضًا تجميع المكتبة من المصدر ، لذلك تحتاج إلى Visual Studio و Cmake و SWIG). الآن كل شيء جاهز ، ويمكنك كتابة التعليمات البرمجية.

نستورد المكتبة ونحصل على قائمة بالمستقبلات:

from __future__ import print_function import SoapySDR # Enumerate devices print("SDR devices:") for d in SoapySDR.Device.enumerate(''): print(d) print() 

نقوم بتوصيل جهاز الاستقبال وتشغيل الكود ورؤية قائمة من الأجهزة ، من بينها rtlsdr لدينا.



بقية الأجهزة عبارة عن بطاقات صوتية ، كما نذكر ، تاريخياً كانت أول حقوق السحب الخاصة تعمل بدقة من خلال الإدخال الخطي للكمبيوتر الشخصي ، كما تدعمها المكتبة. نحصل على معلومات حول الجهاز - عدد القنوات المتاحة ، ومدى التردد ، إلخ:

 soapy_device = "rtlsdr" device = SoapySDR.Device(dict(driver = soapy_device)) channels = list(range(device.getNumChannels(SoapySDR.SOAPY_SDR_RX))) print("Channels:", channels) ch = channels[0] sample_rates = device.listSampleRates(SoapySDR.SOAPY_SDR_RX, ch) print("Sample rates:\n", sample_rates) bandwidths = list(map(lambda r: int(r.maximum()), device.getBandwidthRange(SoapySDR.SOAPY_SDR_RX, ch))) print("Bandwidths:\n", bandwidths) print("Gain controls:") for gain in device.listGains(SoapySDR.SOAPY_SDR_RX, ch): print(" %s: %s" % (gain, device.getGainRange(SoapySDR.SOAPY_SDR_RX, ch, gain))) frequencies = device.listFrequencies(SoapySDR.SOAPY_SDR_RX, ch) print("Frequencies names:", frequencies) frequency_name = frequencies[0] print("Frequency channel name:", frequency_name) print("Frequency range:", device.getFrequencyRange(SoapySDR.SOAPY_SDR_RX, ch, frequency_name)[0]) 

نبدأ البرنامج ونرى معلومات عن المتلقي:



نرى أن لدى المستقبِل قناة إدخال واحدة تحمل الاسم "RF" ، وترددات أخذ العينات المحتملة [250000.0 ، 1024000.0 ، 1536000.0 ، 1792000.0 ، 1920000.0 ، 2048000.0 ، 2160000.0 ، 2560000.0 ، 2880000.0 ، 3200000.0] ونطاق التردد 24 ميجا هرتز -1.7 جيجاهرتز.

اختراق الحياة - يمكن أيضًا الحصول على نفس البيانات من سطر الأوامر عن طريق كتابة الأمر SoapySDRUtil --probe = "driver = rtlsdr" .

مع العلم بذلك ، يمكننا تسجيل دفق البيانات في WAV. كما ذكر في الجزء السابق ، يتم تمثيل البيانات من SDR بواسطة دفق من الإشارات تسمى I و Q ، والتي هي عينات من ADC ، تقريبًا يمكن تمثيلها كبيانات RAW من الكاميرا. يمكن لأي شخص مهتم بمزيد من التفاصيل أن يقرأ ، على سبيل المثال ، هنا . يكفي أن نعرف أنه يمكننا كتابة هذه البيانات ، ويمكن لبرامج حقوق السحب الخاصة الأخرى العمل معها.

السجل نفسه بسيط للغاية - تملأ وظيفة readStream المخزن المؤقت في حالة وجود بيانات ، وإذا لم تكن هناك بيانات بعد ، فسيتم إرجاع -1. يوجد أدناه رمز سجل مكون من 10 عينات (تم حذف أجزاء غير ضرورية من الشفرة).

 device.setFrequency(SoapySDR.SOAPY_SDR_RX, channel, "RF", frequency) device.setGain(SoapySDR.SOAPY_SDR_RX, channel, "TUNER", gain) device.setGainMode(SoapySDR.SOAPY_SDR_RX, channel, False) device.setSampleRate(SoapySDR.SOAPY_SDR_RX, channel, sample_rate) # Number of blocks to save block, max_blocks = 0, 10 block_size = device.getStreamMTU(stream) print("Block size:", block_size) buffer_format = np.int8 buffer_size = 2*block_size # I+Q buffer = np.empty(buffer_size, buffer_format) while True: d_info = device.readStream(stream, [buffer], buffer_size) if d_info.ret > 0: wav.write(buffer[0:2*d_info.ret]) print("Bytes saved:", 2*d_info.ret) block += 1 if block > max_blocks: break 

النتيجة في لقطة الشاشة:



كما ترون ، نحصل على كتل البيانات من الجهاز ، وحجم الكتلة الواحدة هو 131072 بايت ، والتي بمعدل أخذ العينات من 250،000 يعطينا مدة حوالي نصف ثانية. بشكل عام ، أولئك الذين عملوا مسبقًا مع بطاقات الصوت في Windows سيجدون الكثير من القواسم المشتركة.

للاختبار ، اكتب الملف وتحقق من أن كل شيء على ما يرام - يمكن تشغيله في SDR #. هناك خدعة أخرى هنا - لكي يُظهر SDR # ترددات المحطات بشكل صحيح ، يجب كتابة اسم الملف بتنسيق متوافق مع HDSDR ، من النموذج "HDSDR_20190518_115500Z_101000kHz_RF.wav" (كما قد تخمن ، التاريخ والوقت في توقيت جرينتش في البداية) . هذا سهل الكتابة في بايثون:

 frequency = 101000000 file_name = "HDSDR_%s_%dkHz_RF.wav" % (datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%SZ"), frequency/1000) 

أولاً ، تحقق من شريط FM. كل شيء على ما يرام ، المحطة مرئية ، الموسيقى قيد التشغيل ، RDS تعمل.



يمكنك البدء في تسجيل NOAA.

نوا المدخول


لذلك ، لدينا جهاز استقبال وبرنامج تسجيل. سنهتم بالأقمار الصناعية NOAA 15 و NOAA 18 و NOAA 19 التي تنقل صور سطح الأرض بترددات 137.620 و 137.9125 و 137.100 ميجا هرتز. الصعوبة الرئيسية هنا هي أنك تحتاج إلى "اللحاق" باللحظة التي يطير فيها القمر الصناعي فوقنا. يمكنك معرفة وقت الرحلة عبر الإنترنت على https://www.n2yo.com/passes/؟s=25338 و https://www.n2yo.com/passes/؟s=28654 و https://www.n2yo.com / يمر /؟ s = 33591 على التوالي.



لكي لا تجلس على الكمبيوتر ، أضف إلى البرنامج الانتظار في الوقت المناسب. سيسمح لك ذلك أيضًا بتشغيل البرنامج على Raspberry Pi ، بدون شاشة ولوحة مفاتيح.

 import datetime def wait_for_start(dt): # Wait for the start while True: now = datetime.datetime.now() diff = int((dt - now).total_seconds()) print("{:02d}:{:02d}:{:02d}: Recording will be started after {}m {:02d}s...".format(now.hour, now.minute, now.second, int(diff / 60), diff % 60)) time.sleep(5) if diff <= 1: break wait_for_start(datetime.datetime(2019, 5, 18, 21, 49, 0)) 


بالمناسبة ، لتشغيل البرنامج النصي على Raspberry Pi وتركه يعمل بعد إغلاق وحدة التحكم ، تحتاج إلى إدخال الأمر "nohup python recorder.py &".

كل شيء جاهز ، قم بتشغيل البرنامج النصي ويمكن أن يفعل أشياء أخرى ، يستمر التسجيل حوالي 20 دقيقة. في موازاة ذلك ، قد يطرح السؤال - هل من الممكن رؤية مرور القمر الصناعي بالعين المجردة؟ وفقًا للجدول ، يبلغ الحد الأقصى للسطوع حوالي 5.5 متر ، والحد الأقصى للعين البشرية في الظروف المثالية هو 6 أمتار. أي في سماء مظلمة حقًا ، بعيدًا عن المدينة ، يمكن ملاحظة مرور قمر نوا من الناحية النظرية ، في المدينة ، بالطبع ، ليست هناك فرصة (كما كتبوا عن حبري ، جيل من الناس لم يشاهدوا درب التبانة في حياتهم قد نما بالفعل).

نتيجة البرنامج النصي هي ملف wav مسجل ، يظهر طيفه في لقطة الشاشة.



نرى إشارة يمكن تمييزها تمامًا ، على الرغم من أنه بالطبع مع هوائي خاص لاستقبال NOAA ، فإن الجودة ستكون أفضل بكثير. يُسمى تنسيق الإشارة APT ( نقل الصورة التلقائي ) ، حيث يمكنك الحصول على صورة لسطح الأرض ، إذا كان أي شخص مهتمًا ، فيمكنك التفكير في فك تشفيره بشكل منفصل. ولكن هناك ، بالطبع ، برامج جاهزة ، يمكنك فك تشفير مثل هذه الإشارات باستخدام WxToImg أو MultiPSK.

من المثير للاهتمام أن نرى تحول دوبلر في الطيف ، والذي يحدث لأن القمر الصناعي يمر بنا. ربما ، ليس من الصعب حساب سرعته ، ومعرفة الوقت وتغيير التردد في هيرتز.

بالطبع ، يمكن استخدام البرنامج ليس فقط لتسجيل NOAA ، يمكن تحديد أي عرض النطاق الترددي والتردد في الإعدادات. بالنسبة لأولئك الذين يرغبون في تجربة SoapySDR أنفسهم ، يقع رمز البرنامج بالكامل تحت المفسد.

شفرة المصدر
 from __future__ import print_function import SoapySDR import numpy as np import struct import sys import time import datetime def wait_for_start(dt): # Wait for the start while True: now = datetime.datetime.now() diff = int((dt - now).total_seconds()) print("{:02d}:{:02d}:{:02d}: Recording will be started after {}m {:02d}s...".format(now.hour, now.minute, now.second, int(diff / 60), diff % 60)) time.sleep(5) if diff <= 1: break def sdr_enumerate(): # Enumerate SDR devices print("SDR devices:") for d in SoapySDR.Device.enumerate(''): print(d) print() def sdr_init(): soapy_device = "rtlsdr" device = SoapySDR.Device({"driver": soapy_device}) channels = list(range(device.getNumChannels(SoapySDR.SOAPY_SDR_RX))) print("Channels:", channels) ch = channels[0] sample_rates = device.listSampleRates(SoapySDR.SOAPY_SDR_RX, ch) print("Sample rates:\n", sample_rates) print("Gain controls:") for gain in device.listGains(SoapySDR.SOAPY_SDR_RX, ch): print(" %s: %s" % (gain, device.getGainRange(SoapySDR.SOAPY_SDR_RX, ch, gain))) frequencies = device.listFrequencies(SoapySDR.SOAPY_SDR_RX, ch) print("Frequencies names:", frequencies) frequency_name = frequencies[0] print("Frequency channel name:", frequency_name) print("Frequency range:", device.getFrequencyRange(SoapySDR.SOAPY_SDR_RX, ch, frequency_name)[0]) print() return device def sdr_record(device, frequency, sample_rate, gain, blocks_count): print("Frequency:", frequency) print("Sample rate:", sample_rate) print("Gain:", gain) channel = 0 # Always for RTL-SDR device.setFrequency(SoapySDR.SOAPY_SDR_RX, channel, "RF", frequency) device.setGain(SoapySDR.SOAPY_SDR_RX, channel, "TUNER", gain) device.setGainMode(SoapySDR.SOAPY_SDR_RX, channel, False) device.setSampleRate(SoapySDR.SOAPY_SDR_RX, channel, sample_rate) data_format = SoapySDR.SOAPY_SDR_CS8 # if 'rtlsdr' in soapy_device or 'hackrf' in soapy_device else SoapySDR.SOAPY_SDR_CS16 stream = device.setupStream(SoapySDR.SOAPY_SDR_RX, data_format, [channel], {}) device.activateStream(stream) block_size = device.getStreamMTU(stream) print("Block size:", block_size) print("Data format:", data_format) print() # IQ: 2 digits ver variable buffer_format = np.int8 buffer_size = 2 * block_size # I + Q samples buffer = np.empty(buffer_size, buffer_format) # Number of blocks to save block, max_blocks = 0, blocks_count # Save to file file_name = "HDSDR_%s_%dkHz_RF.wav" % (datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%SZ"), frequency/1000) print("Saving file:", file_name) with open(file_name, "wb") as wav: # Wav data info bits_per_sample = 16 channels_num, samples_num = 2, int(max_blocks * block_size) subchunk_size = 16 # always 16 for PCM subchunk2_size = int(samples_num * channels_num * bits_per_sample / 8) block_alignment = int(channels_num * bits_per_sample / 8) # Write RIFF header wav.write('RIFF'.encode('utf-8')) wav.write(struct.pack('<i', 4 + (8 + subchunk_size) + (8 + subchunk2_size))) # Size of the overall file wav.write('WAVE'.encode('utf-8')) # Write fmt subchunk wav.write('fmt '.encode('utf-8')) # chunk type wav.write(struct.pack('<i', subchunk_size)) # subchunk data size (16 for PCM) wav.write(struct.pack('<h', 1)) # compression type 1 - PCM wav.write(struct.pack('<h', channels_num)) # channels wav.write(struct.pack('<i', int(sample_rate))) # sample rate wav.write(struct.pack('<i', int(sample_rate * bits_per_sample * channels_num/ 8))) # byte rate wav.write(struct.pack('<h', block_alignment)) # block alignment wav.write(struct.pack('<h', bits_per_sample)) # sample depth # Write data subchunk wav.write('data'.encode('utf-8')) wav.write(struct.pack('<i', subchunk2_size)) while True: d_info = device.readStream(stream, [buffer], buffer_size) if d_info.ret > 0: data = buffer[0:2*d_info.ret] fileData = data if data_format == SoapySDR.SOAPY_SDR_CS8: fileData = data.astype('int16') wav.write(fileData) print("Block %d saved: %d bytes" % (block, 2*d_info.ret)) block += 1 if block > max_blocks: break device.deactivateStream(stream) device.closeStream(stream) if __name__ == "__main__": print("App started") # Forecast for active NOAA satellites # NOAA 15: 137.620, https://www.n2yo.com/passes/?s=25338 # NOAA 18: 137.9125, https://www.n2yo.com/passes/?s=28654 # NOAA 19: 137.100, https://www.n2yo.com/passes/?s=33591 # Wait for the start: 18-May 21:49 21:49: wait_for_start(datetime.datetime(2019, 5, 18, 21, 49, 0)) device = sdr_init() t_start = time.time() sdr_record(device, frequency=137912500, sample_rate=250000, gain=35, blocks_count=2100) print("Recording complete, time = %ds" % int(time.time() - t_start)) print() 


SoapySDR plus هو أن البرنامج نفسه مع الحد الأدنى من التغييرات سوف يعمل مع أجهزة الاستقبال الأخرى ، على سبيل المثال مع SDRPlay أو HackRF. حسنا ، حول منصة مشتركة ، أيضا ، وقد سبق ذكره.

إذا كان القراء لا يزالون مهتمين بموضوع استقبال الراديو ، فيمكنك التفكير في استخدام SDR مع GNU Radio من خلال إنشاء عدة مستقبلات افتراضية تعتمد على جهاز واحد.

Source: https://habr.com/ru/post/ar452390/


All Articles