GNURadio入门教程
3、自定义块
3.1 如何创建Hier 块(自定义封装块)
(参考实验 A05_Create_Hier_block.grc 和 Self_FrequencyShifter.grc)
一个Hier块被用作封装器,将多个GNU无线电块封装为一个块,重新加载块列表,使其出现在块列表中。
下面示例是一个封装的的移频器块,它将信号源与输入信号相乘。
第一步是创建流程图。将以下块拖放到工作区中,并连接:
1、 Signal Source
2、 Multiply
3、 Noise Source
4、 Low Pass Filter
5、 Throttle
6、 QT GUI Frequency Sink
7、 QT GUI Range
更新 QT GUI Range属性:
l Id: frequency
l Default Value: 0
l Start: -samp_rate/2
l Stop: samp_rate/2
更新Low Pass Filter属性:
l 截止频率Cutoff Freq (Hz): samp_rate/4
l 过渡宽度Transition Width (Hz): samp_rate/8
3.1.1 创建 Hier 块
在工作区窗口中框选Signal Source和Multiply块,包括它们之间的连接:
如下图,右键选中部分,然后选择More->Create Hier,一个新的GRC流图将被创建。
新创建的流图如下:
其中,Pad用于指定Hier块上的输入和输出端口。Pad Source 和 Pad Sink是Hier的输入和输出端口。 Parameter块将成为未来定义好的Hier block的参数属性,几个Parameter将有几个属性。
双击Options块并编辑属性:
- Id: Self_FrequencyShifter (.grc和对应.py的文件名)
- Title: Self_FrequencyShifter Block (将成为自定义block的块名)
- Generate Options: Hier Block
然后,Category属性将更改,以显示Hier块所属类别:
Category是可以在GRC右侧的块库中找到块的位置。Hier 块将位于 GRC Hier 块下,而不是 GNU Radio 其它块所在处。
3.1.2 Variables 与 Parameters 块
Variables块不同于 GNU Radio 中的Parameters。Parameters块为Hier块创建一个接口以接受来自外部的值,仅对当前block有效(相当于局部变量),几个Parameter块就是Hier block的几个参数属性。而Variables仅存在于 Hier 块或某流图文件的内部,对整个流图有效(相当于全局变量),如下图所示:
例如,samp_rate Variables只能在 hier 块内访问:
samp_rate Variables需要替换为Parameters(新版本创建Hier块是自动变为Parameters块),以便可以从较大流图中的另一个块更新该数据。删除samp_rate变量并将Parameters块添加到 GRC 工作区中:
编辑Parameters属性:
添加第二个Parameters块,并设置属性:
将频率参数添加到Signal Source 块的频率属性:
最终Hier块的流程图应如下所示,保存:
3.1.3 生成 Hier 块的代码
单击生成流图 以创建Hier块源代码:
将创建一个 .py 文件和 .yml 文件。对于 GNU Radio v3.10,文件目录在您的主目录中:
/home/$USER/.grc_gnuradio/
需要单击Reload按钮,以更新模块的内部列表,然后才能在GRC块库中找到自定义的移频器FrequencyShifter模块:
在块库中将出现一个新的类别GRC Hier block,而自定义的移频器模块就在其中
3.1.4 使用Hier块
现在可以在流程图中使用Hier块。返回起始流程图并删除信号源和乘法块:
将移频器模块添加到工作区,并将其连接到流程图的其余部分:
通过添加samp_rate和frequency参数来编辑移频器块属性:
运行流程图将打开带有 QT QUI Range的 QT GUI 频率接收器窗口:
拖动频率滑块将通过移频器块参数传递该值,从而导致信号的中心频率被修改:
3.1.5 删除Hier块
可以从通过从/home/$USER/.grc_gnuradio目录中删除文件以从磁盘中清除 hier 块。
cd /home/$USER/.grc_gnuradio rm FrequencyShifter.py FrequencyShifter.block.yml
单击“重新加载块”按钮以更新GRC的块库:
3.2 用 Python Block 自定义块原型
(参考实验 A06_Create_First_Python_Block.grc)
使用Embedded Python Block定义原型,它只能在创建它的流程图中使用(而Hier块是添加到了块库中,所以可以在别的流程图中使用)。
Embedded Python块是一种在流程图中快速制作块原型的工具。搜索 Python Block并将其添加到工作区:
双击块以编辑属性。嵌入式 Python 块有两个属性,
1、 Code,一个点击框Open in Editor,点击跳转到指向块的 Python 代码。
2、 Example_Param,块的输入参数。
单击“Open in Editor”以编辑 Python 代码:
将显示一个提示,选择使用哪个文本编辑器来编写 Python 代码。单击使用默认编辑器:
编辑器窗口显示嵌入式 Python 块的 Python 代码:
3.2.1 Python Block的代码构成
代码中有 import、__init__、work 三个部分,解析当前代码如下:
1、import语句
包括 NumPy 和 GNU Radio 库。
2、__init__声明:
① 接受默认参数为 1.0 的 example_param 参数
② 声明块具有 np.complex64 输入和输出端口,即 GNU Radio Complex Float 32 数据类型
③ 存储输入参数中的self.example_param变量
3、work功能:
① 具有输入input_items和输出output_items参数
② 将数学运算应用于input_items并将结果存储在output_items
③ 返回生成的样本数
3.2.2 更改work函数代码以自定义行为
注意:在修改py代码时,混合使用制表符和空格会引发语法错误。
1、 更改参数名称:将example_param重命名为 additionFlag 以使其更具描述性。从编辑器菜单中选择查找和替换,并将默认值从1.0改为True;将块名称改为“Add or Multiply Block”,保存文件:(注意:init函数参数必须有默认值)
返回到 GRC 窗口。Embedded Python 块显示 Additionflag 参数,而不是example_param;且块名变为“Add or Multiply Block”
2、 编辑块输入输出:默认块具有单个输入和单个输出,但是我们需要两个输入。若要添加输入,请将第二个 np.complex64 添加到in_sig列表中:,保存文件。
返回GRC,块变为如下:
3、 修改work函数:自定义其功能。
连接流程图如下,并调整属性:
运行
将“Add or Multiply Block”的AdditionFlag属性改为 False,再次运行。
根据定义,两个复正弦曲线的乘法在两个频率的总和处产生一个正弦曲线。因此,频率为1000和频率为3000的信号源的乘法是频率为4000的复正弦曲线。运行流程图时可以看到这个复正弦曲线:
cc
3.2.3 支持向量输入输出的Python Block
(实验A07_Create_Python_Block_With_Vector.grc)
修改Python 嵌入式Block,使其输入和输出类型为向量格式。
首先,将Signal Source、Throttle、Stream to Vector、Embedded Python Block、Vector to Stream、QT GUI Time Sink、Virtual Sink、Virtual Source、Variable 加入工作区,并修改参数:
- Signal Source
- Output Type: float
- Frequency: 100
- Variable
- Id: vectorLength
- Value: 16
- Stream to Vector, Num Items: vectorLength
- Vector to Stream, Num Items: vectorLength
- Virtual Sink, Stream Id: sinusoid
- Virtual Source, Stream Id: sinusoid
- QT GUI Time Sink
- Autoscale: Yes
- Number of Inputs: 2
Virtual Source与Virtual Sink成对出现时,必须设置相同的Stream ID 值,作用其实相当于一根导线直连。
Embedded Python Block需要修改为:
a) 接受向量输入
b) 产生向量输出
c) 将数据类型更改为浮动
在使用向量时,必须指定向量的长度以及向量中元素的数据类型。我们使用Python中的元组来实现这一点。双击该块以编辑源代码。
运行得出结果:
3.2.4 向量长度不匹配两种报错情况
Embed Python Block有一个不同于其他树外模块的地方。在流图可以运行之前,GRC检查以确保所有连接的数据类型和向量大小匹配。
a) 代码中默认参数与块属性的向量大小不匹配:代码中默认参数的向量大小与流图中前后块的向量大小匹配,但该Python Block块参数属性中向量大小不匹配,GRC检查不出错误,只有运行后才能报Error。
在上述案例中,__init__()函数中vectorSize的默认值
def __init__(self, vectorSize=16):
用于定义输入和输出的向量大小,
in_sig=[(np.float32,vectorSize)], out_sig=[(np.float32,vectorSize)]
向量大小 = 16。GRC 假定输入和输出端口是长度为 16 的向量,但即使通过块属性传入不同的参数,(如通过属性将vectorSize大小设为 128 ),GRC 不会将其捕获为错误,但流图在运行后将崩溃:
Traceback (most recent call last):
File “/home/username/vectorinput.py”, line 250, in <module>
main()
File “/home/username/vectorinput.py”, line 226, in main
tb </span>=<span style="color: #000000;"> top_block_cls()
File “/home/username/vectorinput.py”, line 188, in init
self.connect((self.blocks_stream_to_vector_0, 0), (self.epy_block_0, 0))
File “/usr/lib/python3/dist-packages/gnuradio/gr/hier_block2.py”, line 48, in wrapped
func(self, src, src_port, dst, dst_port)
File “/usr/lib/python3/dist-packages/gnuradio/gr/hier_block2.py”, line 111, in connect
self.primitive_connect(</span>*<span style="color: #000000;">args)
File “/usr/lib/python3/dist-packages/gnuradio/gr/runtime_swig.py”, line 4531, in primitive_connect
</span><span style="color: #0000ff;">return</span> _runtime_swig.top_block_sptr_primitive_connect(self, *<span style="color: #000000;">args)
RuntimeError: itemsize mismatch: stream_to_vector0:0 using 64, Embedded Python Block0:0 using 512
b) 代码中默认参数与块属性的向量大小不匹配:代码中默认参数的向量大小与流图中前后块的向量大小不匹配,但该Python Block块参数属性中向量大小与前后块匹配,GRC 将检出错误。如下示例这种情况,代码中的默认向量长度为 128,但传入的参数为 16,GRC能直接检出错误:
3.2.5 Python Block的 流索引
对于流,可以使用 端口号和样本索引 对输入和输出进行索引。
基于端口号的索引将返回特定端口的所有输入样本。例如
input_items[0] # 返回端口 0 上的所有输入样 input_items[0][3] # 返回端口 0 上的第 4 个输入示例
流的索引一般化为:
input_items[portIndex][sampleIndex] output_items[portIndex][sampleIndex]
下图显示了如何可视化流索引:
3.2.6 Python Block的 向量索引
使用向量时,输入input_items和输出output_items包含额外的维度。
向量增加了一个额外的维度,在下面表示为向量索引。input_items和output_items现在是三维数组:
input_items[portIndex][vectorIndex][sampleIndex] output_items[portIndex][vectorIndex][sampleIndex]
基于 portIndex 端口索引返回某端口所有向量和样本的二维数组,例如:
input_items[portIndex] output_items[portIndex]
基于 portIndex 和 vectorIndex 的索引返回样本的一维数组,例如:
input_items[portIndex][vectorIndex] input_items[portIndex][vectorIndex]
基于 portIndex、vectorIndex 和 sampleIndex 的索引返回单个样本。
input_items[portIndex][vectorIndex][sampleIndex] input_items[portIndex][vectorIndex][sampleIndex]
下面给出了一个可视化的向量索引示例:(此处认为官网写反了,已订正如下)
例子:创建最大保持函数(A07_Create_Python_Block_With_Vector.grc)
在 input_items[0] 中的所有向量上添加一个循环:
for vectorIndex in range(len(input_items[0])):
计算向量的最大值:
maxValue = np.max(input_items[0][vectorIndex])
遍历每个输入样本:
for sampleIndex in range(len(input_items[0][vectorIndex])):
分配每个输出样本最大值:
output_items[0][vectorIndex][sampleIndex] = maxValue
代码应如下所示:
3.2.7 多端口Python Block向量输入输出
(实验A08_Create_Python_Block_With_MultipleVectorPorts.grc)
修改Max Hold Block以添加第二个向量输入和输出端口。
将Noise Source、Stream to Vector、Vector to Stream、Virtual Sink、Virtual Source、QT GUI Time Sink 添加到工作区:
更改以下块属性:
- Noise Source, Output Type: float
- Stream to Vector, Num Items: vectorLength
- Vector to Stream, Num Items: vectorLength
- Virtual Sink, Stream Id: noise
- Virtual Source, Stream Id: noise
- QT GUI Time Sink
- Autoscale: Yes
- Number of Inputs: 2
连接模块:
编辑Max Hold Block的代码。添加两个向量输入和输出:
in_sig=[(np.float32,vectorSize),(np.float32,vectorSize)], out_sig=[(np.float32,vectorSize),(np.float32,vectorSize)]
work() 函数基于两个输入端口输入的数据执行某些运算,更改如下:
for portIndex in range(len(input_items)):</span><span style="color: #0000ff;">for</span> vectorIndex <span style="color: #0000ff;">in</span><span style="color: #000000;"> range(len(input_items[portIndex])): maxValue </span>=<span style="color: #000000;"> np.max(input_items[portIndex][vectorIndex]) </span><span style="color: #0000ff;">for</span> sampleIndex <span style="color: #0000ff;">in</span><span style="color: #000000;"> range(len(input_items[portIndex][vectorIndex])): output_items[portIndex][vectorIndex][sampleIndex] </span>= maxValue</pre>
代码现在应如下所示:
保存代码并连接块:
运行流程图。现在将生成两个最大保持输出,一个用于噪声源,一个用于正弦波:
3.2.8 Python Block的消息传递
可以先看中级教程中消息传递机制。
(实验 A09_Python_Block_Message_Pasing.grc)
消息端口(message port)之间的连线是虚线,而流数据端口(streaming port)之间的连线是实线。
本次实验以流程图演示了如何:
a) 将消息发送和接收端口添加到 Python Block
b) 发送消息
c) 接收和处理消息
d) 根据收到的消息调整 work() 函数中的块行为
创建两个自定义Embedded Python Block是为了:
a) 根据接收到的消息选择或多路复用两个输入信号之一
b) 计算样本数并向多路复用模块发送消息以切换输入
先创建以下流程图
打开Python Block编辑代码:不需要example_param,因此请从 __init__() 函数中删除变量example_param,以及与之相关的语句。
将块的名称更改为多路复用器:
name='Multiplexer',
向块添加第二个输入:
in_sig=[np.complex64, np.complex64],
(1) 为Multiplexer定义消息的输入端口
返回到代码编辑器。需要添加输入消息端口。
创建一个变量来存储消息端口名称:
self.selectPortName = 'selectPort'
添加一行以创建或注册消息输入端口:
self.message_port_register_in(pmt.intern(self.selectPortName)) # 调用了pmt,需要先导包 import pmt
添加一行以将输入端口与消息处理程序连接。
self.set_msg_handler(pmt.intern(self.selectPortName), self.handle_msg)
(2) 给Multiplexer创建消息处理程序
消息处理程序是在收到消息时调用的函数。
必须定义消息处理程序函数。此消息处理程序根据收到的消息在两个输入端口之间切换。收到的消息是真或假的布尔值。在 __init__() 下定义一个新变量,它是输入选择器,
self.selector = True
再定义 handle_msg() 函数:
def handle_msg(self, msg): self.selector = pmt.to_bool(msg)
函数 pmt.to_bool() 获取消息 PMT,然后将数据类型转换为 Python 的布尔数据类型。PMT 用于将消息传递给抽象数据类型。例如,消息可用于发送和接收字符串、浮点数、整数甚至列表。有关 PMT 的更多信息,请访问多态类型 (PMT) 维基页面。
(3) 在该work()中使用消息
多路复用器的外部接口已完成。修改块的 work() 函数以添加多路复用操作。将以下代码添加到 work() 函数中:
如果 self.selector = True,则多路复用器块选择端口 0,如果 self.selector = False,则选择端口 1。self.selector 的默认值在 __init__() 函数中定义。
保存代码 (CTRL+S) 并返回到 GRC。多路复用器块具有消息端口选择端口:
运行流程图以确保一切正确,然后再继续。如简介中所述,不必连接消息端口即可运行流图。由于 self.selector 的默认值为 True,因此多路复用器的 work() 函数将选择端口 0 并将其发送到输出。QT GUI 时间接收器显示噪音:
(4) 定义另一个Python Block:Selector Control进行控制多路复用器
另一个嵌入式 Python 模块用于计算它收到的样本数量,然后将控制消息发送到多路复用器块以切换选择器。
编辑 Python 块的代码。更改 __init__() 函数中的参数example_param:
def __init__(self, Num_Samples_To_Count=128):
更改块的名称:
name='Selector Control',
将Num_Samples_To_Count存储为私有变量:
self.Num_Samples_To_Count = Num_Samples_To_Count
删除 work() 函数中的example_param乘法:
(5) 定义Selector Control的消息输出端口
(6) 在其work()函数中发送消息
不需要为输出端口定义消息处理程序。但是,需要修改 work() 函数以创建发送消息的逻辑。
首先在 __init__() 中创建两个变量:
self.state = True self.counter = 0
添加该行以增加每次调用 work() 的计数样本数:
self.counter = self.counter + len(output_items[0])
添加逻辑以在超过计数器时发送消息:
if (self.counter > self.Num_Samples_To_Count):PMT_msg </span>=<span style="color: #000000;"> pmt.from_bool(self.state) self.message_port_pub(pmt.intern(self.portName), PMT_msg) self.state </span>= <span style="color: #0000ff;">not</span><span style="color: #000000;">(self.state) self.counter </span>= 0</pre>
该逻辑使用 pmt.from_bool() 函数调用将 self.state 的 Python 布尔数据类型转换为 PMT,然后在输出消息端口上发送或发布消息。self.state 变量切换为其相反的值,计数器被重置。
保存代码并返回到 GRC。在选择器控制块的属性中输入 32000 作为Num_Samples_To_Count:
添加Message Debug块并将msg_out端口连接到该块。运行流图显示消息以每秒一次的速率发送,在#t(真)和#f(假)之间交替:
但是,Selector Control的消息输出端口尚未连接到Multiplexer的输入消息端口,因此QT GUI Time Sink仅显示噪声。
Virtual Sink和Virtual Source可用于简洁连接,并使流图更易于理解;将其Stream ID 更改为相同message,如下:
运行流程图。QT GUI Time Sink显示Noise Source和Signal Source之间的交替输出:
3.3 Message Passing示例2
(实验:A10_Message_Passing_demo.grc 将字符串转为字节流输出并存储在文件中 )
拖入嵌入Python块(Embedded Python Block),并将该块重命名为 Message Python Block。作用是将由 QT GUI Message Edit Box 中输入的字符串转换为字节流输出。如下图所示。图中的embedded Python block命名为"Message Passing Demo"。在File Sink中,File为输出字符的保存文件的路径,可根据实际情况自行设定。注意,图中的虚线连接的是消息端口(message ports),实线连接的是数据流端口(streaming ports)。
QT GUI Message Edit Box的val为输入,msg为输出。其中,val:接受PMT消息以更新输入框中的值:首先检查消息的类型是否正确(整型、浮点型、字符串型或复数型),然后将其转换为字符串显示在输入框中。在使用is_pair时,会检查PMT以确保它是一个有效的PMT对。然后将键提取为字符串,并根据数据类型处理值。 msg:将输入框中的数据生成PMT消息。如果数据类型不正确且转换失败,则该块产生日志警告消息,但不输出数据。
Embedded Python Block的代码如下:(使out输出字节流,clear_input为消息输出端口用于清空输入框)import numpy as np
from gnuradio import gr
“”“ 目的: 将 QT GUI Message Edit Box 中输入的字符串转换为字节流输出 ”“”
import pmt
textboxValue = “”
class blk(gr.sync_block): # other base classes are basic_block, decim_block, interp_block
def init(self): # only default arguments here
“”“arguments to this function show up as parameters in GRC”“”
gr.sync_block.init(
self,
name=‘Message Passing Demo’, # 改名字
in_sig=None, # 本例不需要 stream 数据输入端口
out_sig=[np.byte] # 设置 stream 数据输出端口
)
self.message_port_register_in(pmt.intern(</span><span style="color: #800000;">'</span><span style="color: #800000;">msg_in</span><span style="color: #800000;">'</span>)) <span style="color: #008000;">#</span><span style="color: #008000;"> 设置消息输入端口</span>
self.message_port_register_out(pmt.intern(
'
clear_input
'))
#
设置消息输出端口 (注意:消息输出端口和输出端口是两个端口)
self.set_msg_handler(pmt.intern(
'
msg_in
'), self.handle_msg)
#
为msg_in端口的输入数据绑定消息处理函数函数
<span style="color: #0000ff;">def</span><span style="color: #000000;"> handle_msg(self, msg):
</span><span style="color: #0000ff;">global</span> textboxValue <span style="color: #008000;">#</span><span style="color: #008000;"> 声明全局变量</span>
textboxValue =
pmt.symbol_to_string (msg)
</span><span style="color: #0000ff;">def</span><span style="color: #000000;"> work(self, input_items, output_items):
</span><span style="color: #0000ff;">global</span> textboxValue <span style="color: #008000;">#</span><span style="color: #008000;"> 声明全局变量</span>
_len = len(textboxValue)
#
获取字符串的长度
<span style="color: #0000ff;">if</span> (_len ><span style="color: #000000;"> 0):
</span><span style="color: #008000;">#</span><span style="color: #008000;"> 增加换行符</span>
textboxValue +=
"
\n
"
_len </span>+= 1
<span style="color: #008000;">#</span><span style="color: #008000;"> 再输出数组中存储字符串</span>
<span style="color: #0000ff;">for</span> x <span style="color: #0000ff;">in</span><span style="color: #000000;"> range(_len):
output_items[0][x] </span>= ord(textboxValue[x]) <span style="color: #008000;">#</span><span style="color: #008000;"> ord() 函数以一个字符(长度为1的字符串)作为参数,返回字符对应的 ASCII 数值,或者 Unicode 数值</span>
textboxValue =
""
self.message_port_pub(pmt.intern(</span><span style="color: #800000;">'</span><span style="color: #800000;">clear_input</span><span style="color: #800000;">'</span>), pmt.intern(<span style="color: #800000;">''</span>)) <span style="color: #008000;">#</span><span style="color: #008000;"> 消息输出端口,输出消息为空,(清空Message Edit Box)</span>
<span style="color: #0000ff;">return</span><span style="color: #000000;"> (_len)
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">:
</span><span style="color: #0000ff;">return</span> (0)</pre>
运行后,输入字符按回车,如下图,稍后可以在File Sink 中的文件中看到写入的内容,同时编辑框被清空。
观察流图可以发现,图中的Message Passing Demo输入是消息输出是数据流,只要消息的PMTs数据类型与数据流端口的数据类型相匹配,就可以在消息传递和流端口之间切换(在这个demo中,流端口的粉红色表示字节,而在Message Passing Demo中将message消息处理成ASCII码(字节)后输出,两者相匹配)。
3.4 Python Block Tags标签/标记
标签是一种以时间同步方式与数字化射频样本一起传递信息的方法。可以在时间轴上对采样数据进行打标签,使下游块可以方便的获取样本的时间戳和一些附属信息。消息以异步方式传达信息,和采样时钟没有关系,而标签是与特定RF样本相关的。
当下游块需要知道接收器是在哪个样本上调谐到一个新频率,或者用于包含特定样本的时间戳时,标签特别有用。
如下图,三角号为tag,可以记录时间戳等信息,并传递到下一个模块。
3.4.1 添加和获取标记
包括 Complex Float 32, Float 32, Byte和所有其他格式。
(1) 使用以下api添加标记:
self.add_item_tag(outputPortNumber, absoluteIndex, key, value)
outputPortNumber输出端口号确定将标记添加到哪个输出流。absoluteIndex绝对索引是添加标记(Tags)的样本索引。流程图对每个样本进行计数,产生的第一个样本处于绝对样本索引 0。key是包含要存储的变量名的 PMT 类型,value是包含要存储的信息的另一种 PMT 类型。
(2) 读取标记可以使用以下函数完成:
tagTuple = self.get_tags_in_window(inputPortNumber, relativeIndexStart, relativeIndexStop))
在上图窗口中读取标签会根据当前input_items向量中的相对索引读取它们。获取与当前input_items样本对应的所有标记的最简单方法是使用如下函数调用:
tagTuple = self.get_tags_in_window(inputPortNumber, 0, len(input_items[inputPortNumber])))
有关标签的更多信息,请参阅:Stream Tags
3.4.2 示例:阈值检测
(阈值检测实验:A11_Python_Block_Tags.grc,实验创建了两个Python 块,用于检测输入信号何时超过阈值,并为其写入标签,然后再单独的块中读取标签,并用之前的时间戳输出)
(1) 创建测试信号
需要有一个测试信号。拖入以下块:GLFSR Source、Repeat、Multiply Const、Add Const、Single Pole IIR Filter、Throttle、QT GUI Time Sink
更改以下参数:
- GLFSR Source, Degree: 32 (产生-1~1的随机数)
- Repeat, Interpolation: 128
- Multiply Const, Constant: 0.5
- Add Const, Constant: 0.5
- Single Pole IIR Filter, Alpha: 0.05
- QT GUI Time Sink
- Number of Points: 2048
- Autoscale: Yes
- samp_rate Variable, Value: 3200
将所有块更改为float输入和输出。将它们全部连接起来如下:
运行流程图。生成过滤后的 0~1的伪随机序列:
(2) 定义Python Block - Threshold Detector(阈值检测器)
拖入一个Python Block,Virtual Source,Virtual Sink,更改代码:
更改参数
- Threshold Detector Threshold: 0.75
- Report Period: 128
- Virtual Sink, Stream ID: signal
- Virtual Source, Stream ID: signal
- QT GUI Time Sink, name: "Threshold Detector"
连接如下:
(3) 写入标签
编写Threshold Detector的代码:导入 pmt 库;在 __init__() 函数下添加两个新变量 self.timer 和 self.readyForTag:
self.timer = 0 self.readyForTag = True
work() 函数需要修改:创建一个 for 循环以循环访问所有输入样本,当样本值超过阈值,记录并添加标签。仅当 self.readyForTag 状态变量为 True 时,才会写入标记。写入标记后,状态变量 self.readyForTag 将设置为 False。此后只有 self.timer 达到report_period,状态才会重置。
运行流程图。超过阈值进行打标签,显示在 QT GUI Time Sink中:
(4) 定义检测计数器块
创建一个新的嵌入式 Python 块来读取标签,计算自最后一个标签以来的所有样本数,并将该数字生成为输出。双击嵌入式 Python 块并编辑代码。
添加另一个 QT GUI Time Sink并更改属性:
- QT GUI Time Sink
- Name: "Detection Counter"
- Number of Points: 2048
- Autoscale: Yes
(5) 读取标签,以使检测计数器 算出样本数
需要修改检测计数器块才能读取标记。
先导入 pmt 库; 在 __init__() 下添加一个新变量:
self.samplesSinceDetection = 0
修改 work() 函数以读取标签:
# 获取与input_items[0]相关的所有标签 tagTuple = self.get_tags_in_window(0, 0, len(input_items[0])) #tags = self.get_tags_in_window(which_input, rel_start, rel_end)
遍历所有标签,计算key=“detect”标签的相对偏移量并将其存储在列表中:
# 声明一个列表 relativeOffsetList = []
# 遍历所有“detect”标签并存储它们的相对偏移量
for tag in tagTuple:
if (pmt.to_python(tag.key) == ‘detect’):
relativeOffsetList.append( tag.offset - self.nitems_read(0) )
将偏移量从最低到最高排序:
relativeOffsetList.sort()
遍历所有输出样本:
for index in range(len(output_items[0])):
生成一个输出样本,其中包含自上一次detect标签以来到当前样本的样本计数:
output_items[0][index] = self.samplesSinceDetection
如果当前输出样本索引大于或等于当前detect标签的索引,说明该索引处对应下一个detect标签,则重新计数,从列表中删除偏移值并重置样本计数器 self.samplesSinceDetection。否则,将样本计数器增加 1。
if (len(relativeOffsetList) > 0 and index >= relativeOffsetList[0]): relativeOffsetList.pop(0) # 删除偏移值 self.samplesSinceDetection = 0 # 重置输出计数器 else: self.samplesSinceDetection = self.samplesSinceDetection + 1
最后,work()函数如下所示:
保存代码 (CTRL + S)。运行流程图。输出如下所示:
请注意,Detection Counter块输入中的所有标记都会自动传送到其输出。
3.4.3 标签传播
默认情况下,所有输入标签都会传播到所有输出标签。有时不需要标签,需要从某些流中减少或完全删除标签。Tag Gate块可以实现该需求,属性Propagate Tags:No。在Detection Counter块之后连接即可:
运行流程图。标签将在对应 QT GUI Time Sink中删除: