跳至主要內容

程式碼解說

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主要用來調用 C 語言的程式庫。
sys接收命令列參數或檢查平台會用到。
PyQt5Qt 圖形程式框架的 Python 版本。
gnuradioGNU Radio 的主要套件,包括多個子模組,用於訊號處理和 GUI 功能。
sip為 C/C++ 程式庫創建 Python 綁定。
signal系統訊號處理,用來終止應用程式。
argparse解析命令列參數以創建友善的命令列使用界面。
gnuradio.eng_arg用來處理 GNU Radio 應用程式中的參數轉換。
gnuradio.eng_notation協助處理 GNU Radio 參數的工程標記,例如將 1e6=>1M。

類別與建構函數

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 的建構函數,設定系統的標題並啓用異常攔截。

  • Qt.QWidget.__init__(self):這一行調用 Qt.QWidget 的建構函數,使其擁有圖形界面的功能,例如顯示視窗。

設置視窗屬性

  • 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中功能完整。

模塊 (Block) 設定

##################################################
# 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:窗函數類型,用來將訊號做平滑處理,以減少頻譜泄漏 (Spectral leakage)。
    • 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):這個循環實際上只迭代一次,亦即只設置第一條數據線的屬性。
    • 在循環中,檢查 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:相位偏移和初始相位。

通過這些設置,該應用程式配置了一個圖形界面,顯示一個頻率為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 中,數據流通過連接各個處理模塊(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 在軟體定義無線電和訊號處理領域應用廣泛的原因之一。

操作界面

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 應用程式的主要入口。該函數負責初始化應用程式、啓動和顯示 GNU Radio 系統,以及設置適當的訊號處理,以確保程式可以優雅地關閉。以下是詳細解釋:

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():顯示系統。

設置系統訊號處理

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 檔案 (將用不到的部份刪除),留下最關鍵的程式碼,並確保程式還能正確執行。