跳至主要內容

程式码解说

Jia-Yin大约 12 分钟comm

这节将针对 GRC 所产生出来的 Python 档做进一步的详细解说。

导入模块

一开始文件引入了很多模块:


from packaging.version import Version as StrictVersion

from PyQt5 import Qt
from gnuradio import qtgui
from gnuradio.filter import firdes
import sip
from gnuradio import analog
from gnuradio import blocks
from gnuradio import gr
from gnuradio.fft import window
import sys
import signal
from argparse import ArgumentParser
from gnuradio.eng_arg import eng_float, intx
from gnuradio import eng_notation

from gnuradio import qtgui

import ctypes
import sys

下表描述了各个导入模块的作用:

导入模块描述
packaging.version管理软件版本号;检查版本兼容性。
ctypes允许调用 DLL 或共享库中的 C 函数;在此用于与系统库如 X11 交互。
sys访问 Python 解释器变量和系统特定设置;检查平台。
PyQt5Qt 应用程序框架的 Python 绑定;用于创建图形用户界面。
gnuradioGNU Radio 的主模块,包括多个子模块,用于信号处理和 GUI 功能。
sip在 PyQt5 中处理 C/C++ 库的 Python 绑定。
signal提供信号处理实用程序,以管理应用程序终止。
argparse通过解析参数来方便创建用户友好的命令行界面。
gnuradio.eng_arg处理 GNU Radio 应用程序中的工程特定命令行参数。
gnuradio.eng_notation协助处理 GNU Radio 参数的工程标记。

类与构造器

class basic(gr.top_block, Qt.QWidget):

    def __init__(self):
        gr.top_block.__init__(self, "Not titled yet", catch_exceptions=True)
        Qt.QWidget.__init__(self)
        self.setWindowTitle("Not titled yet")
        qtgui.util.check_set_qss()
        try:
            self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))
        except:
            pass

这边宣告 basic 是一个从 gr.top_block 及 Qt.QWidget 继承下来的类。

__init__ 函数是一个特殊的方法,通常称为类的构造器。它在创建类的新实例时自动调用。此函数用于初始化新创建的对象的状态或给对象的属性赋初值。

继承和多重初始化

  • gr.top_block.__init__(self, "Not titled yet", catch_exceptions=True):这一行调用 gr.top_block(GNU Radio流图的基类)的构造器,初始化流图的基础设置,包括设置流图的标题并启用异常捕获。
  • Qt.QWidget.__init__(self):这一行调用 Qt.QWidget 的构造器,使得这个类也是一个Qt小部件。这使得它能够拥有图形用户界面的功能,比如显示窗口。

设置窗口属性

  • self.setWindowTitle("Not titled yet"):设置窗口的标题。
  • qtgui.util.check_set_qss():可能是用来检查并设置Qt样式表(QSS),以美化界面。

再来解释以下这段代码:

self.top_scroll_layout = Qt.QVBoxLayout()
self.setLayout(self.top_scroll_layout)
self.top_scroll = Qt.QScrollArea()
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)
self.top_scroll_layout.addWidget(self.top_scroll)
self.top_scroll.setWidgetResizable(True)
self.top_widget = Qt.QWidget()
self.top_scroll.setWidget(self.top_widget)
self.top_layout = Qt.QVBoxLayout(self.top_widget)
self.top_grid_layout = Qt.QGridLayout()
self.top_layout.addLayout(self.top_grid_layout)

这段代码用于创建窗口的布局,设定窗口中包含滚动区域等元素。


接下来:

self.settings = Qt.QSettings("GNU Radio", "basic")

try:
    if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):
        self.restoreGeometry(self.settings.value("geometry").toByteArray())
    else:
        self.restoreGeometry(self.settings.value("geometry"))
except:
    pass

这段代码使用 Qt.QSettings 来保存和恢复用户界面的某些设定,如窗口位置和大小。在取用设定时,会检查Qt的版本进行相应的处理。

self

在 Python 的类中,self 是一个指向当前对象(类实例)的引用,用于访问类的属性和方法。在 __init__ 方法中使用 self 是为了设置实例的初始状态或者调用其它方法来执行一些操作。

这种使用 __init__ 方法来初始化类实例的方式是面向对象编程的一个基本概念,允许每个新创建的对象具有各自独立的属性和方法。在本例中,self 用来确保每个 basic 类的实例都能正确设置其图形界面和内部状态,使其在GNU Radio中功能完整。

模块设定

##################################################
# Variables
##################################################
self.samp_rate = samp_rate = 32000

##################################################
# Blocks
##################################################
self.qtgui_freq_sink_x_0 = qtgui.freq_sink_c(
    1024, #size
    window.WIN_BLACKMAN_hARRIS, #wintype
    0, #fc
    samp_rate, #bw
    "", #name
    1,
    None # parent
)
self.qtgui_freq_sink_x_0.set_update_time(0.10)
self.qtgui_freq_sink_x_0.set_y_axis(-140, 10)
self.qtgui_freq_sink_x_0.set_y_label('Relative Gain', 'dB')
self.qtgui_freq_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, 0.0, 0, "")
self.qtgui_freq_sink_x_0.enable_autoscale(False)
self.qtgui_freq_sink_x_0.enable_grid(False)
self.qtgui_freq_sink_x_0.set_fft_average(1.0)
self.qtgui_freq_sink_x_0.enable_axis_labels(True)
self.qtgui_freq_sink_x_0.enable_control_panel(False)
self.qtgui_freq_sink_x_0.set_fft_window_normalized(False)

在这段代码中,我们主要看到了两个部分的内容:变量定义和模块(Blocks)的配置。下面是中文解释:

变量定义

self.samp_rate = samp_rate = 32000
  • 这行代码定义了一个名为 samp_rate 的变量,其值被设置为 32000。这通常代表样本率,即每秒处理的样本数。

模块(Blocks)配置

self.qtgui_freq_sink_x_0 = qtgui.freq_sink_c(
    1024, # size
    window.WIN_BLACKMAN_hARRIS, # wintype
    0, # fc
    samp_rate, # bw
    "", # name
    1,
    None # parent
)
  • qtgui_freq_sink_c 是 GNU Radio 中用于创建频率响应图的组件,这里被配置以下特性:
    • 1024:FFT大小,决定频率分辨率。
    • window.WIN_BLACKMAN_hARRIS:窗函数类型,用于在进行频率转换前对信号进行平滑处理,以减少频谱泄漏。
    • 0:中心频率,这里设为0,通常表示处理的是基带信号。
    • samp_rate:带宽,这里使用之前定义的样本率。
    • 空字符串作为名字和None作为父对象,表示这是一个独立的组件,没有特定的UI标签或者嵌入到其它Qt父组件中。

进一步的设置

  • set_update_time(0.10):设置更新时间为0.10秒,即图形界面每0.10秒刷新一次。
  • set_y_axis(-140, 10):设置Y轴范围,从-140 dB到10 dB,这影响显示的信号增益范围。
  • set_y_label('Relative Gain', 'dB'):设置Y轴标签为“相对增益”,单位为分贝(dB)。
  • set_trigger_mode(qtgui.TRIG_MODE_FREE, 0.0, 0, ""):设置触发模式为自由运行,不基于特定的信号触发图形更新。
  • enable_autoscale(False):关闭自动缩放,使Y轴的范围固定。
  • enable_grid(False):不显示网格,使图形界面更简洁。
  • set_fft_average(1.0):设置FFT平均数为1.0,意味着不进行任何平均,直接显示每次计算的结果。
  • enable_axis_labels(True):开启轴标签显示。
  • enable_control_panel(False):不显示控制面板。
  • set_fft_window_normalized(False):设置FFT窗口不进行归一化处理。

这些设置共同定义了如何在图形用户界面中显示和处理信号的频率分析,确保用户可以根据需求调整和观察信号特性。


labels = ['', '', '', '', '', '', '', '', '', '']
widths = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
colors = ["blue", "red", "green", "black", "cyan",
    "magenta", "yellow", "dark red", "dark green", "dark blue"]
alphas = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]

for i in range(1):
    if len(labels[i]) == 0:
        self.qtgui_freq_sink_x_0.set_line_label(i, "Data {0}".format(i))
    else:
        self.qtgui_freq_sink_x_0.set_line_label(i, labels[i])
    self.qtgui_freq_sink_x_0.set_line_width(i, widths[i])
    self.qtgui_freq_sink_x_0.set_line_color(i, colors[i])
    self.qtgui_freq_sink_x_0.set_line_alpha(i, alphas[i])

self._qtgui_freq_sink_x_0_win = sip.wrapinstance(self.qtgui_freq_sink_x_0.qwidget(), Qt.QWidget)
self.top_layout.addWidget(self._qtgui_freq_sink_x_0_win)
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_gr_complex*1, samp_rate,True)
self.analog_sig_source_x_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, 1000, 1, 0, 0)

这段代码的功能主要是设置和调整频率图中显示的多个数据线(即信号的频率响应)的视觉属性,并将频率图组件嵌入到用户界面中。下面是代码的详细解释:

数据线的视觉属性设置

  1. labels, widths, colors, alphas

    • labels:定义数据线的标签名称,这里所有的标签都被设为空字符串。
    • widths:定义每条数据线的宽度,这里每条线的宽度都设为1。
    • colors:定义每条数据线的颜色,包括蓝色、红色、绿色、黑色、青色等。
    • alphas:定义每条数据线的透明度,这里每条线的透明度都设为1.0(完全不透明)。
  2. 设置循环

    • for i in range(1):这个循环实际上只迭代一次(i从0开始,小于1),意味着只设置第一条数据线的属性。
    • 在循环中,检查 labels[i] 的长度,如果为0(即无标签),则设置数据线的标签为 "Data {0}".format(i)(即 "Data 0")。
    • 然后设置该数据线的宽度、颜色和透明度。

组件嵌入和信号源配置

  • self._qtgui_freq_sink_x_0_win = sip.wrapinstance(self.qtgui_freq_sink_x_0.qwidget(), Qt.QWidget):这行代码使用 sip.wrapinstance 方法将频率图的Qt小部件转换为可用于PyQt5的小部件。
  • self.top_layout.addWidget(self._qtgui_freq_sink_x_0_win):将转换后的频率图小部件添加到界面的顶层布局中。

信号处理块的配置

  • self.blocks_throttle_0 = blocks.throttle(gr.sizeof_gr_complex*1, samp_rate, True):创建一个节流块,用于控制数据流的速率,以避免过度占用CPU。
  • self.analog_sig_source_x_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, 1000, 1, 0, 0):创建一个模拟信号源,生成一个余弦波形,其参数为:
    • samp_rate:信号的采样率。
    • analog.GR_COS_WAVE:生成的波形类型,这里是余弦波。
    • 1000:信号的频率。
    • 1:信号的幅度。
    • 0, 0:相位偏移和初始相位。

通过这些设置,该GNU Radio应用程序配置了一个图形界面,显示一个频率为1000 Hz的余弦波的频率响应,同时控制数据的处理速率,以适应系统性能。


##################################################
# Connections
##################################################
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))
self.connect((self.blocks_throttle_0, 0), (self.qtgui_freq_sink_x_0, 0))

这部分代码主要负责在GNU Radio应用程序中建立各个处理块之间的数据连接。在GNU Radio中,数据流通过连接各个处理块(Blocks)来传递信号数据。这里的代码涉及到的连接如下:

连接过程解释

  1. 连接信号源到节流块

    self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))
    
    • 这行代码建立了从模拟信号源 (self.analog_sig_source_x_0) 到节流块 (self.blocks_throttle_0) 的连接。
    • self.analog_sig_source_x_0, 0 表示信号源的输出端口(端口号为0)。
    • self.blocks_throttle_0, 0 表示节流块的输入端口(端口号为0)。
    • 这样的连接确保信号源产生的信号能够传递到节流块中,节流块用于控制数据的流速,以适应处理能力或避免过度使用CPU。
  2. 连接节流块到频率图显示器

    self.connect((self.blocks_throttle_0, 0), (self.qtgui_freq_sink_x_0, 0))
    
    • 这行代码将节流块的输出连接到频率图显示器 (self.qtgui_freq_sink_x_0)。
    • self.blocks_throttle_0, 0 表示节流块的输出端口(端口号为0)。
    • self.qtgui_freq_sink_x_0, 0 表示频率图显示器的输入端口(端口号为0)。
    • 通过这种连接,经过节流块调整速率的信号数据被送到频率图显示器中,进而在图形用户界面上展示信号的频率分布。

这些连接是GNU Radio流图的核心组成部分,它们定义了信号数据在不同处理块间的流向。通过这样的设置,可以实现信号的采集、处理和可视化展示,形成完整的信号处理链路。这是GNU Radio在软件定义无线电和信号处理领域应用广泛的原因之一。

操作接口

def closeEvent(self, event):
        self.settings = Qt.QSettings("GNU Radio", "basic")
        self.settings.setValue("geometry", self.saveGeometry())
        self.stop()
        self.wait()

        event.accept()

def get_samp_rate(self):
    return self.samp_rate

def set_samp_rate(self, samp_rate):
    self.samp_rate = samp_rate
    self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)
    self.blocks_throttle_0.set_sample_rate(self.samp_rate)
    self.qtgui_freq_sink_x_0.set_frequency_range(0, self.samp_rate)

这段代码中定义了几个方法,用于处理窗口关闭事件以及获取和设置采样率。这些方法都是类 basic 的成员函数,与GNU Radio应用的信号处理和用户界面交互密切相关。以下是详细的解释:

closeEvent 方法

def closeEvent(self, event):
    self.settings = Qt.QSettings("GNU Radio", "basic")
    self.settings.setValue("geometry", self.saveGeometry())
    self.stop()
    self.wait()

    event.accept()
  • 功能:这个方法处理Qt窗口的关闭事件。它在窗口即将关闭时被调用。
  • 设置:使用 Qt.QSettings 来保存窗口的几何布局设置,以便下次打开应用时能恢复到相同的位置和大小。
    • "GNU Radio", "basic":指定设置的组织和应用名称,用于存储设置。
    • self.saveGeometry():保存窗口的当前几何布局。
  • 停止操作:调用 self.stop()self.wait() 方法停止和等待流图中所有处理块的运行,确保资源被正确释放。
  • 确认事件:通过 event.accept() 确认关闭事件,允许窗口关闭。

get_samp_rate 方法

def get_samp_rate(self):
    return self.samp_rate
  • 功能:这个方法用于获取当前设置的采样率。
  • 返回:返回存储在 self.samp_rate 属性中的采样率值。

set_samp_rate 方法

def set_samp_rate(self, samp_rate):
    self.samp_rate = samp_rate
    self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)
    self.blocks_throttle_0.set_sample_rate(self.samp_rate)
    self.qtgui_freq_sink_x_0.set_frequency_range(0, self.samp_rate)
  • 功能:这个方法用于设置新的采样率,并更新相关的处理块配置。
  • 更新采样率
    • self.samp_rate = samp_rate:更新类实例的采样率属性。
    • self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate):设置信号源块的采样频率。
    • self.blocks_throttle_0.set_sample_rate(self.samp_rate):设置节流块的采样率,以控制数据流速率。
    • self.qtgui_freq_sink_x_0.set_frequency_range(0, self.samp_rate):设置频率图显示器的频率范围。

这些方法为用户提供了灵活的操作接口,使他们能够方便地控制和调整GNU Radio应用的关键参数,并确保应用程序关闭时资源得到正确处理。

主程序

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

    if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):
        style = gr.prefs().get_string('qtgui', 'style', 'raster')
        Qt.QApplication.setGraphicsSystem(style)
    qapp = Qt.QApplication(sys.argv)

    tb = top_block_cls()

    tb.start()

    tb.show()

    def sig_handler(sig=None, frame=None):
        tb.stop()
        tb.wait()

        Qt.QApplication.quit()

    signal.signal(signal.SIGINT, sig_handler)
    signal.signal(signal.SIGTERM, sig_handler)

    timer = Qt.QTimer()
    timer.start(500)
    timer.timeout.connect(lambda: None)

    qapp.exec_()

if __name__ == '__main__':
    main()

这段代码定义了一个名为 main 的函数,它是 GNU Radio 和 PyQt5 应用程序的主要入口点。该函数负责初始化应用程序、启动和显示流图,以及设置适当的信号处理以确保程序可以优雅地关闭。以下是详细解释:

main 函数

def main(top_block_cls=basic, options=None):
  • 参数
    • top_block_cls=basic:默认参数,指定顶层流图的类。在这里,默认为 basic 类。
    • options=None:可选参数,用于传递额外配置选项。

版本检查和图形系统设置

if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):
    style = gr.prefs().get_string('qtgui', 'style', 'raster')
    Qt.QApplication.setGraphicsSystem(style)
  • 这段代码检查 Qt 的版本是否在 4.5.0 和 5.0.0 之间。如果是,它从 GNU Radio 的首选项中获取图形界面的样式设置,并将其应用到 Qt 应用程序。

初始化 Qt 应用程序

qapp = Qt.QApplication(sys.argv)
  • 创建一个 Qt 应用程序实例,sys.argv 用于传递命令行参数到应用程序。

创建并启动流图

tb = top_block_cls()
tb.start()
tb.show()
  • tb = top_block_cls():创建顶层流图的实例。
  • tb.start():开始执行流图,处理信号。
  • tb.show():显示流图的 GUI。

设置信号处理器

def sig_handler(sig=None, frame=None):
    tb.stop()
    tb.wait()
    Qt.QApplication.quit()

signal.signal(signal.SIGINT, sig_handler)
signal.signal(signal.SIGTERM, sig_handler)
  • 定义了一个信号处理函数 sig_handler,它在接收到中断信号(如 Ctrl+C)或终止信号时被调用。
  • tb.stop()tb.wait() 分别用于停止流图和等待其完全停止。
  • Qt.QApplication.quit() 用于退出 Qt 应用程序。

设置定时器

timer = Qt.QTimer()
timer.start(500)
timer.timeout.connect(lambda: None)
  • 创建并启动一个 Qt 定时器,每 500 毫秒触发一次。
  • timer.timeout.connect(lambda: None) 这行代码实际上在定时器超时时不执行任何操作,这可能是为了定期触发事件循环,确保应用响应用户界面操作。

执行 Qt 应用程序

qapp.exec_()
  • 启动 Qt 应用程序的事件循环,等待用户操作或程序结束。

程序入口点

if __name__ == '__main__':
    main()
  • 如果直接运行这个脚本,main 函数将被调用,启动整个 GNU Radio 和 PyQt5 应用程序。

练习 2

试著简化 GRC 所产生 的 Python 档案 (将用不到的部份删除),留下最关键的程式码,并且程式还能正确执行。