Software Defined Radio — как это работает? Часть 4

  • Tutorial
Привет, Хабр.

В третьей части было рассказано, как получить доступ к SDR-приемнику посредством языка Python. Сейчас мы познакомимся с программой GNU Radio — системой, позволяющей создать достаточно сложную конфигурацию радиоустройства, не написав ни единой строчки кода.



Для примера рассмотрим задачу параллельного приема нескольких FM-станций на один приемник. В качестве приемника будем использовать все тот же RTL SDR V3.

Продолжение под катом.

Установка


Для начала работы GNU Radio необходимо установить, дистрибутив для Windows можно скачать здесь. Система эта кроссплатформенная, версии есть также под Linux и под OSX (вроде бы GNU Radio успешно запускали и на Raspberry Pi, но 100% гарантии дать не могу).

По сути, GNU Radio это целый фреймворк для цифровой обработки сигналов, в котором программа «собирается» из отдельных модулей. Есть большое количество уже готовых блоков, при желании также можно создавать свои собственные. Сами модули написаны на С++, а для взаимодействия блоков друг с другом используется Python. Желающие могут посмотреть на API более подробно, но на практике это, скорее всего, не пригодится — все действия можно делать визуально в программе GNU Radio Companion.

Система ориентирована на обработку потоков данных, так что каждый блок обычно имеет вход и выход. Далее, соединяя блоки в редакторе, мы получаем готовую систему. Сам интерфейс GNU Radio довольно простой, сложность состоит в понимании того, что делает тот или иной блок. Как говорилось ранее, низкоуровневая работа с SDR имеет высокий порог входа и требует некоторого знания в DSP и математике. Но мы рассмотрим простую задачу, для которой никаких специальных знаний не потребуется. Итак, приступим.

Начало работы


Запускаем GNU Radio Companion, создаем новый проект, тип проекта выбираем WX GUI, добавляем на экран и соединяем два блока, как показано на скриншоте.



Мы видим два типа блоков — Source (источник) и Sink (выход, «слив»). RTL-SDR — это наш приемник, FFT GUI — это виртуальный спектроанализатор.

Переменную Sample Rate устанавливаем в 2048000, это частота дискретизации нашего приемника. Частоту RTL-SDR оставляем по умолчанию равной 100МГц.

Запускаем проект — все работает, мы видим спектр FM-станций. Первая программа для GNU Radio готова!



Если мы посмотрим лог, то увидим такие строки.

Generating: 'D:\\MyProjects\\GNURadio\\top_block.py'
Executing: C:\Python27\python.exe -u D:\MyProjects\GNURadio\top_block.py


Да, мы можем посмотреть файл top_block.py, который сгенерил нам GNU Radio Companion. Истинные джедаи могут писать непосредственно в Python, но требуемый код, как мы видим, довольно большой. Мы же создали его за 1 минуту.

top_blocks.py
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
##################################################
# GNU Radio Python Flow Graph
# Title: Top Block
# Generated: Wed May 22 22:05:14 2019
##################################################

if __name__ == '__main__':
    import ctypes
    import sys
    if sys.platform.startswith('linux'):
        try:
            x11 = ctypes.cdll.LoadLibrary('libX11.so')
            x11.XInitThreads()
        except:
            print "Warning: failed to XInitThreads()"

from gnuradio import eng_notation
from gnuradio import gr
from gnuradio import wxgui
from gnuradio.eng_option import eng_option
from gnuradio.fft import window
from gnuradio.filter import firdes
from gnuradio.wxgui import fftsink2
from grc_gnuradio import wxgui as grc_wxgui
from optparse import OptionParser
import osmosdr
import time
import wx


class top_block(grc_wxgui.top_block_gui):

    def __init__(self):
        grc_wxgui.top_block_gui.__init__(self, title="Top Block")

        ##################################################
        # Variables
        ##################################################
        self.samp_rate = samp_rate = 2048000

        ##################################################
        # Blocks
        ##################################################
        self.wxgui_fftsink2_0 = fftsink2.fft_sink_c(
        	self.GetWin(),
        	baseband_freq=0,
        	y_per_div=10,
        	y_divs=10,
        	ref_level=0,
        	ref_scale=2.0,
        	sample_rate=samp_rate,
        	fft_size=1024,
        	fft_rate=15,
        	average=False,
        	avg_alpha=None,
        	title='FFT Plot',
        	peak_hold=False,
        )
        self.Add(self.wxgui_fftsink2_0.win)
        self.rtlsdr_source_0 = osmosdr.source( args="numchan=" + str(1) + " " + '' )
        self.rtlsdr_source_0.set_sample_rate(samp_rate)
        self.rtlsdr_source_0.set_center_freq(100e6, 0)
        self.rtlsdr_source_0.set_freq_corr(0, 0)
        self.rtlsdr_source_0.set_dc_offset_mode(0, 0)
        self.rtlsdr_source_0.set_iq_balance_mode(0, 0)
        self.rtlsdr_source_0.set_gain_mode(False, 0)
        self.rtlsdr_source_0.set_gain(10, 0)
        self.rtlsdr_source_0.set_if_gain(20, 0)
        self.rtlsdr_source_0.set_bb_gain(20, 0)
        self.rtlsdr_source_0.set_antenna('', 0)
        self.rtlsdr_source_0.set_bandwidth(0, 0)


        ##################################################
        # Connections
        ##################################################
        self.connect((self.rtlsdr_source_0, 0), (self.wxgui_fftsink2_0, 0))

    def get_samp_rate(self):
        return self.samp_rate

    def set_samp_rate(self, samp_rate):
        self.samp_rate = samp_rate
        self.wxgui_fftsink2_0.set_sample_rate(self.samp_rate)
        self.rtlsdr_source_0.set_sample_rate(self.samp_rate)


def main(top_block_cls=top_block, options=None):

    tb = top_block_cls()
    tb.Start(True)
    tb.Wait()


if __name__ == '__main__':
    main()


Впрочем, если убрать громоздкую инициализацию, то мы увидим, что ключевых строк кода не так уж много.
from gnuradio import gr
from gnuradio.wxgui import fftsink2
import osmosdr

class top_block(grc_wxgui.top_block_gui):

    def __init__(self):
        grc_wxgui.top_block_gui.__init__(self, title="Top Block")
        self.samp_rate = samp_rate = 2048000
        self.wxgui_fftsink2_0 = fftsink2.fft_sink_c(...)
        self.Add(self.wxgui_fftsink2_0.win)
        self.rtlsdr_source_0 = osmosdr.source(args="numchan=" + str(1) + " " + '' )
        self.connect((self.rtlsdr_source_0, 0), (self.wxgui_fftsink2_0, 0))

def main(top_block_cls=top_block, options=None):
    tb = top_block_cls()
    tb.Start(True)
    tb.Wait()

Так что в принципе, это можно написать вручную. Но мышью оно все-таки быстрее. Хотя возможность поменять код иногда может пригодиться, если захочется добавить какую-то нестандартную логику.

Принимаем FM-радио


Теперь попробуем принять одну из станций. Как было видно из скриншотов, центральная частота приемника 100МГц и ширина полосы пропускания около 2МГц. На спектре мы видим две станции, на 100.1МГц и 100.7МГц соответственно.

Первым шагом необходимо перенести спектр станции в центр, сейчас он отстоит вправо на 100КГц. Для этого вспоминаем школьную формулу умножения косинусов — в результате будет две частоты, сумма и разность — нужная станция сдвинется в центр, что нам и нужно (а лишнее мы потом отфильтруем).

Создаем две переменные для хранения частоты freq_center=100000000 и freq_1=100100000, также добавляем генератор сигналов с частотой freq_center — freq_1.



Т.к. система построена на базе Python, то в полях ввода параметров мы можем использовать выражения, что достаточно удобно.

Схема в итоге должна выглядеть так:



Теперь необходимо добавить сразу несколько блоков — уменьшить тактовую частоту входного сигнала (она равна 2048КГц), отфильтровать сигнал, подать его на FM-декодер, затем еще раз уменьшить тактовую частоту до 48КГц.

Результат показан на картинке:



Считаем внимательно. Мы делим тактовую частоту 2048КГц в 4 раза блоком Rational Resampler (получаем 512КГц), затем после Low Pass фильтра стоит WBFM-декодер с децимацией 10 (получаем 51.2КГц). В принципе, этот сигнал уже можно подать на звуковую карту, но высота тона будет чуть отличаться. Еще раз меняем тактовую частоту в 48/51, в результате будет тактовая частота 48.2КГц, разницей уже можно пренебречь.

Второй важный момент — тип входов. С приемника поступает комплексный IQ-сигнал (входы-выходы синего цвета), с FM-декодера выходит вещественный сигнал — входы и выходы желтого цвета. Если перепутать, ничего не заработает. Подробнее уже было на Хабре, нам достаточно понять общий принцип.

В общем, запускаем, убеждаемся что все работает. Можно запустить программу и слушать радио. Мы пойдем дальше — у нас же все-таки Software Defined радио — добавим одновременный прием второй станции.

Многоканальный прием


Второй приемник добавляется любимым программистским методом — Ctrl+C/Ctrl+V. Добавляем переменную freq_2, копируем блоки и соединяем их точно также.



Результат вполне сюрреалистичный — две FM-станции можно слушать одновременно. Тем же самым методом (Ctrl+V) можно добавить и третью станцию.

Запись


Слушать две станции оригинально, но на практике мало полезно. Сделаем что-то более нужное, например добавим запись звука в отдельные файлы. Это может быть достаточно удобно — с одного физического приемника можно параллельно записывать несколько каналов.

Добавим к каждому выходу компонент File Sink, как показано на скриншоте.



Windows-версия почему-то требует абсолютные пути файлов, иначе запись не работает. Запускаем, убеждаемся что все нормально. Размер сохраняемых файлов довольно большой, т.к. по умолчанию записывается формат float. Запись в формате int оставлю читателям в качестве домашнего задания.

Получившиеся файлы можно открыть в Cool Edit и убедиться, что звук записался нормально.





Разумеется, число записываемых каналов можно увеличить, оно ограничено только полосой пропускания приемника и мощностью компьютера. Кроме File Sink можно использовать и UDP Sink, так что программу можно использовать для трансляции по сети.

Запуск из командной строки


И последнее. Если использовать программу автономно, например для многоканальной записи, то UI в принципе и не нужен. В верхнем левом блоке Options меняем параметр Run Options на No UI. Запускаем программу еще раз, убеждаемся что все работает. Теперь сохраняем сгенерированный файл top_block.py — мы можем просто запускать его из командной строки, например из bat-файла или из консоли.



Если кому интересно, сгенерированный файл сохранен под спойлером.
recorder.py
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
##################################################
# GNU Radio Python Flow Graph
# Title: Top Block
# Generated: Fri May 24 21:47:03 2019
##################################################

from gnuradio import analog
from gnuradio import audio
from gnuradio import blocks
from gnuradio import eng_notation
from gnuradio import filter
from gnuradio import gr
from gnuradio.eng_option import eng_option
from gnuradio.filter import firdes
from optparse import OptionParser
import osmosdr
import time


class top_block(gr.top_block):

    def __init__(self):
        gr.top_block.__init__(self, "Top Block")

        ##################################################
        # Variables
        ##################################################
        self.samp_rate = samp_rate = 2048000
        self.freq_center = freq_center = 100000000
        self.freq_2 = freq_2 = 100700000
        self.freq_1 = freq_1 = 100100000

        ##################################################
        # Blocks
        ##################################################
        self.rtlsdr_source_0 = osmosdr.source( args="numchan=" + str(1) + " " + '' )
        self.rtlsdr_source_0.set_sample_rate(samp_rate)
        self.rtlsdr_source_0.set_center_freq(freq_center, 0)
        self.rtlsdr_source_0.set_freq_corr(0, 0)
        self.rtlsdr_source_0.set_dc_offset_mode(0, 0)
        self.rtlsdr_source_0.set_iq_balance_mode(0, 0)
        self.rtlsdr_source_0.set_gain_mode(False, 0)
        self.rtlsdr_source_0.set_gain(10, 0)
        self.rtlsdr_source_0.set_if_gain(20, 0)
        self.rtlsdr_source_0.set_bb_gain(20, 0)
        self.rtlsdr_source_0.set_antenna('', 0)
        self.rtlsdr_source_0.set_bandwidth(0, 0)

        self.rational_resampler_xxx_1_0 = filter.rational_resampler_fff(
                interpolation=48,
                decimation=51,
                taps=None,
                fractional_bw=None,
        )
        self.rational_resampler_xxx_1 = filter.rational_resampler_fff(
                interpolation=48,
                decimation=51,
                taps=None,
                fractional_bw=None,
        )
        self.rational_resampler_xxx_0_0 = filter.rational_resampler_ccc(
                interpolation=1,
                decimation=4,
                taps=None,
                fractional_bw=None,
        )
        self.rational_resampler_xxx_0 = filter.rational_resampler_ccc(
                interpolation=1,
                decimation=4,
                taps=None,
                fractional_bw=None,
        )
        self.low_pass_filter_0_0 = filter.fir_filter_ccf(1, firdes.low_pass(
        	1, samp_rate/4, 100000, 500000, firdes.WIN_HAMMING, 6.76))
        self.low_pass_filter_0 = filter.fir_filter_ccf(1, firdes.low_pass(
        	1, samp_rate/4, 100000, 500000, firdes.WIN_HAMMING, 6.76))
        self.blocks_multiply_xx_0_0 = blocks.multiply_vcc(1)
        self.blocks_multiply_xx_0 = blocks.multiply_vcc(1)
        self.blocks_file_sink_0_0 = blocks.file_sink(gr.sizeof_float*1, 'D:\\Temp\\1\\audio2.snd', False)
        self.blocks_file_sink_0_0.set_unbuffered(False)
        self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_float*1, 'D:\\Temp\\1\\audio1.snd', False)
        self.blocks_file_sink_0.set_unbuffered(False)
        self.audio_sink_0 = audio.sink(48000, '', True)
        self.analog_wfm_rcv_0_0 = analog.wfm_rcv(
        	quad_rate=samp_rate/4,
        	audio_decimation=10,
        )
        self.analog_wfm_rcv_0 = analog.wfm_rcv(
        	quad_rate=samp_rate/4,
        	audio_decimation=10,
        )
        self.analog_sig_source_x_0_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq_center - freq_2, 1, 0)
        self.analog_sig_source_x_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq_center - freq_1, 1, 0)

        ##################################################
        # Connections
        ##################################################
        self.connect((self.analog_sig_source_x_0, 0), (self.blocks_multiply_xx_0, 1))
        self.connect((self.analog_sig_source_x_0_0, 0), (self.blocks_multiply_xx_0_0, 1))
        self.connect((self.analog_wfm_rcv_0, 0), (self.rational_resampler_xxx_1, 0))
        self.connect((self.analog_wfm_rcv_0_0, 0), (self.rational_resampler_xxx_1_0, 0))
        self.connect((self.blocks_multiply_xx_0, 0), (self.rational_resampler_xxx_0, 0))
        self.connect((self.blocks_multiply_xx_0_0, 0), (self.rational_resampler_xxx_0_0, 0))
        self.connect((self.low_pass_filter_0, 0), (self.analog_wfm_rcv_0, 0))
        self.connect((self.low_pass_filter_0_0, 0), (self.analog_wfm_rcv_0_0, 0))
        self.connect((self.rational_resampler_xxx_0, 0), (self.low_pass_filter_0, 0))
        self.connect((self.rational_resampler_xxx_0_0, 0), (self.low_pass_filter_0_0, 0))
        self.connect((self.rational_resampler_xxx_1, 0), (self.audio_sink_0, 0))
        self.connect((self.rational_resampler_xxx_1, 0), (self.blocks_file_sink_0, 0))
        self.connect((self.rational_resampler_xxx_1_0, 0), (self.audio_sink_0, 1))
        self.connect((self.rational_resampler_xxx_1_0, 0), (self.blocks_file_sink_0_0, 0))
        self.connect((self.rtlsdr_source_0, 0), (self.blocks_multiply_xx_0, 0))
        self.connect((self.rtlsdr_source_0, 0), (self.blocks_multiply_xx_0_0, 0))

    def get_samp_rate(self):
        return self.samp_rate

    def set_samp_rate(self, samp_rate):
        self.samp_rate = samp_rate
        self.rtlsdr_source_0.set_sample_rate(self.samp_rate)
        self.low_pass_filter_0_0.set_taps(firdes.low_pass(1, self.samp_rate/4, 100000, 500000, firdes.WIN_HAMMING, 6.76))
        self.low_pass_filter_0.set_taps(firdes.low_pass(1, self.samp_rate/4, 100000, 500000, firdes.WIN_HAMMING, 6.76))
        self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate)
        self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)

    def get_freq_center(self):
        return self.freq_center

    def set_freq_center(self, freq_center):
        self.freq_center = freq_center
        self.rtlsdr_source_0.set_center_freq(self.freq_center, 0)
        self.analog_sig_source_x_0_0.set_frequency(self.freq_center - self.freq_2)
        self.analog_sig_source_x_0.set_frequency(self.freq_center - self.freq_1)

    def get_freq_2(self):
        return self.freq_2

    def set_freq_2(self, freq_2):
        self.freq_2 = freq_2
        self.analog_sig_source_x_0_0.set_frequency(self.freq_center - self.freq_2)

    def get_freq_1(self):
        return self.freq_1

    def set_freq_1(self, freq_1):
        self.freq_1 = freq_1
        self.analog_sig_source_x_0.set_frequency(self.freq_center - self.freq_1)


def main(top_block_cls=top_block, options=None):

    tb = top_block_cls()
    tb.start()
    try:
        raw_input('Press Enter to quit: ')
    except EOFError:
        pass
    tb.stop()
    tb.wait()


if __name__ == '__main__':
    main()


Удобно и то, что система является кросс-платформенной, и получившаяся программа может работать на Linux, Windows и OSX.

Заключение


Можно сказать, что GNU Radio достаточно сложная система, не в плане рисования блоков конечно, а в плане понимания того, как все это работает. Но какие-то несложные вещи сделать вполне посильно и интересно. GNU Radio также удобно использовать как «виртуальную лабораторию» для обучения — к любой части схемы можно подключить виртуальный осциллограф или спектроанализатор и посмотреть, как выглядит сигнал.

Если не будет каких-то отдельных пожеланий, тему SDR-приема наверно можно закрыть — все основные моменты уже рассмотрены, да и количество просмотров от первой к третьей части падает почти по экспоненте (хотя еще можно написать про передачу, но оно требует более дорогого «железа» для тестов чем RTL SDR). Надеюсь все же, что некоторое понимание того как это работает, у читателей осталось. Ну и всем удачных экспериментов.
Поделиться публикацией

Комментарии 26

    +4
    Спасибо вам за интересный цикл публикаций!
      +1
      Слушать две станции оригинально, но на практике мало полезно

      github.com/szpajder/RTLSDR-Airband

      RTLSDR-Airband receives analog radio voice channels and produces audio streams which can be routed to various outputs, such as online streaming services like LiveATC.net

      Для полноты совпадения функционала вместо записи необходимо реализовать вещание в сеть
        +1
        Да, в GNU Radio есть компонент UDP Sink который можно использовать вместо File Sink. WBFM-декодер также придется поменять на AM, если нужно принимать air band.
          0
          Air band эта программа и так принимает, а с NFM у нее некие проблемы с производительностью.
          Возможно кому-то важно мониторить несколько частот: репитеры, вызывные и т.д.
        +2
        спасибо за интересный материал.
        с интересом жду продолжения про передачу
          0
          Слышал что с помощью этого софта можно создать «поддельную» базовую станцию GSM и перехватывать трафик?
            0
            Есть проект OpenBTS но в подробности не вникал.
            www.youtube.com/watch?v=mRuq7EP--yM

            Правда не уверен что он на базе GNU Radio, хотя может быть.
              +1
              Нужен адаптер USB-VGA на определенном чипсете — он работает как программный передатчик.

              hackaday.com/2018/04/23/spoofing-cell-networks-with-a-usb-to-vga-adapter
                0
                Пассивно послушать можно, но толку от этого немного.
                Для нормального взаимодействия нужен приёмопередатчик, пусть и с небольшой мощностью, типа LimeSDR
                +2
                Если бы в учебных заведениях делали бы лабораторные работы в gnu radio с копеечными приемниками RTL, качество обучения выросло бы в разы. Когда своими глазами спектрограмму по времени увидел, сразу видишь разницу, и это откладывается в мозгу.
                А сейчас по факту, даже после экзаменов 80% потока не понимает разницы между этими всеми AM FM PSK.
                  0
                  В школах до сих пор Borland Pascal под MS-DOS изучают (только вместо оригинального доса DOSBox), какое там GNU Radio…

                  А так да, верно, SDR в разы нагляднее в плане представления сигналов и работы с ними.
                    +1
                    Считаю, что Паскаль идеален для обучения, чтобы не говорило каждое поколение студентов. Сам таким был.
                    Хотя и для школы он может быть сложноват. До сих пор помню школьную олимпиаду по информатике, нам предложили на выбор QBasic или Паскаль, 2 из 30 выбрали Паскаль, и их отвели в отдельный кабинет. Среди оставшихся в коридоре послышался возглас «самоубийцы», и все единодушно согласились. В итоге с Бейсиком сидели за компами, у которых «вся мощь в клавиатуре», если кто помнит такие, а год был 2000.
                    Ещё была занятная история с Паскалем, где-то в 2012 году. Нужно было помочь дочери коллеги с лабораторной работой в универе. Первые три лабы делались в Borland pascal, я ещё тогда удивился почему не в Delphi или Lazarus. Ладно, показал Lazarus, сделали в нем по быстрому, проверили в Borland, и разошлись. Потом в середине семестра снова пришли, им нужно было сделать простенькую игру типа танчиков. Классический game_step() без всяких заморочек, управление с клавиатуре стрелками и вывод на экран uses graph; и вот это вот все. И как то я сразу не нашёл современной альтернативы на тот момент (да и сейчас тоже) для написания примитивной игры с использованием графики. В итоге использовали Lazarus в качестве редактора, и досбокс для проверки.
                      0
                      Сейчас все это замечательно делается в Python — и консольные приложения, и даже графика и UI. Код простой и понятный, и ничуть не сложнее Паскаля, и работает везде (Windows, Linux, OSX). Для совсем маленьких есть Sketch.

                      Понятно что учителям так проще, да и пособия, планы и методички давно написаны. Ладно в школе, сдали и забыли, всем пофиг, но ведь университет должен готовить специалистов с актуальными знаниями, а не с опытом работы в DOSBox.

                      Разумеется, в ДОС было проще — система однозадачная, включил и работай. Сейчас чтобы написать что-то, нужно больше кода. Ну а что делать, жизнь меняется…
                        +1
                        Про досбокс есть нюансы. В универе изучали немного ассемблер и систему прерываний x86. Так вот лабы можно было сделать или на компах с win9x или в dosbox, или на различных SoC, которых не было. Так что, условный дос нельзя выкидывать из курса. Другой вопрос, что на большинстве специальностей аппаратный уровень вообще не затрагивается, и как работает графический режим всем пофиг.
                        Аналогичная ситуация сейчас в телевидении, аналоговый сигнал везде выключают, как работает видеоаппаратура никто не знает. Если выкинуть исторический базис систем из обучения, то общий уровень подготовки падает.
                          0
                          Ассемблер можно и сейчас использовать даже из Си, там и команды MMX и много чего интересного. Прерывания ДОС изучать смысла уже точно нет, те же прерывания на любом микроконтроллере есть, да хоть в Ардуине, которые стоят 5$/шт. Если реально углубляться в железо для соответствующих специальностей, то наверно FPGA нужен, да они сейчас тоже дешевые, если топовые модели не брать.

                          Это вопрос желания и компетенции преподавателей, в первую очередь, хотя тут и системная проблема конечно — если есть утвержденный учебный план, далеко от него не уйдешь, написано ДОС значит будет дос…
                            0
                            Прерывания доса, кстати не изучались, скорее изучалась сама парадигма программирования с прерываниями, ну и то, что они нужны для работы с периферией. Использовать для этого отдельную железку не нужно было. И в данном контексте досбокс всего лишь инструмент.
                            Тогда это было так удивительно, что основной поток программы может быть прерван функцией той же самой программы.
                            Про змейку посмотрю, спасибо. Питон на питоне)
                              0
                              На самом деле, с «железом» оно даже интереснее — чисто на компьютере зачастую слишком абстрактно, а когда что-то мигает или по столу ездит, смотрится совершенно иначе :)

                              Сейчас все это в Ардуине есть, те же прерывания от GPIO, можно кнопку подключить и смотреть как код выполняется.
                              Чисто в Винде прерывания от клавиатуры также работают, только называются уже message а не interrupt, всякие WM_KEYDOWN/WM_KEYUP с кодами клавиш: docs.microsoft.com/en-us/windows/desktop/inputdev/using-keyboard-input

                              Понятно что общий принцип можно объяснить на чем угодно, но и актуальность тоже важна — когда ученики видят, что им дают материал 20-летней давности, то и отношение соответствующее.

                              Смотрю учебник информатики, изданный в 2015м году informika-e.ru/S2/10_SEMAKIN.pdf и вижу там опять Паскаль — о господи, зачем? Они там ни до прерываний не дошли, ни до графики. Зачем грузить учеников никому не нужным материалом? Да на том же Питоне или Си все переписать, и проще и понятнее было бы. Это просто проф.непригодность авторов в вопросах современного программирования (я не исключаю что эти авторы сами никогда программистами и не работали и дальше своего пед.вуза не выходили) или полное нежелание что-то менять — проще старый материал еще раз перепечатать.
                        0
                        И как то я сразу не нашёл современной альтернативы на тот момент (да и сейчас тоже) для написания примитивной игры с использованием графики.

                        Вот для примера «змейка» на Python, проще некуда, и работает без необходимости какого-либо доса.
                        snake.py
                        import pygame
                         
                        # --- Globals ---
                        # Colors
                        BLACK = (0, 0, 0)
                        WHITE = (255, 255, 255)
                         
                        # Set the width and height of each snake segment
                        segment_width = 15
                        segment_height = 15
                        # Margin between each segment
                        segment_margin = 3
                         
                        # Set initial speed
                        x_change = segment_width + segment_margin
                        y_change = 0
                         
                         
                        class Segment(pygame.sprite.Sprite):
                            """ Class to represent one segment of the snake. """
                            # -- Methods
                            # Constructor function
                            def __init__(self, x, y):
                                # Call the parent's constructor
                                super().__init__()
                         
                                # Set height, width
                                self.image = pygame.Surface([segment_width, segment_height])
                                self.image.fill(WHITE)
                         
                                # Make our top-left corner the passed-in location.
                                self.rect = self.image.get_rect()
                                self.rect.x = x
                                self.rect.y = y
                         
                        # Call this function so the Pygame library can initialize itself
                        pygame.init()
                         
                        # Create an 800x600 sized screen
                        screen = pygame.display.set_mode([800, 600])
                         
                        # Set the title of the window
                        pygame.display.set_caption('Snake Example')
                         
                        allspriteslist = pygame.sprite.Group()
                         
                        # Create an initial snake
                        snake_segments = []
                        for i in range(15):
                            x = 250 - (segment_width + segment_margin) * i
                            y = 30
                            segment = Segment(x, y)
                            snake_segments.append(segment)
                            allspriteslist.add(segment)
                         
                         
                        clock = pygame.time.Clock()
                        done = False
                         
                        while not done:
                         
                            for event in pygame.event.get():
                                if event.type == pygame.QUIT:
                                    done = True
                         
                                # Set the speed based on the key pressed
                                # We want the speed to be enough that we move a full
                                # segment, plus the margin.
                                if event.type == pygame.KEYDOWN:
                                    if event.key == pygame.K_LEFT:
                                        x_change = (segment_width + segment_margin) * -1
                                        y_change = 0
                                    if event.key == pygame.K_RIGHT:
                                        x_change = (segment_width + segment_margin)
                                        y_change = 0
                                    if event.key == pygame.K_UP:
                                        x_change = 0
                                        y_change = (segment_height + segment_margin) * -1
                                    if event.key == pygame.K_DOWN:
                                        x_change = 0
                                        y_change = (segment_height + segment_margin)
                         
                            # Get rid of last segment of the snake
                            # .pop() command removes last item in list
                            old_segment = snake_segments.pop()
                            allspriteslist.remove(old_segment)
                         
                            # Figure out where new segment will be
                            x = snake_segments[0].rect.x + x_change
                            y = snake_segments[0].rect.y + y_change
                            segment = Segment(x, y)
                         
                            # Insert new segment into the list
                            snake_segments.insert(0, segment)
                            allspriteslist.add(segment)
                         
                            # -- Draw everything
                            # Clear screen
                            screen.fill(BLACK)
                         
                            allspriteslist.draw(screen)
                         
                            # Flip screen
                            pygame.display.flip()
                         
                            # Pause
                            clock.tick(5)
                         
                        pygame.quit()


                        Другой пример еще проще.
                        rectangle.py
                        import pygame
                         
                        # Define some colors
                        BLACK = (0, 0, 0)
                        WHITE = (255, 255, 255)
                        GREEN = (0, 255, 0)
                        RED = (255, 0, 0)
                         
                        pygame.init()
                         
                        # Set the height and width of the screen
                        size = [700, 500]
                        screen = pygame.display.set_mode(size)
                         
                        pygame.display.set_caption("Bouncing Rectangle")
                         
                        # Loop until the user clicks the close button.
                        done = False
                         
                        # Used to manage how fast the screen updates
                        clock = pygame.time.Clock()
                         
                        # Starting position of the rectangle
                        rect_x = 50
                        rect_y = 50
                         
                        # Speed and direction of rectangle
                        rect_change_x = 2
                        rect_change_y = 2
                         
                        # -------- Main Program Loop -----------
                        while not done:
                            # --- Event Processing
                            for event in pygame.event.get():
                                if event.type == pygame.QUIT:
                                    done = True
                         
                            # --- Logic
                            # Move the rectangle starting point
                            rect_x += rect_change_x
                            rect_y += rect_change_y
                         
                            # Bounce the ball if needed
                            if rect_y > 450 or rect_y < 0:
                                rect_change_y = rect_change_y * -1
                            if rect_x > 650 or rect_x < 0:
                                rect_change_x = rect_change_x * -1
                         
                            # --- Drawing
                            # Set the screen background
                            screen.fill(BLACK)
                         
                            # Draw the rectangle
                            pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])
                            pygame.draw.rect(screen, RED, [rect_x + 10, rect_y + 10, 30, 30])
                         
                            # --- Wrap-up
                            # Limit to 60 frames per second
                            clock.tick(60)
                         
                            # Go ahead and update the screen with what we've drawn.
                            pygame.display.flip()
                         
                        # Close everything down
                        pygame.quit()
                        


                        Для запуска: pip install pygame и python snake.py или python rectangle.py.
                    +1
                    GNU Radio для приема fm? Микроскопом забивать гвозди. gqrx для этого более чем достаточно и еще в кв диапазон можно перевести. Впрочем, если я правильно понял, статья больше для обучения что есть весьма хорошо. К сожалению людей понимающих как это все работает очень мало. Про аналоговые цепи вообще уже забыли а ведь даже примитивный преселектор разгрузит цап весьма не плохо.
                      +2

                      Спасибо за статью.
                      Как вводная в гнурадио очень даже хорошо.
                      Кстати для того чтобы пути тоже были кроссплатформенными можно использовать что-то вроде:


                      import os
                      os.path.join(os.path.curdir, 'file.name')
                        0
                        Спасибо Вам, DmitrySpb79, за интересные статьи по SDR. Жду продолжения в том же духе.
                          0
                          Sink (выход, «слив»)

                          Это слово, скорее всего, переводится как "коллектор". Полупроводники и всё такое.

                            0
                            Нет, тут по смыслу не коллектор все-таки, а именно выход наружу.
                              0

                              Выход наружу называется "out". Line-out, к примеру.

                                0
                                GNU Radio по замыслу авторов работает с потоками данных (data flow), так что термин sink думаю, использовался вполне намеренно: wiki.gnuradio.org/index.php/TutorialsCoreConcepts

                                Тема технических переводов без контекста вообще непростая, помнится, один переводчик перевел в электротехническом тексте то ли «конденсатор», то ли «емкость» как tank, что получилось довольно-таки смешно.
                            0
                            У этой части просмотров будет больше

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое