分享

benchmark例程详解(不断更新中) |GNU Radio 入门

 吾乃阿尔法 2013-10-26
 Benchmar例程详解
错误更正
一 3) 原文:Benchmark_tx 和 benchmark_rx 可以分别在两台电脑加两台USRP运行,也可以用一台电脑开两个终端同时运行,此时需要处理能力较强的电脑,建议可以配置较低的bit速率和较小的SAMPLES_PER_SYMBOL值,从而能够减小计算机的运算量。
改正:经过实际测试,发现目前的benchmark版本还无法实现一台电脑加一套usrp板子的收发程序。然而当我自己写了简单的测试程序,采用一台电脑开和一套USRP设备,可以成功的发送和接收正弦波形,所以我估计只要适当修改程序,应该还是可以实现benchmark_rx 和benchmark_tx 单机实现的。

更新说明
4月6日增加内容 二.2  2) receive_path.py 3)usrp_options.py 4)self.packet_receiver
4月7日增加内容  继续完成二.2  4)self.packet_receiver
4月10日增加内容 二.2  5)demodulators
4月20日增加内容 三、benchmark_tx详解  1、模块连接关系图  2、代码详解 1)benchmark_tx.py 程序   2)send_pkt     3) gr.message_source
还在出差中,时间比较紧张,上网也不方便,所以更新的慢了些。
5月1日增加内容 三 1、4)transmit_path.py

从论坛里面大家关心的问题来看,benchmark的关注度一直比较高,然而确实也缺乏系统的介绍,从今天开始,我准备逐渐的将benchmark 例程序给出详细的注释,以便初学者能够迅速理解。
几点说明
1)版本问题。大家所安装gnuradio版本都不尽相同,另外由于我的一些应用都是基于gnuradio3.2.1开发的,所以一直没有安装新的gnuradio版本,我也简单的看了一下新版本的benchmark相关例程,除了封装模块有些变化,核心模块没有本质区别,所以我准备介绍gnuradio3.2.1中gnuradio-examples/python/digital中的例程,不同的地方我也会给出适当的解释,为了明确所解读的源码,我将3.2.1的digtial 文件上传上来,便于大家比较分析 digital.zip (67 K) 下载次数:127
2)阐述方法,如果要每一条程序都解释,首先时间花费很大,而且也不利于大家理清思路,所以我首先给出模块的连接关系,然后以模块为牵引,一步一步的介绍python源文件,有些细节地方,如果我漏掉了大家有需要,大家可以提出来,不能保证每一点我都能解释,但是尽量去查阅资料。
3)计划安排,由于空余时间也比较少,等到写完以后,再发出来,估计时间会很长,所以我准备还是写一部分就发布一部分,可能会有些小错误,希望大家能够及时指出来。

一、如何运行benchmark例程,以及相关参数的介绍
Benchmark中可以直接运行的程序有:
benchmark_rx.py 接收程序
benchmark_tx.py 发送程序
benchmark_looppback.py 自环程序,无需usrp硬件
1)无需usrp硬件的benchmark_loopback.py
./benchmark_loopback.py
即可在屏幕看到输出结果,配置的参数有调制方式,信噪比,等等。由于没有USRP硬件,所以很多参数不需要配置,本质上就是一个仿真程序。
2)接收子程序 benchmark_rx.py
首先运行
./benchmar_rx.py –h 可以看到需要配置的参数有
Options:
  -h, --help            show this help message and exit
  --modulation=MODULATION
                        Select modulation from: dbpsk, d8psk, dqpsk, gmsk
                        [default=gmsk]
  -f FREQ, --freq=FREQ  set Tx and/or Rx frequency to FREQ [default=none]
  -r BITRATE, --bitrate=BITRATE
                        specify bitrate.  samples-per-symbol and interp/decim
                        will be derived.
  -R RX_SUBDEV_SPEC, --rx-subdev-spec=RX_SUBDEV_SPEC
                        select USRP Rx side A or B
  --rx-gain=GAIN        set receiver gain in dB [default=midpoint].  See also
                        --show-rx-gain-range
  --show-rx-gain-range  print min and max Rx gain available on selected
                        daughterboard
  -d DECIM, --decim=DECIM
                        set fpga decimation rate to DECIM [default=none]
  -u USRPX, --usrpx=USRPX
                        specify which usrp model: 1 for USRP, 2 for USRP2
                        [default=auto]
  -w WHICH, --which=WHICH
                        select USRP board [default=0]
  -e INTERFACE, --interface=INTERFACE
                        Use USRP2 at specified Ethernet interface
                        [default=eth0]
  -m MAC_ADDR, --mac-addr=MAC_ADDR
                        Use USRP2 at specified MAC address [default=None]
  -v, --verbose        

  Expert:
    -B FUSB_BLOCK_SIZE, --fusb-block-size=FUSB_BLOCK_SIZE
                        specify fast usb block size [default=0]
    -N FUSB_NBLOCKS, --fusb-nblocks=FUSB_NBLOCKS
                        specify number of fast usb blocks [default=0]
    -S SAMPLES_PER_SYMBOL, --samples-per-symbol=SAMPLES_PER_SYMBOL
                        set samples/symbol [default=none]
    --rx-freq=FREQ      set Rx frequency to FREQ [default=none]
    --log               Log all parts of flow graph to files (CAUTION: lots of
                        data)
    --log-rx-power      Log receive signal power to file (CAUTION: lots of
                        data)
    --excess-bw=EXCESS_BW
                        set RRC excess bandwith factor [default=0.35] (PSK)
    --no-gray-code      disable gray coding on modulated bits (PSK)
    --costas-alpha=COSTAS_ALPHA
                        set Costas loop alpha value [default=0.15] (PSK)
    --gain-mu=GAIN_MU   M&M clock recovery gain mu [default=none] (GMSK/PSK)
    --mu=MU             M&M clock recovery mu [default=0.5] (GMSK/PSK)
    --omega-relative-limit=OMEGA_RELATIVE_LIMIT
                        M&M clock recovery omega relative limit
                        [default=0.005] (GMSK/PSK)
    --freq-error=FREQ_ERROR
                        M&M clock recovery frequency error [default=0.0]
                        (GMSK)
这些参数的意思也非常直观,并不需要每一项都进行配置,因为有些参数已经给出了默认值,如default=xx表示所配置默认值。但一般至少需要制定接收频率,子板和比特速率,所以我们可以输入
./benchmark –f 900e6 –R A –r 400e3
表示接收频率为900MHz,选择A 板进行接收 400kbit/s的数据信息,此时默认的调制方式为gmsk。需要注意的是抽取率-d DECIM ,每符号的样值数-S SAMPLES_PER_SYMBOL,以及比特速率-r BITRATE,FPGA的时钟频率为MASTER_CLOCK_RATE 和 调制方式这五者之间的关系。
假设对应调制方式每个符号对应的比特数为BITS_PER_SYMBOL,那么

BITRATE=(MASTER_CLOCK_RATE / DECIM / SAMPLES_PER_SYMBOL) *BITS_PER_SYMBOL

而pick_bitrate.py就是为了计算满足关系式的比特速率,如果给出的参数不满足这一关系式,pick_bitrate.py 会自动的调整以最接近所配置的BITRATE值。

3) 发送子程序./benchmark_tx.py
和benchmark_rx.py 几乎相同,只是指定子板采用 –T 表示发送子板
如 ./benchmark_tx.py –f 900e6 –T B –r 400e6
Benchmark_tx 和 benchmark_rx 目前只实现了两台电脑加两台USRP设备发送接收,并且发送USRP设备和接收设备需保持3米的距离。
运行收发程序时,首先将运行benchmark_rx 然后云心benchmark_tx 成功后发端每发送一个包会在屏幕上输出一个”.” 而收端则会显示数据包接收的情况,当前的数据包序号,正确接收的帧数等等。


二、benchmark_rx.py 详解
1、 模块连接关系图解
图1 gnuradio-3.2.1 中benchmark_rx.py 各模块的连接关系
图1

其中顶层模块 my_top_clock 在benchmark_rx.py 中定义
接收模块     receive_path 在receive_path.py 中定义
注意gnuradio-3.3.0以上版本模块的封装有所变化,如图2所示
图2 gnuradio-3.3.0 以上版本
图2

从图中可以看出,3.3.0后的版本,增加了一个usrp_recieve_path的模块,将原先的receive_path中的usrp_source 模块取出,然后usrp_source 加上 receive_path 就是usrp_recieve_path 模块。
虽然封转方式有所变化,但是处理流程都是一致的
Usrp_source 采集到所需的信号,经过滤波以后,一路进行解调packet_reciver,一路用于探测probe, 从当前的程序来看,probe并没有被benchmark_rx所利用,注释掉对应的connect 并不影响数据的解调。
2、 代码详解
1)首先还是从benchmark_rx.py 开始,因为它包含了顶层模块的定义和使用。
先找到程序的入口,就好比我们c 语言的程序都是从main 函数开始的,python 也一样,此时我们的main 函数是
if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        pass
这一段应该很好看明白,python程序首先运行main( ) 函数,除非键盘操作终止
所以我们找到main ( ) 函数的定义
# /////////////////////////////////////////////////////////////////////////////
#                                   main
# /////////////////////////////////////////////////////////////////////////////

global n_rcvd, n_right

def main():
    global n_rcvd, n_right
首先我们先关注整个main的执行流程,所以其他无关语句,我们暂时不关心
def main():
……
    def rx_callback(ok, payload):
        global n_rcvd, n_right
        (pktno,) = struct.unpack('!H', payload[0:2])
        n_rcvd += 1
        if ok:
            n_right += 1
        print "ok = %5s  pktno = %4d  n_rcvd = %4d  n_right = %4d" % (
            ok, pktno, n_rcvd, n_right)
……
    # build the graph
    tb = my_top_block(demods[options.modulation], rx_callback, options)
……
    tb.start()        # start flow graph
tb.wait()         # wait for it to finish

去掉旁支末节保留最关键的部分,main() 函数的主体部分如图所示
首先定义一个rx_callback 函数,其作用是显示接收数据包的信息,我们可以通过修改该函数达到我们需要的接收效果,(然而在benchmark_rx.py中并没有该函数的调用,但是该函数的入口地址作为参数被传递到了blk2.demods_pkt模块中,而最终会被触发,这一点我们后面会介绍)
接下来是 生成一个my_top_block的顶层模块,该模块的定义我们待会再解释,从main 函数中可以看到我们得到了一个tb模块,它属于class my_top_block, 然后我们启动它tb.start 并使其一直运行下去 tb.wait() 可以理解为一个死循环,否则程序一运行就结束了。

此时我们最关心的是my_top_block的具体定义是什么,同样可以在benchmark_rx.py 中找到
class my_top_block(gr.top_block):
    def __init__(self, demodulator, rx_callback, options):
        gr.top_block.__init__(self)
        self.rxpath = receive_path(demodulator, rx_callback, options)
        self.connect(self.rxpath)
非常简单,它只包含一个模块,self.rxpath = receive_path(demodulator, rx_callback, options),如果只从benchmark_rx现阶段的功能来看,这层包装是可有可无,可以在main 函数里面直接改写成 tb=receive_path(demods[options.modulation], rx_callback, options);所以需要了解的是receive_path 模块,而它的定义可以从receive_path.py 中找到,我们在下一部分具体介绍。
下面先先简单介绍一下python 程序的基础知识
a, python程序的框架
一个最简单可以运行的python程序,必须要有入口函数,和c语言程序一样一定要有main 函数,python程序的main函数是
if __name__ == '__main__':
    执行语句
Python 程序首先找到if __name__ == '__main__':
然后执行其所属语句,python中不像 c 语言采用 “{ }” 来划定其作用域,通过的是缩进方式来表示。
需要注意的是在benchmark_rx.py 中我们定义了main(),但是这个main 函数只是一个普通的函数叫了一个不普通的名字而已,我们改成其它任何名字都不影响其运行。
举一个例子
def show( ):
    print “hello world”
if __name__ == '__main__':
    show( )
这就是一个hello worl!的程序

python同样也可以采用“include”的方式,只不过写法是
Import 或者 from ……import,
Python 同样可以采用类进行封装,而且一般类都和模块直接对应,所以更好理解,比如可以理解为一个类的实体就是一个模块
如这里的top_block的定义
class my_top_block(gr.top_block):
    def __init__(self, demodulator, rx_callback, options):
        gr.top_block.__init__(self)
        self.rxpath = receive_path(demodulator, rx_callback, options)
        self.connect(self.rxpath)
class 表示类的声明,gr.tob_block是父类,这是一个比较原始的类,所以定义比较简单,只定义了对应的初始化函数,好比c语言的构建函数,首先调用父类的构建函数,然后创建自身的模块,连接关系等等。还可以为my_top_block定义其它的函数,这个根据需求来定。

b. option 的添加
程序可以在运行的时候键入参数,这些参数是怎样添加的呢,可以和c语言里面用argv argc的方式,然而采用OptionParser则更加简单方便,如benchmark 中
    parser = OptionParser (option_class=eng_option, conflict_handler="resolve")
    parser.add_option("-m", "--modulation", type="choice", choices=demods.keys(),
                      default='gmsk',
                      help="Select modulation from: %s [default=%%default]"
                            % (', '.join(demods.keys()),))
首先需要实体化一个OptionParser这里是parser,然后一一添加option即可,通过add_option来实现,可以定义参数的缩写,参数的名称,类型,帮助信息,默认值为多少
2)receive_path.py
需要注意的是,这里的receive_path.py 完成是gnuradio-3.3.0以后版本的usrp_receive_path.py的功能
从receive_path.py开始,我们就开始接触到gr.hier_block2所衍生的类,gr.hier_block2类是各种组合信号处理模块的基类,由它可以衍生出各种组合的信号处理模块(由于大部分信号处理模块需要多种模块组合使用,所以gr.hier_block2是gnuradio中最关键的类之一),class receive_path就是其子类之一,声明gr.hier_block2子类的基本步骤为(以recevie_path为例)

(1)首先确定类名 class receive_path(gr.hier_block2):
(2)然后确定该类的构建函数所带的参数
def __init__(self, demod_class, rx_callback, options):
(3)由于该类对应的都是信号处理模块,所以接下来需要定义该类对应模块的输入输出端口
    gr.hier_block2.__init__(self, "receive_path",
                                gr.io_signature(0, 0, 0), # Input signature
                                gr.io_signature(0, 0, 0)) # Output signature
gr.io.signature(a,b,size) 定义端口的宽度,a,b表示二维输出向量的维度,而size表示每一个输出值的数据位数,所以其最终的数据宽度是a*b*size
(4)添加组合模块所需的各种模块,然后定义其连接关系,需要注意的是第一级的输入端口位宽必须和组合模块声明的端口位宽一致,最后一级输出的模块的端口位宽必须和定义的输出端口位宽一致,在这里由于没有输入输出,所以定义的位宽都是“0”
(5) 最后是定义该组合模块对应的一些函数,以便实现对该模块灵活的控制,和查看该模块的一些参数
总结来看,一个gr.hier_block2衍生类的定义可以简单的归纳为
class  module_name(gr.hier_block2):
    def __init__(self,p1,p2,p3):
    gr.hier_block2.__init__(self,”module_name”,
gr.io_signature(Ina, Inb, Insize),
                                gr.io_signature(Outa, Outb, Outsize))
    submodule1=xxxxx
    submodule2=xxxxx
    submodule3=xxxxx
    self.connect(submdoule1,submodule2,submoddule3)
    def cope1(self,p4)
         xxxxxxx
    def get1(self,p5)
        xxxx
        return xxxx
回到receive_path.py 可以看到该模块里面有
self.u = usrp_options.create_usrp_source(options)
self.packet_receiver = blks2.demod_pkts(self._demod_class(**demod_kwargs),
                             access_code=None,
                             callback=self._rx_callback,
                             threshold=-1)
chan_coeffs = gr.firdes.low_pass (1.0,                  # gain
                               sw_decim * self._samples_per_symbol, # sampling rate
                               1.0,                  # midpoint of trans. band
                                0.5,                  # width of trans. band
                                 gr.firdes.WIN_HANN)   # filter type

self.u是对应于usrp_source模块,用于接收usrp硬件设备的采样值
chan_coeffs对应于信道的接收滤波器
self.packet_receiver对应于解调和数据包的接收是一个关键模块
其实还有一个probe,虽然被连接了,但是在benchmark现有的程序中并没有被使用,这里暂时不介绍,接下来首先介绍self.u,所以我们需要进入到usrp_options.py 中学习
3)usrp_options.py
该文件主要是定义了一些与usrp相关的函数,如usrp模块的构建函数
def create_usrp_source(options):
    u = generic_usrp.generic_usrp_source_c(
        usrpx=options.usrpx,
        which=options.which,
        subdev_spec=options.rx_subdev_spec,
        interface=options.interface,
        mac_addr=options.mac_addr,
        fusb_block_size=options.fusb_block_size,
        fusb_nblocks=options.fusb_nblocks,
    )
    if options.show_rx_gain_range:
        print "Rx Gain Range: minimum = %g, maximum = %g, step size = %g"%tuple(u.gain_range())
    return u
这里先不详细介绍,只要理解,这个函数构建了一个按照指定参数设定的usrp接收模块,由于滤波器是一个单一的模块,物理意义和参数也比较好理解,这里也不详细介绍了,如果有疑问,可以提出来。
4) self.packet_receiver
它是核心的接收模块,但是blks2.demod_pkts在digtial 文件夹里面找不到定义文件,在哪里呢?有一个好的方法就是搜索工具,包含关键字的搜索方法非常有用,windows和linux文件搜索都有对应的功能,通过搜索很快就能找到在 gnuradio-core\src\python\gnuradio\blks2impl\pkt.py中
class demod_pkts(gr.hier_block2):
    """
    Wrap an arbitrary digital demodulator in our packet handling framework.

    The input is complex baseband.  When packets are demodulated, they are passed to the
    app via the callback.
    """

    def __init__(self, demodulator, access_code=None, callback=None, threshold=-1):
        """
    Hierarchical block for demodulating and deframing packets.

    The input is the complex modulated signal at baseband.
        Demodulated packets are sent to the handler.

        @param demodulator: instance of demodulator class (gr_block or hier_block2)
        @type demodulator: complex baseband in
        @param access_code: AKA sync vector
        @type access_code: string of 1's and 0's
        @param callback:  function of two args: ok, payload
        @type callback: ok: bool; payload: string
        @param threshold: detect access_code with up to threshold bits wrong (-1 -> use default)
        @type threshold: int
    """

    gr.hier_block2.__init__(self, "demod_pkts",
                gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
                gr.io_signature(0, 0, 0))                    # Output signature

        self._demodulator = demodulator
        if access_code is None:
            access_code = packet_utils.default_access_code
        if not packet_utils.is_1_0_string(access_code):
            raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,)
        self._access_code = access_code

        if threshold == -1:
            threshold = 12              # FIXME raise exception

        self._rcvd_pktq = gr.msg_queue()          # holds packets from the PHY
        self.correlator = gr.correlate_access_code_bb(access_code, threshold)

        self.framer_sink = gr.framer_sink_1(self._rcvd_pktq)
        self.connect(self, self._demodulator, self.correlator, self.framer_sink)
        
        self._watcher = _queue_watcher_thread(self._rcvd_pktq, callback)
demod_pkts看上去比较复杂,没有关系,我们一步一步来分析
demod_pkts包括self._demodulator = demodulator 是解调模块,如何按照设置好的解调方式去调用解调模块,一会儿分析,此处先明白它就是负责信号解调,将采样值变成0、1比特
self.correlator = gr.correlate_access_code_bb(access_code, threshold) 是一个相关器,虽然我还没有具体去看其过程,但是很明显,是实现相关捕获的,它有两个参数,一个是门限,一个就是用于同步的access_code = packet_utils.default_access_code其值可以在packet_utils.py中找到
default_access_code = conv_packed_binary_string_to_1_0_string('\xAC\xDD\xA4\xE2\xF2\x8C\x20\xFC')
就是1010 1100 1101 1101 1010 0100 1110 0010 1111 0010 1000 1100 0010 0000 1111 1100
应该是64位的伪码序列
self.framer_sink = gr.framer_sink_1(self._rcvd_pktq)将接收的数据封装成帧。而
self._rcvd_pktq = gr.msg_queue() 就是一个数据链表,用于存放接收到的数据包
最后将各模块按顺序连接,即可实现信号的解调,帧同步,和封帧
self.connect(self, self._demodulator, self.correlator, self.framer_sink)
信号的流程为:基带样值 进入到解调模块,得到0,1比特流,流入相关器,当出现相关峰值,则将后续的比特流传入framer_sink中 framer_sink 将一个个数据包添加到self._rcvd_pktq = gr.msg_queue()链表中去。
接下来还有一个关键模块,虽然没有和输入输出端口相连接,但是却能够调用callback函数,它就是
self._watcher = _queue_watcher_thread(self._rcvd_pktq, callback)

这个模块一直在查看self._rcvd_pktq链表是否有节点,如果为空,那么不做任何处理,如果链表不为空,那么就调用callback函数,同时将当前节点作为参数交予callback函数,然后删除该节点,(如果要看这些过程的具体源代码,按照我开始介绍的搜索方式,可以找到相关的py 或者c++代码)。

而callback 函数有具体指的是哪一个函数呢?我们一步一步往回看,demok_pkt的callback是由receive_path 中callback 赋值的,receive_path的 callback 是my_top_block赋值的,而my_top_block的是在main 函数定义中定义的rx_callback,所以每收到一个数据包,都会触发一次rx_callback 函数。
5) demodulator
对于demod_pkts 中的demodulator 具体是调用了哪一个模块,还是需要回到benchmark_rx.py 中去
demods = modulation_utils.type_1_demods()
然后根据options.modulation 来选择options制定的调制方式
tb = my_top_block(demods[options.modulation], rx_callback, options)
首先我们可以找到modulation_utils.py 文件,其中定义了type_1_demods
_type_1_demodulators = {}

def type_1_demods():
    return _type_1_demodulators

def add_type_1_demod(name, demod_class):
_type_1_demodulators[name] = demod_class
而我们发现就是一个{},但是我们直接在demods = modulation_utils.type_1_demods()后面增加
print demods
直接输出,我们发现可以看到是一个会输出一组字符串
{ ‘dpsk’:’class ‘gnuradio.blk2impl.dpsk.dpsk_demod’, ‘d8psk’:’class ‘gnuradio. blk2impl.d8psk.d8psk_demod’…………}
这又是为什么呢?
这是因为在receive_path.py中有这么一句
from gnuradio import gr, gru, blks2
而blks2中的__init__.py 中可以看到
for p in __path__:
    filenames = glob.glob (os.path.join (p, "..", "blks2impl", "*.py"))
    for f in filenames:
        f = os.path.basename(f).lower()
        f = f[:-3]
        if f == '__init__':
            continue
        # print f
        exec "from gnuradio.blks2impl.%s import *" % (f,)
而这一段可以理解为
from blks2impl import *.*
就是引用blks2impl中的所有模块
以其中一个模块dbpsk.py为例,在源代码的最后有这么两句
modulation_utils.add_type_1_mod('dbpsk', dbpsk_mod)
modulation_utils.add_type_1_demod('dbpsk', dbpsk_demod)
在modulation_utils.py 中 dd_type_1_demod的定义为
def add_type_1_demod(name, demod_class):
_type_1_demodulators[name] = demod_class
因此完成了dbpsk的注册,将‘dbpsk’与dbpsk_mod 和 dbpsk_demod 建立了映射关系
如果options.modulation=‘dbpsk’那么
demods[options.modulation]= class ‘gnuradio.blk2impl.dbpsk.dbpsk_demod’
因此就实现了配置options.modulation 而自动的加载dbpsk_demod模块
同理可以通过mods = modulation_utils.type_1_mods()
mods[options.modulation]
和配置options中的modulation 自动的加载dbpsk_mod模块
Type_1_mods 或 type_1_demods 将比特流输入复数样值输出的这样的调制模块 和 复数样值输入比特流输出的解调模块归为一类。
从程序的结构猜测估计以后还会扩展出type_2_mods等等,这样只需要设计新的注册函数即可,如 modulation_utils.add_type_2_mod
三、benchmark_tx详解
1、模块连接关系图
同样个给出两个版本的模块连接关系图,图3是gnuradio3.2.1中benchmark_tx的模块连接图,而图4是gnuradio3.3.0版本的中的benchmark_tx模块连接图
图3

图4
  
2、代码详解
1)benchmark_tx.py 程序
仍然是3.2.1版本的程序,几乎和benchmark_rx.py的结构几乎类似,首先定义了一个顶层模块
class my_top_block(gr.top_block):
    def __init__(self, modulator, options):
        gr.top_block.__init__(self)
        self.txpath = transmit_path(modulator, options)
        self.connect(self.txpath)
然后在main 函数里面实例化一个tb模块,
tb = my_top_block(mods[options.modulation], options)
tb就是class my_top_block的一个实例。
而和benchmark_tx和benchmark_rx不同的是在main 里面定义了两个函数
    def send_pkt(payload='', eof=False):
        return tb.txpath.send_pkt(payload, eof)

    def rx_callback(ok, payload):
        print "ok = %r, payload = '%s'" % (ok, payload)
一个用于数据包的发送,一个用于接收,而从当前的程序来看,rx_callback只是定义了,并没有被使用,所以benchmark_tx还只是单纯的发送程序。
2)send_pkt
在阅读benchmark_tx相关的源程序时,我们会发现,里面有许多的send_pkt程序,容易混乱。为了理清楚关系,这里首先将send_pkt拎出来单独的介绍。
程序首先是从main函数开始运行的,而最早被调用的send_pkt是在main 函数中调用的,为了便于叙述我们将其表示为 main::send_pkt, 而在main 函数中,发送数据包是通过调用一连串的send_pkt来实现的,如
send_pkt(payload),我们不妨表示为main::send_pkt(payload)
而根据main中的send_pkt的定义可知
   def main::send_pkt(payload='', eof=False):
        return tb.txpath.send_pkt(payload, eof)
(注意源代码中是send_pkt,我们用main::send_pkt为的是能够清晰的看到其作用域)
所以main函数中调用的send_pkt实际上是调用了txpath的send_pkt
而txpath是类class transmit_path的一个实例
所以调用过程为
(1) main::send_pkt(payload)
(2) transmit_path::send_pkt(payload)
transmit_path::send_pkt的定义可以在transmit_path中找到
    def transmit_path::send_pkt(self, payload='', eof=False):
        """
        Calls the transmitter method to send a packet
        """
        return self.packet_transmitter.send_pkt(payload, eof)
(注意源代码中是send_pkt,我们用transmit_path::send_pkt为的是能够清晰的看到其作用域)
由于        
self.packet_transmitter = \
            blks2.mod_pkts(self._modulator_class(**mod_kwargs),
                           access_code=None,
                           msgq_limit=4,
                           pad_for_usrp=True,
                           use_whitener_offset=options.use_whitener_offset)
接下来就是调用
(3)blks2.mod_pkts::send_pkt(payload)
blks2.mod_pkts::send_pkt可以在xxxxx/gnuradiox.x.x/gnuradio-core/src/python/gnuradio/blk2impl/pkt.py 中可以找到其定义
    def send_pkt(self, payload='', eof=False):
        """
        Send the payload.

        @param payload: data to send
        @type payload: string
        """
        if eof:
            msg = gr.message(1) # tell self._pkt_input we're not sending any more packets
        else:
            # print "original_payload =", string_to_hex_list(payload)
            pkt = packet_utils.make_packet(payload,
                                           self._modulator.samples_per_symbol(),
                                           self._modulator.bits_per_symbol(),
                                           self._access_code,
                                           self._pad_for_usrp,
                                           self._whitener_offset)
            #print "pkt =", string_to_hex_list(pkt)
            msg = gr.message_from_string(pkt)
            if self._use_whitener_offset is True:
                self._whitener_offset = (self._whitener_offset + 1) % 16
                
        self._pkt_input.msgq().insert_tail(msg)
可以看到最终send_pkt 完成的是
self._pkt_input.msgq().insert_tail(msg)
就是在信息列表_pkt_input的尾部插入一个信息节点,而信息列表采用的是gr.message_source
self._pkt_input = gr.message_source(gr.sizeof_char, msgq_limit)
最后总结一下整个send_pkt的过程
(1) main::send_pkt(payload)
(2) transmit_path::send_pkt(payload)
(3)blks2.mod_pkts::send_pkt(payload)
经过这三步就往blks2.mod_pkts中的信息链表_pkt_input =gr.message_source中添加了一个节点,而blks2.mod_pkts模块会一直查询自己的信息节点_pkt_input的节点是否为空,如果不为空就将最前面的节点信息以比特流的方式发送给调制模块,然后删除该节点,所以等到_pkt_input为空则结束,为了便于判断为空,在发送信息的最后需要加一个标志节点,如在benchmark_tx中在发送的最后有这么一句
send_pkt(eof=True)
3) gr.message_source
论坛里有网友说找不到message_source的源码,可以根据包含关键字查找,关键字在有足够的区分度时,长度越短越好,我搜多的关键字就是message_source,最后可以找到在
gnuradio-3.2.1\gnuradio-core\src\lib\io中
gr_message_source.h
gr_message_source.cc
首先看看其定义
class gr_message_source : public gr_sync_block
{
private:
  size_t         d_itemsize;
  gr_msg_queue_sptr    d_msgq;
  gr_message_sptr    d_msg;
  unsigned        d_msg_offset;
  bool            d_eof;

  friend gr_message_source_sptr
  gr_make_message_source(size_t itemsize, int msgq_limit);

protected:
  gr_message_source (size_t itemsize, int msgq_limit);

public:
  ~gr_message_source ();

  gr_msg_queue_sptr    msgq() const { return d_msgq; }

  int work (int noutput_items,
        gr_vector_const_void_star &input_items,
        gr_vector_void_star &output_items);
};
首先看其构建函数,在gr_message_source.cc文件中可以找到
gr_message_source::gr_message_source (size_t itemsize, int msgq_limit)
  : gr_sync_block("message_source",
          gr_make_io_signature(0, 0, 0),
          gr_make_io_signature(1, 1, itemsize)),
    d_itemsize(itemsize), d_msgq(gr_make_msg_queue(msgq_limit)), d_msg_offset(0), d_eof(false)
{
}
可以看出来,它是一个信号源模块,只有输出没有输入,而信号源就是通过构建函数所创建的d_msgq(gr_make_msg_queue(msgq_limit))中存储的信息提供,前面我们也介绍了通过
send_pkt就实现了self._pkt_input.msgq().insert_tail(msg)
而msgq() 的定义为
gr_msg_queue_sptr    msgq() const { return d_msgq; }
其实就是d_msgq

然后看起工作函数work
int
gr_message_source::work(int noutput_items,
            gr_vector_const_void_star &input_items,
            gr_vector_void_star &output_items)
{
  char *out = (char *) output_items[0];
//首先找到输出端口的指针
  int nn = 0;
//noutput_items是输出的数据长度,这个长度和时序模拟的算法有关,每次调用work时会根据时序模拟的数据流算法自动给出,由于不是很关心,所以我没有去详细查证。
  while (nn < noutput_items){
if (d_msg){
// 其实d_msg = d_msgq->delete_head();是指向信息列表的指针,可以看出是直接指向数据
      //
      // Consume whatever we can from the current message
      //
      int mm = std::min(noutput_items - nn, (int)((d_msg->length() - d_msg_offset) / d_itemsize));
//确定输出的长度,取还需输出的长度noutput_items – nn,和该信息节点剩余的比特数
      memcpy (out, &(d_msg->msg()[d_msg_offset]), mm * d_itemsize);
//将数据拷贝到输出端口
    //cout<<"message out="<<strlen(out)<<" "<<out<<endl;
      nn += mm;
      out += mm * d_itemsize;
      d_msg_offset += mm * d_itemsize;
//调整输出和输入的位置以及需输出的比特数
      assert(d_msg_offset <= d_msg->length());

      if (d_msg_offset == d_msg->length()){//如果该信息节点的数据已经输出完毕
    if (d_msg->type() == 1)               // type == 1 sets EOF
      d_eof = true;  
    d_msg.reset();  //如果信息全部输出完毕,信息复位
      }
    }
    else {
      //
      // No current message
//如果当前没有信息
      //
      if (d_msgq->empty_p() && nn > 0){    // no more messages in the queue, return what we've got
    break;
//如果信息空了,跳出循环
      }

      if (d_eof)
    return -1;  //如果是结束标志,返回

      d_msg = d_msgq->delete_head();       // block, waiting for a message
// 取下一条信息,同时删除当前信息
      d_msg_offset = 0;
//当前信息偏置为0
      if ((d_msg->length() % d_itemsize) != 0)
    throw std::runtime_error("msg length is not a multiple of d_itemsize");
//如果信息长度与输出数据结构不是整数倍关系,则给出错误信息,如信息比特数为80即十个字节,而调制方式选择8psk那么每个符号3比特那么,就会出现这个错误
    }
  }

  return nn;
}

所以message_source的工作流程可以归纳为
查询自己的信息节点d_mesg的节点是否为空,如果不为空就将最前面的节点信息以比特流的方式输出,然后删除该节点,所以等到d_mesg为空则结束
4)transmit_path.py
从图3可知,top_block里面只包含一个transmit_path模块(图4是后期版本中的封装关系,将transmit_path中的usrp模块拎出来,然后外面再包了一层usrp_transmit_path,这样benchmark_loopback就相对简单,只需要将transmit_path ,模拟信道和receive_path相连,即可完成不经过USRP硬件的调制解调模拟过程)
仍然以图3为例,transmit_path 中有三个模块
blks2.mod_pkts,amp=gr.multiply_const_cc( ) 和
self.u = usrp_options.create_usrp_sink(options)
而self.u是通过调用函数self._setup_usrp_sink(options)实现构建的,blks2.mod_pkts完成信息的基带调制,amp只是将调制后的信号乘上一个幅度值,用于调整输出信号的强度,而self.u则对应usrp发送通道,实现基带信号到射频信号的转换并发送,benchmark中的usrp硬件设备的配置内容相对独立,将在下一部分详细介绍,所以此处我们详细介绍一下blks2.mod_pkts
打开文件 gnuradio-core\src\python\gnuradio\blks2impl\pkt.py
可以找到其定义
class mod_pkts(gr.hier_block2):
其实源文件给出不少关于该模块接口的注释
在初始化函数
def __init__(self, modulator, access_code=None, msgq_limit=2, pad_for_usrp=True, use_whitener_offset=False):
下一行通过 “ “ “ 开始 “ ” ”结束的这一段就是端口的说明部分
        """
    Hierarchical block for sending packets

        Packets to be sent are enqueued by calling send_pkt.
        The output is the complex modulated signal at baseband.

        @param modulator: instance of modulator class (gr_block or hier_block2)
参数 modulator: 调制类的一个实例
        @type modulator: complex baseband out
Modulator的类型:基带复数信号输出
        @param access_code: AKA sync vector
参数access_code: AKA 同步向量
        @type access_code: string of 1's and 0's between 1 and 64 long
Access_code类型:长度从1到64的“0”,“1”字符串,
        @param msgq_limit: maximum number of messages in message queue
Msgq_limit:信息序列的最大信息节点数(send_pkt会增加信息节点,而gr.message_source 会不断的将信息节点的内容输出,并删除以输出的信息节点,所以这样就会有一个动态平衡,但是如果添加节点的速度大于删除节点的速度,信息序列的长度将会不断增加,因此给出一个最大长度的限制)
        @type msgq_limit: int 类型为整型
        @param pad_for_usrp: If true, packets are padded such that they end up a multiple of 128 samples
参数 pad_for_usrp:如果为真,那么数据包将按照128个复数样值进行打包,为的是适应USRP设备的数据读取长度
        @param use_whitener_offset: If true, start of whitener XOR string is incremented each packet
参数 use_whitener_offset: 如果设置为真,whitener 异或字符串的起点将会随着每个数据包的输出而增加
        See gmsk_mod for remaining parameters
        """
pkt = packet_utils.make_packet(payload,
                                           self._modulator.samples_per_symbol(),
                                           self._modulator.bits_per_symbol(),
                                           self._access_code,
                                           self._pad_for_usrp,
                                           self._whitener_offset)
不少参数在send_pkt里面的参数打包时会用上,make_packet 就是按照一定规则进行数据打包,如果有兴趣,以后可以深入研究。

在mod_pkts中可以看到有一个同步码的配置的过程,benchmark体制是每一个信息节点的发送都会有一个同步头,而同步头的内容就是access_code的内容,但是这个access_code 我的理解只是帧同步的功能,如接收端对其的利用时通过对解调后的数据进行比对,当比对正确率达到一定程度,即认为发现了数据包,同时完成了帧同步,具体过程可以查看
gr_correlate_access_code_bb.cc在gnuradio-core/src/general中
回到pkt.py 的mod_pkt 中
        if access_code is None:
            access_code = packet_utils.default_access_code
        if not packet_utils.is_1_0_string(access_code):
            raise ValueError, "Invalid access_code %r. Must be string of 1's and 0's" % (access_code,)
        self._access_code = access_code
同步序列是必须配置的,如果没有则用默认序列
        # accepts messages from the outside world
        self._pkt_input = gr.message_source(gr.sizeof_char, msgq_limit)
        self.connect(self._pkt_input, self._modulator, self)
最后将各个模块相连,_pkt_input在send_pkt小节已经详细介绍了, 如何调用options里面配置的modulation 可以参照benchmark_rx中如何调用demodulation。

未完待续……

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多